From 104c8b890dbca25a0d08b2567d003f02953a0fc1 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Sat, 8 Dec 2018 14:12:50 +0000 Subject: Backport EE GroupSAML origin verification changes --- spec/controllers/omniauth_callbacks_controller_spec.rb | 2 +- spec/support/helpers/login_helpers.rb | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 59463462e5a..f995867c5e1 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -170,7 +170,7 @@ describe OmniauthCallbacksController, type: :controller do before do stub_omniauth_saml_config({ enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config] }) - mock_auth_hash('saml', 'my-uid', user.email, mock_saml_response) + mock_auth_hash_with_saml_xml('saml', 'my-uid', user.email, mock_saml_response) request.env["devise.mapping"] = Devise.mappings[:user] request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth'] post :saml, params: { SAMLResponse: mock_saml_response } diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 87cfb6c04dc..c709f160bff 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -47,7 +47,7 @@ module LoginHelpers end def gitlab_sign_in_via(provider, user, uid, saml_response = nil) - mock_auth_hash(provider, uid, user.email, saml_response) + mock_auth_hash_with_saml_xml(provider, uid, user.email, saml_response) visit new_user_session_path click_link provider end @@ -87,7 +87,12 @@ module LoginHelpers click_link "oauth-login-#{provider}" end - def mock_auth_hash(provider, uid, email, saml_response = nil) + def mock_auth_hash_with_saml_xml(provider, uid, email, saml_response) + response_object = { document: saml_xml(saml_response) } + mock_auth_hash(provider, uid, email, response_object: response_object) + end + + def mock_auth_hash(provider, uid, email, response_object: nil) # The mock_auth configuration allows you to set per-provider (or default) # authentication hashes to return during integration testing. OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({ @@ -110,9 +115,7 @@ module LoginHelpers image: 'mock_user_thumbnail_url' } }, - response_object: { - document: saml_xml(saml_response) - } + response_object: response_object } }) Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym] -- cgit v1.2.1 From 276147c1f6c54ae87941953ec62d0d674c12f462 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Fri, 25 Jan 2019 15:44:50 +0800 Subject: Respond with 403 when non-member requests for private MRs --- spec/requests/api/commits_spec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 6b9bc6eda6a..c24e17fda3f 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -1430,8 +1430,8 @@ describe API::Commits do end describe 'GET /projects/:id/repository/commits/:sha/merge_requests' do - let!(:project) { create(:project, :repository, :private) } - let!(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') } + let(:project) { create(:project, :repository, :private) } + let(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') } let(:commit) { merged_mr.merge_request_diff.commits.last } it 'returns the correct merge request' do @@ -1456,5 +1456,16 @@ describe API::Commits do expect(response).to have_gitlab_http_status(404) end + + context 'public project' do + let(:project) { create(:project, :repository, :public, :merge_requests_private) } + let(:non_member) { create(:user) } + + it 'responds 403 when only members are allowed to read merge requests' do + get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", non_member) + + expect(response).to have_gitlab_http_status(403) + end + end end end -- cgit v1.2.1 From 65723a2cb60f46681a766f34102df700ef5b7573 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Fri, 25 Jan 2019 17:22:48 +0800 Subject: Modify MergeRequestsFinder to allow filtering by commit --- spec/finders/merge_requests_finder_spec.rb | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 107da08a0a9..79f854cdb96 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -31,7 +31,7 @@ describe MergeRequestsFinder do p end end - let(:project4) { create_project_without_n_plus_1(group: subgroup) } + let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) } let(:project5) { create_project_without_n_plus_1(group: subgroup) } let(:project6) { create_project_without_n_plus_1(group: subgroup) } @@ -68,6 +68,15 @@ describe MergeRequestsFinder do expect(merge_requests.size).to eq(2) end + it 'filters by commit sha' do + merge_requests = described_class.new( + user, + commit_sha: merge_request5.merge_request_diff.last_commit_sha + ).execute + + expect(merge_requests).to contain_exactly(merge_request5) + end + context 'filtering by group' do it 'includes all merge requests when user has access' do params = { group_id: group.id } @@ -269,6 +278,21 @@ describe MergeRequestsFinder do expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) end end + + context 'when project restricts merge requests' do + let(:non_member) { create(:user) } + let(:project) { create(:project, :repository, :public, :merge_requests_private) } + let!(:merge_request) { create(:merge_request, source_project: project) } + + it "returns nothing to to non members" do + merge_requests = described_class.new( + non_member, + project_id: project.id + ).execute + + expect(merge_requests).to be_empty + end + end end describe '#row_count', :request_store do -- cgit v1.2.1 From d5c858cd4032b3bf37c6fbe47340ccea825503bc Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Fri, 8 Feb 2019 16:11:37 +0000 Subject: Prevent Releases links API to leak tag existance --- spec/requests/api/release/links_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb index ba948e37e2f..3a59052bb29 100644 --- a/spec/requests/api/release/links_spec.rb +++ b/spec/requests/api/release/links_spec.rb @@ -73,6 +73,22 @@ describe API::Release::Links do expect(response).to have_gitlab_http_status(:ok) end end + + context 'when project is public and the repository is private' do + let(:project) { create(:project, :repository, :public, :repository_private) } + + it_behaves_like '403 response' do + let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) } + end + + context 'when the release does not exists' do + let!(:release) { } + + it_behaves_like '403 response' do + let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) } + end + end + end end end -- cgit v1.2.1 From 5dc047dc72c08a64aaf4f4a0c9fe0fba2742b905 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Mon, 11 Feb 2019 18:51:53 +0800 Subject: Disable board policies when issues are disabled Board list policies are also included --- spec/policies/project_policy_spec.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 93a468f585b..f8d581ef38f 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -130,22 +130,26 @@ describe ProjectPolicy do subject { described_class.new(owner, project) } context 'when the feature is disabled' do - it 'does not include the issues permissions' do + before do project.issues_enabled = false project.save! + end + it 'does not include the issues permissions' do expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue end - end - context 'when the feature is disabled and external tracker configured' do - it 'does not include the issues permissions' do - create(:jira_service, project: project) + it 'disables boards and lists permissions' do + expect_disallowed :read_board, :create_board, :update_board, :admin_board + expect_disallowed :read_list, :create_list, :update_list, :admin_list + end - project.issues_enabled = false - project.save! + context 'when external tracker configured' do + it 'does not include the issues permissions' do + create(:jira_service, project: project) - expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue + expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue + end end end end -- cgit v1.2.1 From e9b84f50e961ee7c3abfb8192de8f4fc778df041 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 11 Feb 2019 15:48:37 -0200 Subject: Migrate issuable states to integer patch 1 Patch 1 that migrates issues/merge requests states from integer to string. On this commit we are only adding the state_id column and syncing it with a backgroud migration. On Patch 2 the code to use the new integer column will be deployed and the old column will be removed. --- spec/migrations/add_state_id_to_issuables_spec.rb | 89 +++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 spec/migrations/add_state_id_to_issuables_spec.rb (limited to 'spec') diff --git a/spec/migrations/add_state_id_to_issuables_spec.rb b/spec/migrations/add_state_id_to_issuables_spec.rb new file mode 100644 index 00000000000..4416f416c18 --- /dev/null +++ b/spec/migrations/add_state_id_to_issuables_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190206144959_change_issuable_states_to_integer.rb') + +describe AddStateIdToIssuables, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + let(:issues) { table(:issues) } + let(:migration) { described_class.new } + + before do + @group = namespaces.create!(name: 'gitlab', path: 'gitlab') + @project = projects.create!(namespace_id: @group.id) + end + + describe '#up' do + context 'issues' do + it 'migrates state column to integer' do + opened_issue = issues.create!(description: 'first', state: 'opened') + closed_issue = issues.create!(description: 'second', state: 'closed') + nil_state_issue = issues.create!(description: 'third', state: nil) + + migrate! + + issues.reset_column_information + expect(opened_issue.reload.state).to eq(Issue.states.opened) + expect(closed_issue.reload.state).to eq(Issue.states.closed) + expect(nil_state_issue.reload.state).to eq(nil) + end + end + + context 'merge requests' do + it 'migrates state column to integer' do + opened_merge_request = merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') + closed_merge_request = merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') + merged_merge_request = merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') + locked_merge_request = merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') + nil_state_merge_request = merge_requests.create!(state: nil, target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') + + migrate! + + merge_requests.reset_column_information + expect(opened_merge_request.reload.state).to eq(MergeRequest.states.opened) + expect(closed_merge_request.reload.state).to eq(MergeRequest.states.closed) + expect(merged_merge_request.reload.state).to eq(MergeRequest.states.merged) + expect(locked_merge_request.reload.state).to eq(MergeRequest.states.locked) + expect(nil_state_merge_request.reload.state).to eq(nil) + end + end + end + + describe '#down' do + context 'issues' do + it 'migrates state column to string' do + opened_issue = issues.create!(description: 'first', state: 1) + closed_issue = issues.create!(description: 'second', state: 2) + nil_state_issue = issues.create!(description: 'third', state: nil) + + migration.down + + issues.reset_column_information + expect(opened_issue.reload.state).to eq('opened') + expect(closed_issue.reload.state).to eq('closed') + expect(nil_state_issue.reload.state).to eq(nil) + end + end + + context 'merge requests' do + it 'migrates state column to string' do + opened_merge_request = merge_requests.create!(state: 1, target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') + closed_merge_request = merge_requests.create!(state: 2, target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') + merged_merge_request = merge_requests.create!(state: 3, target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') + locked_merge_request = merge_requests.create!(state: 4, target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') + nil_state_merge_request = merge_requests.create!(state: nil, target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') + + migration.down + + merge_requests.reset_column_information + expect(opened_merge_request.reload.state).to eq('opened') + expect(closed_merge_request.reload.state).to eq('closed') + expect(merged_merge_request.reload.state).to eq('merged') + expect(locked_merge_request.reload.state).to eq('locked') + expect(nil_state_merge_request.reload.state).to eq(nil) + end + end + end +end -- cgit v1.2.1 From 362d56e65a0e23fcf4fd5bd4535d258c3659ffd5 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 12 Feb 2019 14:40:37 -0200 Subject: Schedule background migrations and specs --- spec/migrations/add_state_id_to_issuables_spec.rb | 60 +++++------------------ 1 file changed, 12 insertions(+), 48 deletions(-) (limited to 'spec') diff --git a/spec/migrations/add_state_id_to_issuables_spec.rb b/spec/migrations/add_state_id_to_issuables_spec.rb index 4416f416c18..b0e285db1f3 100644 --- a/spec/migrations/add_state_id_to_issuables_spec.rb +++ b/spec/migrations/add_state_id_to_issuables_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -require Rails.root.join('db', 'migrate', '20190206144959_change_issuable_states_to_integer.rb') +require Rails.root.join('db', 'migrate', '20190211131150_add_state_id_to_issuables.rb') describe AddStateIdToIssuables, :migration do let(:namespaces) { table(:namespaces) } @@ -20,14 +20,15 @@ describe AddStateIdToIssuables, :migration do it 'migrates state column to integer' do opened_issue = issues.create!(description: 'first', state: 'opened') closed_issue = issues.create!(description: 'second', state: 'closed') + invalid_state_issue = issues.create!(description: 'fourth', state: 'not valid') nil_state_issue = issues.create!(description: 'third', state: nil) migrate! - issues.reset_column_information - expect(opened_issue.reload.state).to eq(Issue.states.opened) - expect(closed_issue.reload.state).to eq(Issue.states.closed) - expect(nil_state_issue.reload.state).to eq(nil) + expect(opened_issue.reload.state_id).to eq(Issue.states.opened) + expect(closed_issue.reload.state_id).to eq(Issue.states.closed) + expect(invalid_state_issue.reload.state_id).to be_nil + expect(nil_state_issue.reload.state_id).to be_nil end end @@ -37,52 +38,15 @@ describe AddStateIdToIssuables, :migration do closed_merge_request = merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') merged_merge_request = merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') locked_merge_request = merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') - nil_state_merge_request = merge_requests.create!(state: nil, target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') + invalid_state_merge_request = merge_requests.create!(state: 'not valid', target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') migrate! - merge_requests.reset_column_information - expect(opened_merge_request.reload.state).to eq(MergeRequest.states.opened) - expect(closed_merge_request.reload.state).to eq(MergeRequest.states.closed) - expect(merged_merge_request.reload.state).to eq(MergeRequest.states.merged) - expect(locked_merge_request.reload.state).to eq(MergeRequest.states.locked) - expect(nil_state_merge_request.reload.state).to eq(nil) - end - end - end - - describe '#down' do - context 'issues' do - it 'migrates state column to string' do - opened_issue = issues.create!(description: 'first', state: 1) - closed_issue = issues.create!(description: 'second', state: 2) - nil_state_issue = issues.create!(description: 'third', state: nil) - - migration.down - - issues.reset_column_information - expect(opened_issue.reload.state).to eq('opened') - expect(closed_issue.reload.state).to eq('closed') - expect(nil_state_issue.reload.state).to eq(nil) - end - end - - context 'merge requests' do - it 'migrates state column to string' do - opened_merge_request = merge_requests.create!(state: 1, target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') - closed_merge_request = merge_requests.create!(state: 2, target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') - merged_merge_request = merge_requests.create!(state: 3, target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') - locked_merge_request = merge_requests.create!(state: 4, target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') - nil_state_merge_request = merge_requests.create!(state: nil, target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') - - migration.down - - merge_requests.reset_column_information - expect(opened_merge_request.reload.state).to eq('opened') - expect(closed_merge_request.reload.state).to eq('closed') - expect(merged_merge_request.reload.state).to eq('merged') - expect(locked_merge_request.reload.state).to eq('locked') - expect(nil_state_merge_request.reload.state).to eq(nil) + expect(opened_merge_request.reload.state_id).to eq(MergeRequest.states.opened) + expect(closed_merge_request.reload.state_id).to eq(MergeRequest.states.closed) + expect(merged_merge_request.reload.state_id).to eq(MergeRequest.states.merged) + expect(locked_merge_request.reload.state_id).to eq(MergeRequest.states.locked) + expect(invalid_state_merge_request.reload.state_id).to be_nil end end end -- cgit v1.2.1 From a9291f15ea10e3cfc94282ffb4e0969e9d4175eb Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Wed, 13 Feb 2019 16:23:58 +0800 Subject: Align spec with actual usage Currently we pass temp file path to FileMover --- spec/uploaders/file_mover_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb index de29d0c943f..a28d7445b1c 100644 --- a/spec/uploaders/file_mover_spec.rb +++ b/spec/uploaders/file_mover_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe FileMover do let(:filename) { 'banana_sample.gif' } - let(:file) { fixture_file_upload(File.join('spec', 'fixtures', filename)) } let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) } let(:temp_description) do @@ -12,7 +11,7 @@ describe FileMover do let(:file_path) { File.join('uploads/-/system/personal_snippet', snippet.id.to_s, 'secret55', filename) } let(:snippet) { create(:personal_snippet, description: temp_description) } - subject { described_class.new(file_path, snippet).execute } + subject { described_class.new(temp_file_path, snippet).execute } describe '#execute' do before do -- cgit v1.2.1 From 37741c59a4daf1b0d6d9f7a6a51337e9d8effb66 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 14 Feb 2019 11:48:20 -0200 Subject: Split background migration for issues and merge requests --- spec/migrations/add_state_id_to_issuables_spec.rb | 53 ---------------------- .../schedule_sync_issuables_state_id_spec.rb | 53 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 53 deletions(-) delete mode 100644 spec/migrations/add_state_id_to_issuables_spec.rb create mode 100644 spec/migrations/schedule_sync_issuables_state_id_spec.rb (limited to 'spec') diff --git a/spec/migrations/add_state_id_to_issuables_spec.rb b/spec/migrations/add_state_id_to_issuables_spec.rb deleted file mode 100644 index b0e285db1f3..00000000000 --- a/spec/migrations/add_state_id_to_issuables_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20190211131150_add_state_id_to_issuables.rb') - -describe AddStateIdToIssuables, :migration do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:merge_requests) { table(:merge_requests) } - let(:issues) { table(:issues) } - let(:migration) { described_class.new } - - before do - @group = namespaces.create!(name: 'gitlab', path: 'gitlab') - @project = projects.create!(namespace_id: @group.id) - end - - describe '#up' do - context 'issues' do - it 'migrates state column to integer' do - opened_issue = issues.create!(description: 'first', state: 'opened') - closed_issue = issues.create!(description: 'second', state: 'closed') - invalid_state_issue = issues.create!(description: 'fourth', state: 'not valid') - nil_state_issue = issues.create!(description: 'third', state: nil) - - migrate! - - expect(opened_issue.reload.state_id).to eq(Issue.states.opened) - expect(closed_issue.reload.state_id).to eq(Issue.states.closed) - expect(invalid_state_issue.reload.state_id).to be_nil - expect(nil_state_issue.reload.state_id).to be_nil - end - end - - context 'merge requests' do - it 'migrates state column to integer' do - opened_merge_request = merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') - closed_merge_request = merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') - merged_merge_request = merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') - locked_merge_request = merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') - invalid_state_merge_request = merge_requests.create!(state: 'not valid', target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') - - migrate! - - expect(opened_merge_request.reload.state_id).to eq(MergeRequest.states.opened) - expect(closed_merge_request.reload.state_id).to eq(MergeRequest.states.closed) - expect(merged_merge_request.reload.state_id).to eq(MergeRequest.states.merged) - expect(locked_merge_request.reload.state_id).to eq(MergeRequest.states.locked) - expect(invalid_state_merge_request.reload.state_id).to be_nil - end - end - end -end diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb new file mode 100644 index 00000000000..a926ee38387 --- /dev/null +++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190214112022_schedule_sync_issuables_state_id.rb') + +describe ScheduleSyncIssuablesStateId, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + let(:issues) { table(:issues) } + let(:migration) { described_class.new } + + before do + @group = namespaces.create!(name: 'gitlab', path: 'gitlab') + @project = projects.create!(namespace_id: @group.id) + end + + describe '#up' do + context 'issues' do + it 'migrates state column to integer' do + opened_issue = issues.create!(description: 'first', state: 'opened') + closed_issue = issues.create!(description: 'second', state: 'closed') + invalid_state_issue = issues.create!(description: 'fourth', state: 'not valid') + nil_state_issue = issues.create!(description: 'third', state: nil) + + migrate! + + expect(opened_issue.reload.state_id).to eq(Issue.available_states[:opened]) + expect(closed_issue.reload.state_id).to eq(Issue.available_states[:closed]) + expect(invalid_state_issue.reload.state_id).to be_nil + expect(nil_state_issue.reload.state_id).to be_nil + end + end + + context 'merge requests' do + it 'migrates state column to integer' do + opened_merge_request = merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') + closed_merge_request = merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') + merged_merge_request = merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') + locked_merge_request = merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') + invalid_state_merge_request = merge_requests.create!(state: 'not valid', target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') + + migrate! + + expect(opened_merge_request.reload.state_id).to eq(MergeRequest.available_states[:opened]) + expect(closed_merge_request.reload.state_id).to eq(MergeRequest.available_states[:closed]) + expect(merged_merge_request.reload.state_id).to eq(MergeRequest.available_states[:merged]) + expect(locked_merge_request.reload.state_id).to eq(MergeRequest.available_states[:locked]) + expect(invalid_state_merge_request.reload.state_id).to be_nil + end + end + end +end -- cgit v1.2.1 From 30ab6ee416783cd9481085f021603383eeb4f317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Mon, 14 Jan 2019 11:46:39 +0100 Subject: Check issue milestone availability Add project when creating milestone in specs We validate milestone is from the same project/parent group as issuable -> we need to set project in specs correctly Improve methods names and specs organization --- .../dashboard/milestones_controller_spec.rb | 2 +- spec/features/issues_spec.rb | 4 +- .../user_lists_merge_requests_spec.rb | 4 +- spec/models/concerns/issuable_spec.rb | 97 ++++++++++++++++++++-- spec/models/issue/metrics_spec.rb | 6 +- spec/models/milestone_spec.rb | 8 +- spec/requests/api/issues_spec.rb | 2 +- .../issuable/common_system_notes_service_spec.rb | 4 +- spec/services/issues/build_service_spec.rb | 76 ++++++++--------- spec/services/issues/update_service_spec.rb | 6 +- spec/services/merge_requests/build_service_spec.rb | 9 ++ .../services/merge_requests/update_service_spec.rb | 6 +- .../shared_examples/issuable_shared_examples.rb | 2 +- 13 files changed, 158 insertions(+), 68 deletions(-) (limited to 'spec') diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 8b176e07bc8..ddf33ebad16 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -15,7 +15,7 @@ describe Dashboard::MilestonesController do ) end let(:issue) { create(:issue, project: project, milestone: project_milestone) } - let(:group_issue) { create(:issue, milestone: group_milestone) } + let(:group_issue) { create(:issue, milestone: group_milestone, project: create(:project, group: group)) } let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) } let!(:group_label) { create(:group_label, group: group, title: 'Group Issue Label', issues: [group_issue]) } diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 406e80e91aa..9bc340ed4bb 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -233,8 +233,8 @@ describe 'Issues' do created_at: Time.now - (index * 60)) end end - let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') } - let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } + let(:newer_due_milestone) { create(:milestone, project: project, due_date: '2013-12-11') } + let(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') } it 'sorts by newest' do visit project_issues_path(project, sort: sort_value_created_date) diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index ef7ae490b0f..c691011b9ca 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -13,7 +13,7 @@ describe 'Merge requests > User lists merge requests' do source_project: project, source_branch: 'fix', assignee: user, - milestone: create(:milestone, due_date: '2013-12-11'), + milestone: create(:milestone, project: project, due_date: '2013-12-11'), created_at: 1.minute.ago, updated_at: 1.minute.ago) create(:merge_request, @@ -21,7 +21,7 @@ describe 'Merge requests > User lists merge requests' do source_project: project, source_branch: 'markdown', assignee: user, - milestone: create(:milestone, due_date: '2013-12-12'), + milestone: create(:milestone, project: project, due_date: '2013-12-12'), created_at: 2.minutes.ago, updated_at: 2.minutes.ago) create(:merge_request, diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 41159348e04..72c6161424b 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -32,17 +32,56 @@ describe Issuable do end describe "Validation" do - subject { build(:issue) } + context 'general validations' do + subject { build(:issue) } - before do - allow(InternalId).to receive(:generate_next).and_return(nil) + before do + allow(InternalId).to receive(:generate_next).and_return(nil) + end + + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:iid) } + it { is_expected.to validate_presence_of(:author) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_length_of(:title).is_at_most(255) } end - it { is_expected.to validate_presence_of(:project) } - it { is_expected.to validate_presence_of(:iid) } - it { is_expected.to validate_presence_of(:author) } - it { is_expected.to validate_presence_of(:title) } - it { is_expected.to validate_length_of(:title).is_at_most(255) } + describe 'milestone' do + let(:project) { create(:project) } + let(:milestone_id) { create(:milestone, project: project).id } + let(:params) do + { + title: 'something', + project: project, + author: build(:user), + milestone_id: milestone_id + } + end + + subject { issuable_class.new(params) } + + context 'with correct params' do + it { is_expected.to be_valid } + end + + context 'with empty string milestone' do + let(:milestone_id) { '' } + + it { is_expected.to be_valid } + end + + context 'with nil milestone id' do + let(:milestone_id) { nil } + + it { is_expected.to be_valid } + end + + context 'with a milestone id from another project' do + let(:milestone_id) { create(:milestone).id } + + it { is_expected.to be_invalid } + end + end end describe "Scope" do @@ -66,6 +105,48 @@ describe Issuable do end end + describe '#milestone_available?' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:issue) { create(:issue, project: project) } + + def build_issuable(milestone_id) + issuable_class.new(project: project, milestone_id: milestone_id) + end + + it 'returns true with a milestone from the issue project' do + milestone = create(:milestone, project: project) + + expect(build_issuable(milestone.id).milestone_available?).to be_truthy + end + + it 'returns true with a milestone from the issue project group' do + milestone = create(:milestone, group: group) + + expect(build_issuable(milestone.id).milestone_available?).to be_truthy + end + + it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do + parent = create(:group) + group.update(parent: parent) + milestone = create(:milestone, group: parent) + + expect(build_issuable(milestone.id).milestone_available?).to be_truthy + end + + it 'returns false with a milestone from another project' do + milestone = create(:milestone) + + expect(build_issuable(milestone.id).milestone_available?).to be_falsey + end + + it 'returns false with a milestone from another group' do + milestone = create(:milestone, group: create(:group)) + + expect(build_issuable(milestone.id).milestone_available?).to be_falsey + end + end + describe ".search" do let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") } let!(:searchable_issue2) { create(:issue, title: 'Aw') } diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index 1bf0ecb98ad..b7291eebe64 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -9,7 +9,7 @@ describe Issue::Metrics do context "milestones" do it "records the first time an issue is associated with a milestone" do time = Time.now - Timecop.freeze(time) { subject.update(milestone: create(:milestone)) } + Timecop.freeze(time) { subject.update(milestone: create(:milestone, project: project)) } metrics = subject.metrics expect(metrics).to be_present @@ -18,9 +18,9 @@ describe Issue::Metrics do it "does not record the second time an issue is associated with a milestone" do time = Time.now - Timecop.freeze(time) { subject.update(milestone: create(:milestone)) } + Timecop.freeze(time) { subject.update(milestone: create(:milestone, project: project)) } Timecop.freeze(time + 2.hours) { subject.update(milestone: nil) } - Timecop.freeze(time + 6.hours) { subject.update(milestone: create(:milestone)) } + Timecop.freeze(time + 6.hours) { subject.update(milestone: create(:milestone, project: project)) } metrics = subject.metrics expect(metrics).to be_present diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index af7e3d3a6c9..77b7042424c 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -182,7 +182,7 @@ describe Milestone do describe '#total_items_count' do before do create :closed_issue, milestone: milestone, project: project - create :merge_request, milestone: milestone + create :merge_request, milestone: milestone, source_project: project end it 'returns total count of issues and merge requests assigned to milestone' do @@ -192,10 +192,10 @@ describe Milestone do describe '#can_be_closed?' do before do - milestone = create :milestone - create :closed_issue, milestone: milestone + milestone = create :milestone, project: project + create :closed_issue, milestone: milestone, project: project - create :issue + create :issue, project: project end it 'returns true if milestone active and all nested issues closed' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 04908378a24..46cd3ec88e1 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -49,7 +49,7 @@ describe API::Issues do create(:label, title: 'label', color: '#FFAABB', project: project) end let!(:label_link) { create(:label_link, label: label, target: issue) } - set(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let(:milestone) { create(:milestone, title: '1.0.0', project: project) } set(:empty_milestone) do create(:milestone, title: '2.0.0', project: project) end diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb index fa5d5ebac5c..0edc9016c96 100644 --- a/spec/services/issuable/common_system_notes_service_spec.rb +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Issuable::CommonSystemNotesService do let(:user) { create(:user) } let(:project) { create(:project) } - let(:issuable) { create(:issue) } + let(:issuable) { create(:issue, project: project) } context 'on issuable update' do it_behaves_like 'system note creation', { title: 'New title' }, 'changed title' @@ -70,7 +70,7 @@ describe Issuable::CommonSystemNotesService do end context 'on issuable create' do - let(:issuable) { build(:issue) } + let(:issuable) { build(:issue, project: project) } subject { described_class.new(project, user).execute(issuable, old_labels: [], is_update: false) } diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 248e7d5a389..86e58fe06b9 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -8,29 +8,29 @@ describe Issues::BuildService do project.add_developer(user) end + def build_issue(issue_params = {}) + described_class.new(project, user, issue_params).execute + end + context 'for a single discussion' do describe '#execute' do let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) } let(:discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, note: "Almost done").to_discussion } - let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) } - it 'references the noteable title in the issue title' do - issue = service.execute + subject { build_issue(merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) } - expect(issue.title).to include('Hello world') + it 'references the noteable title in the issue title' do + expect(subject.title).to include('Hello world') end it 'adds the note content to the description' do - issue = service.execute - - expect(issue.description).to include('Almost done') + expect(subject.description).to include('Almost done') end end end context 'for discussions in a merge request' do let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) } - let(:issue) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute } describe '#items_for_discussions' do it 'has an item for each discussion' do @@ -66,28 +66,30 @@ describe Issues::BuildService do end describe '#execute' do - it 'has the merge request reference in the title' do - expect(issue.title).to include(merge_request.title) - end + let(:base_params) { { merge_request_to_resolve_discussions_of: merge_request.iid } } - it 'has the reference of the merge request in the description' do - expect(issue.description).to include(merge_request.to_reference) + context 'without additional params' do + subject { build_issue(base_params) } + + it 'has the merge request reference in the title' do + expect(subject.title).to include(merge_request.title) + end + + it 'has the reference of the merge request in the description' do + expect(subject.description).to include(merge_request.to_reference) + end end - it 'does not assign title when a title was given' do - issue = described_class.new(project, user, - merge_request_to_resolve_discussions_of: merge_request, - title: 'What an issue').execute + it 'uses provided title if title param given' do + issue = build_issue(base_params.merge(title: 'What an issue')) expect(issue.title).to eq('What an issue') end - it 'does not assign description when a description was given' do - issue = described_class.new(project, user, - merge_request_to_resolve_discussions_of: merge_request, - description: 'Fix at your earliest conveignance').execute + it 'uses provided description if description param given' do + issue = build_issue(base_params.merge(description: 'Fix at your earliest convenience')) - expect(issue.description).to eq('Fix at your earliest conveignance') + expect(issue.description).to eq('Fix at your earliest convenience') end describe 'with multiple discussions' do @@ -96,20 +98,20 @@ describe Issues::BuildService do it 'mentions all the authors in the description' do authors = merge_request.resolvable_discussions.map(&:author) - expect(issue.description).to include(*authors.map(&:to_reference)) + expect(build_issue(base_params).description).to include(*authors.map(&:to_reference)) end it 'has a link for each unresolved discussion in the description' do notes = merge_request.resolvable_discussions.map(&:first_note) links = notes.map { |note| Gitlab::UrlBuilder.build(note) } - expect(issue.description).to include(*links) + expect(build_issue(base_params).description).to include(*links) end it 'mentions additional notes' do create_list(:diff_note_on_merge_request, 2, noteable: merge_request, project: merge_request.target_project, in_reply_to: diff_note) - expect(issue.description).to include('(+2 comments)') + expect(build_issue(base_params).description).to include('(+2 comments)') end end end @@ -120,7 +122,7 @@ describe Issues::BuildService do describe '#execute' do it 'mentions the merge request in the description' do - issue = described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute + issue = build_issue(merge_request_to_resolve_discussions_of: merge_request.iid) expect(issue.description).to include("Review the conversation in #{merge_request.to_reference}") end @@ -128,20 +130,18 @@ describe Issues::BuildService do end describe '#execute' do - let(:milestone) { create(:milestone, project: project) } - it 'builds a new issues with given params' do - issue = described_class.new( - project, - user, - title: 'Issue #1', - description: 'Issue description', - milestone_id: milestone.id - ).execute - - expect(issue.title).to eq('Issue #1') - expect(issue.description).to eq('Issue description') + milestone = create(:milestone, project: project) + issue = build_issue(milestone_id: milestone.id) + expect(issue.milestone).to eq(milestone) end + + it 'sets milestone to nil if it is not available for the project' do + milestone = create(:milestone, project: create(:project)) + issue = build_issue(milestone_id: milestone.id) + + expect(issue.milestone).to be_nil + end end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 931e47d3a77..f1684209729 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -356,7 +356,7 @@ describe Issues::UpdateService, :mailer do it_behaves_like 'system notes for milestones' it 'sends notifications for subscribers of changed milestone' do - issue.milestone = create(:milestone) + issue.milestone = create(:milestone, project: project) issue.save @@ -380,7 +380,7 @@ describe Issues::UpdateService, :mailer do end it 'marks todos as done' do - update_issue(milestone: create(:milestone)) + update_issue(milestone: create(:milestone, project: project)) expect(todo.reload.done?).to eq true end @@ -389,7 +389,7 @@ describe Issues::UpdateService, :mailer do it 'sends notifications for subscribers of changed milestone' do perform_enqueued_jobs do - update_issue(milestone: create(:milestone)) + update_issue(milestone: create(:milestone, project: project)) end should_email(subscriber) diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 536d0d345a4..057e8137a4e 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -229,6 +229,15 @@ describe MergeRequests::BuildService do end end end + + context 'when a milestone is from another project' do + let(:milestone) { create(:milestone, project: create(:project)) } + let(:milestone_id) { milestone.id } + + it 'sets milestone to nil' do + expect(merge_request.milestone).to be_nil + 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 20580bf14b9..8e367db031c 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -328,7 +328,7 @@ describe MergeRequests::UpdateService, :mailer do it_behaves_like 'system notes for milestones' it 'sends notifications for subscribers of changed milestone' do - merge_request.milestone = create(:milestone) + merge_request.milestone = create(:milestone, project: project) merge_request.save @@ -352,7 +352,7 @@ describe MergeRequests::UpdateService, :mailer do end it 'marks pending todos as done' do - update_merge_request({ milestone: create(:milestone) }) + update_merge_request({ milestone: create(:milestone, project: project) }) expect(pending_todo.reload).to be_done end @@ -361,7 +361,7 @@ describe MergeRequests::UpdateService, :mailer do it 'sends notifications for subscribers of changed milestone' do perform_enqueued_jobs do - update_merge_request(milestone: create(:milestone)) + update_merge_request(milestone: create(:milestone, project: project)) end should_email(subscriber) diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb index c3d40c5b231..d97b21f71cd 100644 --- a/spec/support/shared_examples/issuable_shared_examples.rb +++ b/spec/support/shared_examples/issuable_shared_examples.rb @@ -31,7 +31,7 @@ shared_examples 'system notes for milestones' do context 'project milestones' do it 'creates a system note' do expect do - update_issuable(milestone: create(:milestone)) + update_issuable(milestone: create(:milestone, project: project)) end.to change { Note.system.count }.by(1) end end -- cgit v1.2.1 From 8ad127083ef28cfea389ec00a6f7ebf39e15c431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Mon, 11 Feb 2019 11:31:30 +0100 Subject: Show only MRs visible to user on milestone detail --- spec/models/concerns/milestoneish_spec.rb | 47 ++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 87bf731340f..4647eecbdef 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -48,7 +48,7 @@ describe Milestone, 'Milestoneish' do merge_request_2 = create(:labeled_merge_request, labels: [label_1], source_project: project, source_branch: 'branch_2', milestone: milestone) merge_request_3 = create(:labeled_merge_request, labels: [label_3], source_project: project, source_branch: 'branch_3', milestone: milestone) - merge_requests = milestone.sorted_merge_requests + merge_requests = milestone.sorted_merge_requests(member) expect(merge_requests.first).to eq(merge_request_2) expect(merge_requests.second).to eq(merge_request_1) @@ -56,6 +56,51 @@ describe Milestone, 'Milestoneish' do end end + describe '#merge_requests_visible_to_user' do + let(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) } + + context 'when project is private' do + before do + project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'does not return any merge request for a non member' do + merge_requests = milestone.merge_requests_visible_to_user(non_member) + expect(merge_requests).to be_empty + end + + it 'returns milestone merge requests for a member' do + merge_requests = milestone.merge_requests_visible_to_user(member) + expect(merge_requests).to contain_exactly(merge_request) + end + end + + context 'when project is public' do + context 'when merge requests are available to anyone' do + it 'returns milestone merge requests for a non member' do + merge_requests = milestone.merge_requests_visible_to_user(non_member) + expect(merge_requests).to contain_exactly(merge_request) + end + end + + context 'when merge requests are available to project members' do + before do + project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE) + end + + it 'does not return any merge request for a non member' do + merge_requests = milestone.merge_requests_visible_to_user(non_member) + expect(merge_requests).to be_empty + end + + it 'returns milestone merge requests for a member' do + merge_requests = milestone.merge_requests_visible_to_user(member) + expect(merge_requests).to contain_exactly(merge_request) + end + end + end + end + describe '#closed_items_count' do it 'does not count confidential issues for non project members' do expect(milestone.closed_items_count(non_member)).to eq 2 -- cgit v1.2.1 From d4a5d8d07069acb6f068990633baaf56d20bc18b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 14 Feb 2019 14:24:23 -0200 Subject: Add specs for issuable states sync --- spec/models/concerns/issuable_states_spec.rb | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spec/models/concerns/issuable_states_spec.rb (limited to 'spec') diff --git a/spec/models/concerns/issuable_states_spec.rb b/spec/models/concerns/issuable_states_spec.rb new file mode 100644 index 00000000000..70450159cc0 --- /dev/null +++ b/spec/models/concerns/issuable_states_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# This spec checks if state_id column of issues and merge requests +# are being synced on every save. +# It can be removed in the next release. Check https://gitlab.com/gitlab-org/gitlab-ce/issues/51789 for more information. +describe IssuableStates do + [Issue, MergeRequest].each do |klass| + it "syncs state_id column when #{klass.model_name.human} gets created" do + klass.available_states.each do |state, state_id| + issuable = build(klass.model_name.param_key, state: state.to_s) + + issuable.save! + + expect(issuable.state_id).to eq(state_id) + end + end + + it "syncs state_id column when #{klass.model_name.human} gets updated" do + klass.available_states.each do |state, state_id| + issuable = create(klass.model_name.param_key, state: state.to_s) + + issuable.update(state: state) + + expect(issuable.state_id).to eq(state_id) + end + end + end +end -- cgit v1.2.1 From bf99ce7bf8bf4bda75ca57f5db4d216f3585615a Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 15 Feb 2019 14:37:55 -0200 Subject: Fix specs --- spec/db/schema_spec.rb | 4 ++-- spec/lib/gitlab/import_export/safe_model_attributes.yml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 897b4411055..40c3a6d90d0 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -25,12 +25,12 @@ describe 'Database schema' do events: %w[target_id], forked_project_links: %w[forked_from_project_id], identities: %w[user_id], - issues: %w[last_edited_by_id], + issues: %w[last_edited_by_id state_id], keys: %w[user_id], label_links: %w[target_id], lfs_objects_projects: %w[lfs_object_id project_id], members: %w[source_id created_by_id], - merge_requests: %w[last_edited_by_id], + merge_requests: %w[last_edited_by_id state_id], namespaces: %w[owner_id parent_id], notes: %w[author_id commit_id noteable_id updated_by_id resolved_by_id discussion_id], notification_settings: %w[source_id], diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index baca8f6d542..9cfa29ca7d9 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -11,6 +11,7 @@ Issue: - branch_name - description - state +- state_id - iid - updated_by_id - confidential @@ -158,6 +159,7 @@ MergeRequest: - created_at - updated_at - state +- state_id - merge_status - target_project_id - iid -- cgit v1.2.1 From b1346db3a0b49b295a9702921285fdb30029563c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 15 Feb 2019 17:11:41 -0200 Subject: Add Reschedulable module --- spec/lib/gitlab/background_migration/delete_diff_files_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb index 27281333348..690881ab59b 100644 --- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb @@ -40,14 +40,14 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch end end - it 'reschedules itself when should_wait_deadtuple_vacuum' do + it 'reschedules itself when should wait for dead tuple vacuum' do merge_request = create(:merge_request, :merged) first_diff = merge_request.merge_request_diff second_diff = merge_request.create_merge_request_diff Sidekiq::Testing.fake! do worker = described_class.new - allow(worker).to receive(:should_wait_deadtuple_vacuum?) { true } + allow(worker).to receive(:wait_for_deadtuple_vacuum?) { true } worker.perform([first_diff.id, second_diff.id]) @@ -57,10 +57,10 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch end end - describe '#should_wait_deadtuple_vacuum?' do + describe '#wait_for_deadtuple_vacuum?' do it 'returns true when hitting merge_request_diff_files hits DEAD_TUPLES_THRESHOLD', :postgresql do worker = described_class.new - threshold_query_result = [{ "n_dead_tup" => described_class::DEAD_TUPLES_THRESHOLD.to_s }] + threshold_query_result = [{ "n_dead_tup" => 50_000 }] normal_query_result = [{ "n_dead_tup" => '3' }] allow(worker) @@ -68,7 +68,7 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch .with(/SELECT n_dead_tup */) .and_return(threshold_query_result, normal_query_result) - expect(worker.should_wait_deadtuple_vacuum?).to be(true) + expect(worker.wait_for_deadtuple_vacuum?('merge_request_diff_files')).to be(true) end end end -- cgit v1.2.1 From 618b87448e9167f39d8216d1100733cc0fbf020b Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Thu, 31 Jan 2019 17:21:35 +1300 Subject: Prevent leaking of private repo data through API default_branch, statistics and config_ci_path are now only exposed if the user has permissions to the repository. --- spec/requests/api/projects_spec.rb | 59 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cfa7a1a31a3..1c05be8de70 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -110,6 +110,7 @@ describe API::Projects do end let!(:public_project) { create(:project, :public, name: 'public_project') } + before do project project2 @@ -942,8 +943,16 @@ describe API::Projects do describe 'GET /projects/:id' do context 'when unauthenticated' do - it 'returns the public projects' do - public_project = create(:project, :public) + it 'does not return private projects' do + private_project = create(:project, :private) + + get api("/projects/#{private_project.id}") + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns public projects' do + public_project = create(:project, :repository, :public) get api("/projects/#{public_project.id}") @@ -951,8 +960,34 @@ describe API::Projects do expect(json_response['id']).to eq(public_project.id) expect(json_response['description']).to eq(public_project.description) expect(json_response['default_branch']).to eq(public_project.default_branch) + expect(json_response['ci_config_path']).to eq(public_project.ci_config_path) expect(json_response.keys).not_to include('permissions') end + + context 'and the project has a private repository' do + let(:project) { create(:project, :repository, :public, :repository_private) } + let(:protected_attributes) { %w(default_branch ci_config_path) } + + it 'hides protected attributes of private repositories if user is not a member' do + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(200) + protected_attributes.each do |attribute| + expect(json_response.keys).not_to include(attribute) + end + end + + it 'exposes protected attributes of private repositories if user is a member' do + project.add_developer(user) + + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(200) + protected_attributes.each do |attribute| + expect(json_response.keys).to include(attribute) + end + end + end end context 'when authenticated' do @@ -1104,6 +1139,26 @@ describe API::Projects do expect(json_response).to include 'statistics' end + context "and the project has a private repository" do + let(:project) { create(:project, :public, :repository, :repository_private) } + + it "does not include statistics if user is not a member" do + get api("/projects/#{project.id}", user), params: { statistics: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).not_to include 'statistics' + end + + it "includes statistics if user is a member" do + project.add_developer(user) + + get api("/projects/#{project.id}", user), params: { statistics: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to include 'statistics' + end + end + it "includes import_error if user can admin project" do get api("/projects/#{project.id}", user) -- cgit v1.2.1 From 04daa0b9701cd5b53c3c1fd6529aba05e4189114 Mon Sep 17 00:00:00 2001 From: Yauhen Kotau Date: Mon, 18 Feb 2019 20:08:36 +0300 Subject: Added YouTrack integration Fixes gitlab-org/gitlab-ce#42595 --- spec/factories/projects.rb | 15 ++++++++ .../filter/external_issue_reference_filter_spec.rb | 22 ++++++++++++ spec/lib/gitlab/import_export/all_models.yml | 1 + spec/lib/gitlab/import_export/project.json | 20 +++++++++++ .../project_services/youtrack_service_spec.rb | 41 ++++++++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 spec/models/project_services/youtrack_service_spec.rb (limited to 'spec') diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index f7ef34d773b..0efb15337ed 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -313,6 +313,21 @@ FactoryBot.define do end end + factory :youtrack_project, parent: :project do + has_external_issue_tracker true + + after :create do |project| + project.create_youtrack_service( + active: true, + properties: { + 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack', + 'issues_url' => 'http://youtrack/issues/:id', + 'new_issue_url' => 'http://youtrack/newIssue' + } + ) + end + end + factory :jira_project, parent: :project do has_external_issue_tracker true jira_service diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index a0270d93d50..e415bbcfc92 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -121,6 +121,28 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do end end + context "youtrack project" do + let(:project) { create(:youtrack_project) } + + before do + project.update!(issues_enabled: false) + end + + context "with right markdown" do + let(:issue) { ExternalIssue.new("YT-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with a single-letter prefix" do + let(:issue) { ExternalIssue.new("T-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + end + context "jira project" do let(:project) { create(:jira_project) } let(:reference) { issue.to_reference } diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index c15b360b563..46d78b92b21 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -232,6 +232,7 @@ project: - pushover_service - jira_service - redmine_service +- youtrack_service - custom_issue_tracker_service - bugzilla_service - gitlab_issue_tracker_service diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 1327f414498..773651dd226 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6629,6 +6629,26 @@ ], "deploy_keys": [], "services": [ + { + "id": 101, + "title": "YouTrack", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.327Z", + "updated_at": "2016-06-14T15:01:51.327Z", + "active": false, + "properties": {}, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "job_events": true, + "type": "YoutrackService", + "category": "issue_tracker", + "default": false, + "wiki_page_events": true + }, { "id": 100, "title": "JetBrains TeamCity CI", diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb new file mode 100644 index 00000000000..26591b09d0d --- /dev/null +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe YoutrackService do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end + + describe '.reference_pattern' do + it_behaves_like 'allows project key on reference pattern' + + it 'does allow project prefix on the reference' do + expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YK-123') + end + end +end -- cgit v1.2.1 From fdfa6f1e4d03c295111a3ca95b05b0b189af20c3 Mon Sep 17 00:00:00 2001 From: Yauhen Kotau Date: Mon, 18 Feb 2019 20:50:48 +0300 Subject: Fix failed test --- spec/models/project_services/youtrack_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb index 26591b09d0d..b1e74a6f877 100644 --- a/spec/models/project_services/youtrack_service_spec.rb +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -35,7 +35,7 @@ describe YoutrackService do it_behaves_like 'allows project key on reference pattern' it 'does allow project prefix on the reference' do - expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YK-123') + expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123') end end end -- cgit v1.2.1 From cc7a44c8e130a8f3f753ba0ed32e45b0a6b0e6f7 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 18 Feb 2019 17:43:16 -0300 Subject: Make migrations reschedulable --- .../schedule_sync_issuables_state_id_spec.rb | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'spec') diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb index a926ee38387..a746fb68c30 100644 --- a/spec/migrations/schedule_sync_issuables_state_id_spec.rb +++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb @@ -15,7 +15,40 @@ describe ScheduleSyncIssuablesStateId, :migration do @project = projects.create!(namespace_id: @group.id) end + shared_examples 'rescheduling migrations' do + before do + Sidekiq::Worker.clear_all + end + + it 'reschedules migrations when should wait for dead tuple vacuum' do + worker = worker_class.new + + Sidekiq::Testing.fake! do + allow(worker).to receive(:wait_for_deadtuple_vacuum?) { true } + + worker.perform(resource_1.id, resource_2.id) + + expect(worker_class.name.demodulize).to be_scheduled_delayed_migration(5.minutes, resource_1.id, resource_2.id) + expect(BackgroundMigrationWorker.jobs.size).to eq(1) + end + end + end + describe '#up' do + # it 'correctly schedules diff file deletion workers' do + # Sidekiq::Testing.fake! do + # Timecop.freeze do + # described_class.new.perform + + # expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, [1, 4, 5]) + + # expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, [6]) + + # expect(BackgroundMigrationWorker.jobs.size).to eq(2) + # end + # end + # end + context 'issues' do it 'migrates state column to integer' do opened_issue = issues.create!(description: 'first', state: 'opened') @@ -30,6 +63,12 @@ describe ScheduleSyncIssuablesStateId, :migration do expect(invalid_state_issue.reload.state_id).to be_nil expect(nil_state_issue.reload.state_id).to be_nil end + + it_behaves_like 'rescheduling migrations' do + let(:worker_class) { Gitlab::BackgroundMigration::SyncIssuesStateId } + let(:resource_1) { issues.create!(description: 'first', state: 'opened') } + let(:resource_2) { issues.create!(description: 'second', state: 'closed') } + end end context 'merge requests' do @@ -48,6 +87,12 @@ describe ScheduleSyncIssuablesStateId, :migration do expect(locked_merge_request.reload.state_id).to eq(MergeRequest.available_states[:locked]) expect(invalid_state_merge_request.reload.state_id).to be_nil end + + it_behaves_like 'rescheduling migrations' do + let(:worker_class) { Gitlab::BackgroundMigration::SyncMergeRequestsStateId } + let(:resource_1) { merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') } + let(:resource_2) { merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') } + end end end end -- cgit v1.2.1 From 9d046c8704c0e7df18d2f9e380e987d22b9a0b2e Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 18 Feb 2019 11:51:56 +0800 Subject: Fix git clone revealing private repo's presence Ensure redirection to path with .git suffix regardless whether project exists or not. --- .../constraints/project_url_constrainer_spec.rb | 4 + spec/requests/git_http_spec.rb | 134 +++++++++++---------- 2 files changed, 74 insertions(+), 64 deletions(-) (limited to 'spec') diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index c96e7ab8495..3496b01ebcc 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -16,6 +16,10 @@ describe Constraints::ProjectUrlConstrainer do let(:request) { build_request('foo', 'bar') } it { expect(subject.matches?(request)).to be_falsey } + + context 'existence_check is false' do + it { expect(subject.matches?(request, existence_check: false)).to be_truthy } + end end context "project id ending with .git" do diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5b625fd47be..bfa178f5cae 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -104,6 +104,70 @@ describe 'Git HTTP requests' do end end + shared_examples_for 'project path without .git suffix' do + context "GET info/refs" do + let(:path) { "/#{project_path}/info/refs" } + + context "when no params are added" do + before do + get path + end + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project_path}.git/info/refs") + end + end + + context "when the upload-pack service is requested" do + let(:params) { { service: 'git-upload-pack' } } + + before do + get path, params: params + end + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project_path}.git/info/refs?service=#{params[:service]}") + end + end + + context "when the receive-pack service is requested" do + let(:params) { { service: 'git-receive-pack' } } + + before do + get path, params: params + end + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project_path}.git/info/refs?service=#{params[:service]}") + end + end + + context "when the params are anything else" do + let(:params) { { service: 'git-implode-pack' } } + + before do + get path, params: params + end + + it "redirects to the sign-in page" do + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context "POST git-upload-pack" do + it "fails to find a route" do + expect { clone_post(project_path) }.to raise_error(ActionController::RoutingError) + end + end + + context "POST git-receive-pack" do + it "fails to find a route" do + expect { push_post(project_path) }.to raise_error(ActionController::RoutingError) + end + end + end + describe "User with no identities" do let(:user) { create(:user) } @@ -143,6 +207,10 @@ describe 'Git HTTP requests' do expect(response).to have_gitlab_http_status(:unprocessable_entity) end end + + it_behaves_like 'project path without .git suffix' do + let(:project_path) { "#{user.namespace.path}/project.git-project" } + end end end @@ -706,70 +774,8 @@ describe 'Git HTTP requests' do end end - context "when the project path doesn't end in .git" do - let(:project) { create(:project, :repository, :public, path: 'project.git-project') } - - context "GET info/refs" do - let(:path) { "/#{project.full_path}/info/refs" } - - context "when no params are added" do - before do - get path - end - - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.full_path}.git/info/refs") - end - end - - context "when the upload-pack service is requested" do - let(:params) { { service: 'git-upload-pack' } } - - before do - get path, params: params - end - - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}") - end - end - - context "when the receive-pack service is requested" do - let(:params) { { service: 'git-receive-pack' } } - - before do - get path, params: params - end - - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}") - end - end - - context "when the params are anything else" do - let(:params) { { service: 'git-implode-pack' } } - - before do - get path, params: params - end - - it "redirects to the sign-in page" do - expect(response).to redirect_to(new_user_session_path) - end - end - end - - context "POST git-upload-pack" do - it "fails to find a route" do - expect { clone_post(project.full_path) }.to raise_error(ActionController::RoutingError) - end - end - - context "POST git-receive-pack" do - it "fails to find a route" do - expect { push_post(project.full_path) }.to raise_error(ActionController::RoutingError) - end - end + it_behaves_like 'project path without .git suffix' do + let(:project_path) { create(:project, :repository, :public, path: 'project.git-project').full_path } end context "retrieving an info/refs file" do -- cgit v1.2.1 From fc8c1a77d36003795586fe076243b6eb90db6f03 Mon Sep 17 00:00:00 2001 From: Tiger Date: Wed, 13 Feb 2019 11:11:28 +1100 Subject: Validate session key when authorizing with GCP to create a cluster It was previously possible to link a GCP account to another user's GitLab account by having them visit the callback URL, as there was no check that they were the initiator of the request. We now reject the callback unless the state parameter matches the one added to the initiating user's session. --- .../google_api/authorizations_controller_spec.rb | 60 +++++++++++++++------- 1 file changed, 41 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/controllers/google_api/authorizations_controller_spec.rb b/spec/controllers/google_api/authorizations_controller_spec.rb index 1e8e82da4f3..d9ba85cf56a 100644 --- a/spec/controllers/google_api/authorizations_controller_spec.rb +++ b/spec/controllers/google_api/authorizations_controller_spec.rb @@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do let(:token) { 'token' } let(:expires_at) { 1.hour.since.strftime('%s') } - subject { get :callback, params: { code: 'xxx', state: @state } } + subject { get :callback, params: { code: 'xxx', state: state } } before do sign_in(user) @@ -15,35 +15,57 @@ describe GoogleApi::AuthorizationsController do .to receive(:get_token).and_return([token, expires_at]) end - it 'sets token and expires_at in session' do - subject + shared_examples_for 'access denied' do + it 'returns a 404' do + subject - expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]) - .to eq(token) - expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]) - .to eq(expires_at) + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil + expect(response).to have_http_status(:not_found) + end end - context 'when redirect uri key is stored in state' do - set(:project) { create(:project) } - let(:redirect_uri) { project_clusters_url(project).to_s } + context 'session key is present' do + let(:session_key) { 'session-key' } + let(:redirect_uri) { 'example.com' } before do - @state = GoogleApi::CloudPlatform::Client - .new_session_key_for_redirect_uri do |key| - session[key] = redirect_uri + session[GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(session_key)] = redirect_uri + end + + context 'session key matches state param' do + let(:state) { session_key } + + it 'sets token and expires_at in session' do + subject + + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]) + .to eq(token) + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]) + .to eq(expires_at) + end + + it 'redirects to the URL stored in state param' do + expect(subject).to redirect_to(redirect_uri) end end - it 'redirects to the URL stored in state param' do - expect(subject).to redirect_to(redirect_uri) + context 'session key does not match state param' do + let(:state) { 'bad-key' } + + it_behaves_like 'access denied' end - end - context 'when redirection url is not stored in state' do - it 'redirects to root_path' do - expect(subject).to redirect_to(root_path) + context 'state param is blank' do + let(:state) { '' } + + it_behaves_like 'access denied' end end + + context 'state param is present, but session key is blank' do + let(:state) { 'session-key' } + + it_behaves_like 'access denied' + end end end -- cgit v1.2.1 From 52155d8cf8374e9184c2ae834cab761b7520db93 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 19 Feb 2019 11:33:30 -0300 Subject: Add more specs and code improvements --- .../schedule_sync_issuables_state_id_spec.rb | 53 +++++++++++++++------- 1 file changed, 37 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb index a746fb68c30..6c4c31ae554 100644 --- a/spec/migrations/schedule_sync_issuables_state_id_spec.rb +++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb @@ -15,6 +15,25 @@ describe ScheduleSyncIssuablesStateId, :migration do @project = projects.create!(namespace_id: @group.id) end + shared_examples 'scheduling migrations' do + before do + Sidekiq::Worker.clear_all + stub_const("#{described_class.name}::BATCH_SIZE", 2) + end + + it 'correctly schedules issuable sync background migration' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(migration).to be_scheduled_delayed_migration(5.minutes, resource_1.id, resource_2.id) + expect(migration).to be_scheduled_delayed_migration(10.minutes, resource_3.id, resource_4.id) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end + end + shared_examples 'rescheduling migrations' do before do Sidekiq::Worker.clear_all @@ -35,20 +54,6 @@ describe ScheduleSyncIssuablesStateId, :migration do end describe '#up' do - # it 'correctly schedules diff file deletion workers' do - # Sidekiq::Testing.fake! do - # Timecop.freeze do - # described_class.new.perform - - # expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, [1, 4, 5]) - - # expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, [6]) - - # expect(BackgroundMigrationWorker.jobs.size).to eq(2) - # end - # end - # end - context 'issues' do it 'migrates state column to integer' do opened_issue = issues.create!(description: 'first', state: 'opened') @@ -64,10 +69,18 @@ describe ScheduleSyncIssuablesStateId, :migration do expect(nil_state_issue.reload.state_id).to be_nil end + it_behaves_like 'scheduling migrations' do + let(:migration) { described_class::ISSUES_MIGRATION } + let!(:resource_1) { issues.create!(description: 'first', state: 'opened') } + let!(:resource_2) { issues.create!(description: 'second', state: 'closed') } + let!(:resource_3) { issues.create!(description: 'third', state: 'closed') } + let!(:resource_4) { issues.create!(description: 'fourth', state: 'closed') } + end + it_behaves_like 'rescheduling migrations' do let(:worker_class) { Gitlab::BackgroundMigration::SyncIssuesStateId } - let(:resource_1) { issues.create!(description: 'first', state: 'opened') } - let(:resource_2) { issues.create!(description: 'second', state: 'closed') } + let!(:resource_1) { issues.create!(description: 'first', state: 'opened') } + let!(:resource_2) { issues.create!(description: 'second', state: 'closed') } end end @@ -88,6 +101,14 @@ describe ScheduleSyncIssuablesStateId, :migration do expect(invalid_state_merge_request.reload.state_id).to be_nil end + it_behaves_like 'scheduling migrations' do + let(:migration) { described_class::MERGE_REQUESTS_MIGRATION } + let!(:resource_1) { merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') } + let!(:resource_2) { merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') } + let!(:resource_3) { merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') } + let!(:resource_4) { merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') } + end + it_behaves_like 'rescheduling migrations' do let(:worker_class) { Gitlab::BackgroundMigration::SyncMergeRequestsStateId } let(:resource_1) { merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') } -- cgit v1.2.1 From e5d491d15bd1f7a0679ca18a7bd36459392ae629 Mon Sep 17 00:00:00 2001 From: Paul B Date: Mon, 31 Dec 2018 14:13:07 +0100 Subject: tests: adding a failing unit test --- spec/lib/gitlab/ci/yaml_processor_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 91139d421f5..8d9ae2ff22a 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -602,6 +602,15 @@ module Gitlab end end + describe "Include" do + it "does not return any error with a valid configuration" do + config = YAML.dump({ include: "/local.gitlab-ci.yml" }) + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.not_to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) + end + end + describe "When" do %w(on_success on_failure always).each do |when_state| it "returns #{when_state} when defined" do -- cgit v1.2.1 From d3aa9feffd725f8fd7e1826c56c0e26d6708ac38 Mon Sep 17 00:00:00 2001 From: Paul B Date: Wed, 2 Jan 2019 10:57:31 +0100 Subject: tests(yaml_processor): complete test scenarios on 'include' keyword --- spec/lib/gitlab/ci/config/entry/global_spec.rb | 6 +- spec/lib/gitlab/ci/yaml_processor_spec.rb | 80 ++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 7651f594a4c..e23efff18d5 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::Ci::Config::Entry::Global do expect(described_class.nodes.keys) .to match_array(%i[before_script image services after_script variables stages - types cache]) + types cache include]) end end end @@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::Entry::Global do end it 'creates node object for each entry' do - expect(global.descendants.count).to eq 8 + expect(global.descendants.count).to eq 9 end it 'creates node object using valid class' do @@ -189,7 +189,7 @@ describe Gitlab::Ci::Config::Entry::Global do describe '#nodes' do it 'instantizes all nodes' do - expect(global.descendants.count).to eq 8 + expect(global.descendants.count).to eq 9 end it 'contains unspecified nodes' do diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 8d9ae2ff22a..29638ef47c5 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -603,11 +603,81 @@ module Gitlab end describe "Include" do - it "does not return any error with a valid configuration" do - config = YAML.dump({ include: "/local.gitlab-ci.yml" }) - expect do - Gitlab::Ci::YamlProcessor.new(config) - end.not_to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) + let(:opts) { {} } + + let(:config) do + { + include: include_content, + rspec: { script: "test" } + } + end + + subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) } + + context "when validating a ci config file with no project context" do + context "when an array is provided" do + let(:include_content) { ["/local.gitlab-ci.yml"] } + + it "does not return any error" do + expect { subject }.not_to raise_error + end + end + + context "when an array of wrong keyed object is provided" do + let(:include_content) { [{ yolo: "/local.gitlab-ci.yml" }] } + + it "returns a validation error" do + expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) + end + end + + context "when an array of mixed typed objects is provided" do + let(:include_content) do + [ + 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml', + '/templates/.after-script-template.yml', + { template: 'Auto-DevOps.gitlab-ci.yml' } + ] + end + + it "does not return any error" do + expect { subject }.not_to raise_error + end + end + + context "when the include type is incorrect" do + let(:include_content) { { name: "/local.gitlab-ci.yml" } } + + it "returns an invalid configuration error" do + expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError) + end + end + end + + context "when validating a ci config file within a project" do + let(:include_content) { "/local.gitlab-ci.yml" } + let(:project) { create(:project, :repository) } + let(:opts) { { project: project, sha: project.commit.sha } } + + context "when the included internal file is present" do + before do + expect(project.repository).to receive(:blob_data_at) + .and_return(YAML.dump({ job1: { script: 'hello' } })) + end + + it "does not return an error" do + expect { subject }.not_to raise_error + end + end + + context "when the included internal file is not present" do + it "returns an error with missing file details" do + expect { subject }.to raise_error( + Gitlab::Ci::YamlProcessor::ValidationError, + "Local file `#{include_content}` does not exist!" + ) + end + end end end -- cgit v1.2.1 From 7bd066a1fa51018211e26ca0c5624aecbc364a66 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 19 Feb 2019 14:00:53 -0300 Subject: Address review comments --- spec/lib/gitlab/background_migration/delete_diff_files_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb index 690881ab59b..27281333348 100644 --- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb @@ -40,14 +40,14 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch end end - it 'reschedules itself when should wait for dead tuple vacuum' do + it 'reschedules itself when should_wait_deadtuple_vacuum' do merge_request = create(:merge_request, :merged) first_diff = merge_request.merge_request_diff second_diff = merge_request.create_merge_request_diff Sidekiq::Testing.fake! do worker = described_class.new - allow(worker).to receive(:wait_for_deadtuple_vacuum?) { true } + allow(worker).to receive(:should_wait_deadtuple_vacuum?) { true } worker.perform([first_diff.id, second_diff.id]) @@ -57,10 +57,10 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch end end - describe '#wait_for_deadtuple_vacuum?' do + describe '#should_wait_deadtuple_vacuum?' do it 'returns true when hitting merge_request_diff_files hits DEAD_TUPLES_THRESHOLD', :postgresql do worker = described_class.new - threshold_query_result = [{ "n_dead_tup" => 50_000 }] + threshold_query_result = [{ "n_dead_tup" => described_class::DEAD_TUPLES_THRESHOLD.to_s }] normal_query_result = [{ "n_dead_tup" => '3' }] allow(worker) @@ -68,7 +68,7 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch .with(/SELECT n_dead_tup */) .and_return(threshold_query_result, normal_query_result) - expect(worker.wait_for_deadtuple_vacuum?('merge_request_diff_files')).to be(true) + expect(worker.should_wait_deadtuple_vacuum?).to be(true) end end end -- cgit v1.2.1 From 54a5d513e5f068c53fad3b2dac04998f5e9afd88 Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Tue, 19 Feb 2019 13:27:53 -0700 Subject: Fixing CI icon mismatch MR list, and related MRs and branches were using a deprecated helper. Created a new icon haml file to help move these forward. --- spec/views/ci/status/_icon.html.haml_spec.rb | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 spec/views/ci/status/_icon.html.haml_spec.rb (limited to 'spec') diff --git a/spec/views/ci/status/_icon.html.haml_spec.rb b/spec/views/ci/status/_icon.html.haml_spec.rb new file mode 100644 index 00000000000..43806446164 --- /dev/null +++ b/spec/views/ci/status/_icon.html.haml_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe 'ci/status/_icon' do + let(:user) { create(:user) } + let(:project) { create(:project, :private) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when rendering status for build' do + let(:build) do + create(:ci_build, :success, pipeline: pipeline) + end + + context 'when user has ability to see details' do + before do + project.add_developer(user) + end + + it 'has link to build details page' do + details_path = project_job_path(project, build) + + render_status(build) + + expect(rendered).to have_link(href: details_path) + end + end + + context 'when user do not have ability to see build details' do + before do + render_status(build) + end + + it 'contains build status text' do + expect(rendered).to have_css('.ci-status-icon.ci-status-icon-success') + end + + it 'does not contain links' do + expect(rendered).not_to have_link + end + end + end + + context 'when rendering status for external job' do + context 'when user has ability to see commit status details' do + before do + project.add_developer(user) + end + + context 'status has external target url' do + before do + external_job = create(:generic_commit_status, + status: :running, + pipeline: pipeline, + target_url: 'http://gitlab.com') + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_css('.ci-status-icon.ci-status-icon-running') + end + + it 'has link to external status page' do + expect(rendered).to have_link(href: 'http://gitlab.com') + end + end + + context 'status do not have external target url' do + before do + external_job = create(:generic_commit_status, status: :canceled) + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_css('.ci-status-icon.ci-status-icon-canceled') + end + + it 'has link to external status page' do + expect(rendered).not_to have_link + end + end + end + end + + def render_status(resource) + render 'ci/status/icon', status: resource.detailed_status(user) + end +end -- cgit v1.2.1 From 2e182f337ed2cdfcca26dad1e7a482bc81a2000e Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Thu, 14 Feb 2019 01:20:52 +0900 Subject: Fix alignment of dropdown icon on issuable on mobile Signed-off-by: Takuya Noguchi --- .../issues/filtered_search/visual_tokens_spec.rb | 21 --------------------- 1 file changed, 21 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index a4c34ce85f0..9fd661d80ae 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -59,13 +59,6 @@ describe 'Visual tokens', :js do expect(page).to have_css('#js-dropdown-author', visible: false) end - it 'ends editing mode when scroll container is clicked' do - find('.scroll-container').click - - expect_filtered_search_input_empty - expect(page).to have_css('#js-dropdown-author', visible: false) - end - describe 'selecting different author from dropdown' do before do filter_author_dropdown.find('.filter-dropdown-item .dropdown-light-content', text: "@#{user_rock.username}").click @@ -109,13 +102,6 @@ describe 'Visual tokens', :js do expect(page).to have_css('#js-dropdown-assignee', visible: false) end - it 'ends editing mode when scroll container is clicked' do - find('.scroll-container').click - - expect_filtered_search_input_empty - expect(page).to have_css('#js-dropdown-assignee', visible: false) - end - describe 'selecting static option from dropdown' do before do find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click @@ -167,13 +153,6 @@ describe 'Visual tokens', :js do expect_filtered_search_input_empty expect(page).to have_css('#js-dropdown-milestone', visible: false) end - - it 'ends editing mode when scroll container is clicked' do - find('.scroll-container').click - - expect_filtered_search_input_empty - expect(page).to have_css('#js-dropdown-milestone', visible: false) - end end describe 'editing label token' do -- cgit v1.2.1 From 38f3c2e18684f94f75ab52e5039203e8321ed7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Wed, 13 Feb 2019 11:50:24 +0100 Subject: Remove link after issue move when no permissions Don't show new issue link after move when a user does not have permissions to display the new issue --- spec/mailers/notify_spec.rb | 56 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 4f578c48d5b..418f707a130 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -194,23 +194,53 @@ describe Notify do let(:new_issue) { create(:issue) } subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } - end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' + context 'when a user has permissions to access the new issue' do + before do + new_issue.project.add_developer(recipient) + end + + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' + + it 'contains description about action taken' do + is_expected.to have_body_text 'Issue was moved to another project' + end + + it 'has the correct subject and body' do + new_issue_url = project_issue_path(new_issue.project, new_issue) - it 'contains description about action taken' do - is_expected.to have_body_text 'Issue was moved to another project' + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(new_issue_url) + is_expected.to have_body_text(project_issue_path(project, issue)) + end + end + + it 'contains the issue title' do + is_expected.to have_body_text new_issue.title + end end - it 'has the correct subject and body' do - new_issue_url = project_issue_path(new_issue.project, new_issue) + context 'when a user does not permissions to access the new issue' do + it 'has the correct subject and body' do + new_issue_url = project_issue_path(new_issue.project, new_issue) - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text(new_issue_url) - is_expected.to have_body_text(project_issue_path(project, issue)) + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.not_to have_body_text(new_issue_url) + is_expected.to have_body_text(project_issue_path(project, issue)) + end + end + + it 'does not contain the issue title' do + is_expected.not_to have_body_text new_issue.title + end + + it 'contains information about missing permissions' do + is_expected.to have_body_text "You don't have access to the project." end end end -- cgit v1.2.1 From 211c4e5985bf40afe7cf2391c76a6cfde153fb49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Tue, 12 Feb 2019 13:29:47 +0100 Subject: Change policy regarding group visibility --- .../groups/shared_projects_controller_spec.rb | 2 ++ .../projects/group_links_controller_spec.rb | 37 ++++++++++++++++++++++ .../features/projects/members/invite_group_spec.rb | 2 ++ .../settings/user_manages_group_links_spec.rb | 1 + spec/requests/api/projects_spec.rb | 12 +++++++ .../projects/group_links/create_service_spec.rb | 8 +++++ 6 files changed, 62 insertions(+) (limited to 'spec') diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb index dab7700cf64..b0c20fb5a90 100644 --- a/spec/controllers/groups/shared_projects_controller_spec.rb +++ b/spec/controllers/groups/shared_projects_controller_spec.rb @@ -6,6 +6,8 @@ describe Groups::SharedProjectsController do end def share_project(project) + group.add_developer(user) + Projects::GroupLinks::CreateService.new( project, user, diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 675eeff8d12..ce021b2f085 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -65,8 +65,24 @@ describe Projects::GroupLinksController do end end + context 'when user does not have access to the public group' do + let(:group) { create(:group, :public) } + + include_context 'link project to group' + + it 'renders 404' do + expect(response.status).to eq 404 + end + + it 'does not share project with that group' do + expect(group.shared_projects).not_to include project + end + end + context 'when project group id equal link group id' do before do + group2.add_developer(user) + post(:create, params: { namespace_id: project.namespace, project_id: project, @@ -102,5 +118,26 @@ describe Projects::GroupLinksController do expect(flash[:alert]).to eq('Please select a group.') end end + + context 'when link is not persisted in the database' do + before do + allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute) + .and_return({ status: :error, http_status: 409, message: 'error' }) + + post(:create, params: { + namespace_id: project.namespace, + project_id: project, + link_group_id: group.id, + link_group_access: ProjectGroupLink.default_access + }) + end + + it 'redirects to project group links page' do + expect(response).to redirect_to( + project_project_members_path(project) + ) + expect(flash[:alert]).to eq('error') + end + end end end diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb index fceead0b45e..b2d2dba55f1 100644 --- a/spec/features/projects/members/invite_group_spec.rb +++ b/spec/features/projects/members/invite_group_spec.rb @@ -27,6 +27,7 @@ describe 'Project > Members > Invite group', :js do before do project.add_maintainer(maintainer) + group_to_share_with.add_guest(maintainer) sign_in(maintainer) end @@ -112,6 +113,7 @@ describe 'Project > Members > Invite group', :js do before do project.add_maintainer(maintainer) + group.add_guest(maintainer) sign_in(maintainer) visit project_settings_members_path(project) diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb index 676659b90c3..e5a58c44e41 100644 --- a/spec/features/projects/settings/user_manages_group_links_spec.rb +++ b/spec/features/projects/settings/user_manages_group_links_spec.rb @@ -10,6 +10,7 @@ describe 'Projects > Settings > User manages group links' do before do project.add_maintainer(user) + group_market.add_guest(user) sign_in(user) share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cfa7a1a31a3..feff53ebc25 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1484,6 +1484,9 @@ describe API::Projects do describe "POST /projects/:id/share" do let(:group) { create(:group) } + before do + group.add_developer(user) + end it "shares project with group" do expires_at = 10.days.from_now.to_date @@ -1534,6 +1537,15 @@ describe API::Projects do expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq 'group_access does not have a valid value' end + + it "returns a 409 error when link is not saved" do + allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute) + .and_return({ status: :error, http_status: 409, message: 'error' }) + + post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER } + + expect(response).to have_gitlab_http_status(409) + end end describe 'DELETE /projects/:id/share/:group_id' do diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb index ffb270d277e..68fd82b4cbe 100644 --- a/spec/services/projects/group_links/create_service_spec.rb +++ b/spec/services/projects/group_links/create_service_spec.rb @@ -12,6 +12,10 @@ describe Projects::GroupLinks::CreateService, '#execute' do end let(:subject) { described_class.new(project, user, opts) } + before do + group.add_developer(user) + end + it 'adds group to project' do expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1) end @@ -19,4 +23,8 @@ describe Projects::GroupLinks::CreateService, '#execute' do it 'returns false if group is blank' do expect { subject.execute(nil) }.not_to change { project.project_group_links.count } end + + it 'returns error if user is not allowed to share with a group' do + expect { subject.execute(create :group) }.not_to change { project.project_group_links.count } + end end -- cgit v1.2.1 From 5011d83cb14b5ffae0060b3e7309a24eca80a1b2 Mon Sep 17 00:00:00 2001 From: Yauhen Kotau Date: Wed, 20 Feb 2019 19:00:55 +0300 Subject: Lowercase letters support and additional tests for YouTrack integration service --- .../projects/services/user_activates_issue_tracker_spec.rb | 1 + .../banzai/filter/external_issue_reference_filter_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb index 7cd5b12802b..4a71eea1add 100644 --- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb @@ -87,6 +87,7 @@ describe 'User activates issue tracker', :js do end it_behaves_like 'external issue tracker activation', tracker: 'Redmine' + it_behaves_like 'external issue tracker activation', tracker: 'YouTrack' it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla' it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker' end diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index e415bbcfc92..43222ddb5e2 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -135,6 +135,20 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do it_behaves_like "external issue tracker" end + context "with underscores in the prefix" do + let(:issue) { ExternalIssue.new("PRJ_1-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with lowercase letters in the prefix" do + let(:issue) { ExternalIssue.new("YTkPrj-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + context "with a single-letter prefix" do let(:issue) { ExternalIssue.new("T-123", project) } let(:reference) { issue.to_reference } -- cgit v1.2.1 From dd6fbbc44d2807b93d609d304db2c4da229297dc Mon Sep 17 00:00:00 2001 From: Yauhen Kotau Date: Wed, 20 Feb 2019 20:54:47 +0300 Subject: API documentation, changelog and additional tests for YouTrack integration service --- spec/models/project_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1f9088c2e6b..75fb30554b3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -50,6 +50,7 @@ describe Project do it { is_expected.to have_one(:teamcity_service) } it { is_expected.to have_one(:jira_service) } it { is_expected.to have_one(:redmine_service) } + it { is_expected.to have_one(:youtrack_service) } it { is_expected.to have_one(:custom_issue_tracker_service) } it { is_expected.to have_one(:bugzilla_service) } it { is_expected.to have_one(:gitlab_issue_tracker_service) } -- cgit v1.2.1 From 52c910eeca47f140246d003fbb6b4748d1be8bb8 Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Wed, 20 Feb 2019 12:20:09 -0700 Subject: Remove deprecated ci status helper function After changing all places that used the function, we can now remove it. --- spec/views/ci/status/_icon.html.haml_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/views/ci/status/_icon.html.haml_spec.rb b/spec/views/ci/status/_icon.html.haml_spec.rb index 43806446164..626159fc512 100644 --- a/spec/views/ci/status/_icon.html.haml_spec.rb +++ b/spec/views/ci/status/_icon.html.haml_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'spec_helper' describe 'ci/status/_icon' do -- cgit v1.2.1 From d24ea868ab5a3644884cb02f6faa2b8daf48b1d2 Mon Sep 17 00:00:00 2001 From: Diana Stanley Date: Wed, 20 Feb 2019 15:09:27 -0800 Subject: Capture due date when importing milestones from Github --- spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb index b1cac3b6e46..471f499dd19 100644 --- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis let(:project) { create(:project, import_source: 'foo/bar') } let(:client) { double(:client) } let(:importer) { described_class.new(project, client) } + let(:due_on) { Time.new(2017, 2, 1, 12, 00) } let(:created_at) { Time.new(2017, 1, 1, 12, 00) } let(:updated_at) { Time.new(2017, 1, 1, 12, 15) } @@ -14,6 +15,7 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis title: '1.0', description: 'The first release', state: 'open', + due_on: due_on, created_at: created_at, updated_at: updated_at ) @@ -98,6 +100,10 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis expect(milestone_hash[:state]).to eq(:active) end + it 'includes the due date' do + expect(milestone_hash[:due_date]).to eq(due_on.to_date) + end + it 'includes the created timestamp' do expect(milestone_hash[:created_at]).to eq(created_at) end -- cgit v1.2.1 From d72b1cd0b5b01d6fec6b93d9dfe84f8302083072 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Wed, 13 Feb 2019 16:24:26 +0800 Subject: Check snippet attached file to be moved is within designated directory Previously one could move any temp/ sub folder around. --- spec/controllers/snippets_controller_spec.rb | 4 ++++ spec/support/helpers/file_mover_helpers.rb | 12 +++++++++++ spec/uploaders/file_mover_spec.rb | 30 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 spec/support/helpers/file_mover_helpers.rb (limited to 'spec') diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 5c6858dc7b2..77a94f26d8c 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -205,6 +205,8 @@ describe SnippetsController do end context 'when the snippet description contains a file' do + include FileMoverHelpers + let(:picture_file) { '/-/system/temp/secret56/picture.jpg' } let(:text_file) { '/-/system/temp/secret78/text.txt' } let(:description) do @@ -215,6 +217,8 @@ describe SnippetsController do before do allow(FileUtils).to receive(:mkdir_p) allow(FileUtils).to receive(:move) + stub_file_mover(text_file) + stub_file_mover(picture_file) end subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) } diff --git a/spec/support/helpers/file_mover_helpers.rb b/spec/support/helpers/file_mover_helpers.rb new file mode 100644 index 00000000000..1ba7cc03354 --- /dev/null +++ b/spec/support/helpers/file_mover_helpers.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module FileMoverHelpers + def stub_file_mover(file_path, stub_real_path: nil) + file_name = File.basename(file_path) + allow(Pathname).to receive(:new).and_call_original + + expect_next_instance_of(Pathname, a_string_including(file_name)) do |pathname| + allow(pathname).to receive(:realpath) { stub_real_path || pathname.cleanpath } + end + end +end diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb index a28d7445b1c..e474a714b10 100644 --- a/spec/uploaders/file_mover_spec.rb +++ b/spec/uploaders/file_mover_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe FileMover do + include FileMoverHelpers + let(:filename) { 'banana_sample.gif' } let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) } @@ -19,6 +21,8 @@ describe FileMover do expect(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path)) allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:exists?).and_return(true) allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:size).and_return(10) + + stub_file_mover(temp_file_path) end context 'when move and field update successful' do @@ -65,4 +69,30 @@ describe FileMover do end end end + + context 'security' do + context 'when relative path is involved' do + let(:temp_file_path) { File.join('uploads/-/system/temp', '..', 'another_subdir_of_temp') } + + it 'does not trigger move if path is outside designated directory' do + stub_file_mover('uploads/-/system/another_subdir_of_temp') + expect(FileUtils).not_to receive(:move) + + subject + + expect(snippet.reload.description).to eq(temp_description) + end + end + + context 'when symlink is involved' do + it 'does not trigger move if path is outside designated directory' do + stub_file_mover(temp_file_path, stub_real_path: Pathname('/etc')) + expect(FileUtils).not_to receive(:move) + + subject + + expect(snippet.reload.description).to eq(temp_description) + end + end + end end -- cgit v1.2.1 From af16fd687e2e5b15a63e6e51d76847512ae8ee72 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 13 Feb 2019 09:46:59 +1300 Subject: Do not allow local urls in Kubernetes form Use existing `public_url` validation to block various local urls. Note that this validation will allow local urls if the "Allow requests to the local network from hooks and services" admin setting is enabled. Block KubeClient from using local addresses It will also respect `allow_local_requests_from_hooks_and_services` so if that is enabled KubeClinet will allow local addresses --- spec/lib/gitlab/kubernetes/kube_client_spec.rb | 30 +++++++++++++++++++++++ spec/models/clusters/platforms/kubernetes_spec.rb | 16 ++++++++++++ 2 files changed, 46 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb index 02364e92149..978e64c4407 100644 --- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb +++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb @@ -50,6 +50,36 @@ describe Gitlab::Kubernetes::KubeClient do end end + describe '#initialize' do + shared_examples 'local address' do + it 'blocks local addresses' do + expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError) + end + + context 'when local requests are allowed' do + before do + stub_application_setting(allow_local_requests_from_hooks_and_services: true) + end + + it 'allows local addresses' do + expect { client }.not_to raise_error + end + end + end + + context 'localhost address' do + let(:api_url) { 'http://localhost:22' } + + it_behaves_like 'local address' + end + + context 'private network address' do + let(:api_url) { 'http://192.168.1.2:3003' } + + it_behaves_like 'local address' + end + end + describe '#core_client' do subject { client.core_client } diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 4068d98d8f7..3b32ca8df05 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -98,6 +98,22 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { expect(kubernetes.save).to be_truthy } end + + context 'when api_url is localhost' do + let(:api_url) { 'http://localhost:22' } + + it { expect(kubernetes.save).to be_falsey } + + context 'Application settings allows local requests' do + before do + allow(ApplicationSetting) + .to receive(:current) + .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true)) + end + + it { expect(kubernetes.save).to be_truthy } + end + end end context 'when validates token' do -- cgit v1.2.1 From d5e38b00cffacf2f0599c99a1bb1515b6f56aa9b Mon Sep 17 00:00:00 2001 From: Kotau Yauhen Date: Thu, 21 Feb 2019 10:39:44 +0000 Subject: Remove new_issue_url field from YouTrack integration service --- spec/factories/projects.rb | 3 +- .../services/user_activates_issue_tracker_spec.rb | 1 - .../services/user_activates_youtrack_spec.rb | 89 ++++++++++++++++++++++ .../project_services/youtrack_service_spec.rb | 3 - 4 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 spec/features/projects/services/user_activates_youtrack_spec.rb (limited to 'spec') diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 0efb15337ed..30d3b22d868 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -321,8 +321,7 @@ FactoryBot.define do active: true, properties: { 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack', - 'issues_url' => 'http://youtrack/issues/:id', - 'new_issue_url' => 'http://youtrack/newIssue' + 'issues_url' => 'http://youtrack/issues/:id' } ) end diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb index 4a71eea1add..7cd5b12802b 100644 --- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb @@ -87,7 +87,6 @@ describe 'User activates issue tracker', :js do end it_behaves_like 'external issue tracker activation', tracker: 'Redmine' - it_behaves_like 'external issue tracker activation', tracker: 'YouTrack' it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla' it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker' end diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb new file mode 100644 index 00000000000..bb6a030c1cf --- /dev/null +++ b/spec/features/projects/services/user_activates_youtrack_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe 'User activates issue tracker', :js do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:url) { 'http://tracker.example.com' } + + def fill_form(active = true) + check 'Active' if active + + fill_in 'service_project_url', with: url + fill_in 'service_issues_url', with: "#{url}/:id" + end + + before do + project.add_maintainer(user) + sign_in(user) + + visit project_settings_integrations_path(project) + end + + shared_examples 'external issue tracker activation' do |tracker:| + describe 'user sets and activates the Service' do + context 'when the connection test succeeds' do + before do + stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' }) + + click_link(tracker) + fill_form + click_button('Test settings and save changes') + wait_for_requests + end + + it 'activates the service' do + expect(page).to have_content("#{tracker} activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + + it 'shows the link in the menu' do + page.within('.nav-sidebar') do + expect(page).to have_link(tracker, href: url) + end + end + end + + context 'when the connection test fails' do + it 'activates the service' do + stub_request(:head, url).to_raise(HTTParty::Error) + + click_link(tracker) + fill_form + click_button('Test settings and save changes') + wait_for_requests + + expect(find('.flash-container-page')).to have_content 'Test failed.' + expect(find('.flash-container-page')).to have_content 'Save anyway' + + find('.flash-alert .flash-action').click + wait_for_requests + + expect(page).to have_content("#{tracker} activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + end + end + + describe 'user sets the service but keeps it disabled' do + before do + click_link(tracker) + fill_form(false) + click_button('Save changes') + end + + it 'saves but does not activate the service' do + expect(page).to have_content("#{tracker} settings saved, but not activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + + it 'does not show the external tracker link in the menu' do + page.within('.nav-sidebar') do + expect(page).not_to have_link(tracker, href: url) + end + end + end + end + + it_behaves_like 'external issue tracker activation', tracker: 'YouTrack' +end diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb index b1e74a6f877..9524b526a46 100644 --- a/spec/models/project_services/youtrack_service_spec.rb +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -14,10 +14,8 @@ describe YoutrackService do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } - it { is_expected.to validate_presence_of(:new_issue_url) } it_behaves_like 'issue tracker service URL attribute', :project_url it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url end context 'when service is inactive' do @@ -27,7 +25,6 @@ describe YoutrackService do it { is_expected.not_to validate_presence_of(:project_url) } it { is_expected.not_to validate_presence_of(:issues_url) } - it { is_expected.not_to validate_presence_of(:new_issue_url) } end end -- cgit v1.2.1 From 445709345f8a451b7882c81166063b8948688e0f Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 15 Feb 2019 17:11:16 -0200 Subject: Stop linking to unrecognized package sources --- spec/features/projects/blobs/blob_show_spec.rb | 5 +-- .../dependency_linker/composer_json_linker_spec.rb | 4 +-- .../dependency_linker/gemfile_linker_spec.rb | 9 +++-- .../dependency_linker/gemspec_linker_spec.rb | 4 +-- .../dependency_linker/package_json_linker_spec.rb | 18 +++++++--- .../dependency_linker/parser/gemfile_spec.rb | 42 ++++++++++++++++++++++ .../dependency_linker/podfile_linker_spec.rb | 5 ++- .../dependency_linker/podspec_linker_spec.rb | 4 +-- 8 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb (limited to 'spec') diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 3edcc7ac2cd..a7aa63018fd 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -548,10 +548,7 @@ describe 'File blob', :js do it 'displays an auxiliary viewer' do aggregate_failures do # shows names of dependency manager and package - expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.') - - # shows a link to the gem - expect(page).to have_link('activerecord', href: 'https://rubygems.org/gems/activerecord') + expect(page).to have_content('This project manages its dependencies using RubyGems.') # shows a learn more link expect(page).to have_link('Learn more', href: 'https://rubygems.org/') diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb index 4d222564fd0..0ebd8994636 100644 --- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb @@ -50,8 +50,8 @@ describe Gitlab::DependencyLinker::ComposerJsonLinker do %{#{name}} end - it 'links the module name' do - expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel')) + it 'does not link the module name' do + expect(subject).not_to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel')) end it 'links the homepage' do diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb index a97803b119e..f00f6b47b94 100644 --- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb @@ -41,13 +41,16 @@ describe Gitlab::DependencyLinker::GemfileLinker do end it 'links dependencies' do - expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails')) expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer')) - expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders')) - expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets')) expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for')) end + it 'links to external dependencies' do + expect(subject).to include(link('rails', 'https://github.com/rails/rails')) + expect(subject).to include(link('responders', 'https://github.com/rails/responders')) + expect(subject).to include(link('sprockets', 'https://gitlab.example.com/gems/sprockets')) + end + it 'links GitHub repos' do expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails')) expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders')) diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb index 24ad7d12f4c..6c6a5d70576 100644 --- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb @@ -43,8 +43,8 @@ describe Gitlab::DependencyLinker::GemspecLinker do %{#{name}} end - it 'links the gem name' do - expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git')) + it 'does not link the gem name' do + expect(subject).not_to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git')) end it 'links the license' do diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb index 1e8b72afb7b..9050127af7f 100644 --- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb @@ -33,7 +33,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do "express": "4.2.x", "bigpipe": "bigpipe/pagelet", "plates": "https://github.com/flatiron/plates/tarball/master", - "karma": "^1.4.1" + "karma": "^1.4.1", + "random": "git+https://EdOverflow@github.com/example/example.git" }, "devDependencies": { "vows": "^0.7.0", @@ -51,8 +52,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do %{#{name}} end - it 'links the module name' do - expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name')) + it 'does not link the module name' do + expect(subject).not_to include(link('module-name', 'https://npmjs.com/package/module-name')) end it 'links the homepage' do @@ -71,14 +72,21 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do expect(subject).to include(link('primus', 'https://npmjs.com/package/primus')) expect(subject).to include(link('async', 'https://npmjs.com/package/async')) expect(subject).to include(link('express', 'https://npmjs.com/package/express')) - expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe')) - expect(subject).to include(link('plates', 'https://npmjs.com/package/plates')) expect(subject).to include(link('karma', 'https://npmjs.com/package/karma')) expect(subject).to include(link('vows', 'https://npmjs.com/package/vows')) expect(subject).to include(link('assume', 'https://npmjs.com/package/assume')) expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit')) end + it 'links dependencies to URL detected on value' do + expect(subject).to include(link('bigpipe', 'https://github.com/bigpipe/pagelet')) + expect(subject).to include(link('plates', 'https://github.com/flatiron/plates/tarball/master')) + end + + it 'does not link to NPM when invalid git URL' do + expect(subject).not_to include(link('random', 'https://npmjs.com/package/random')) + end + it 'links GitHub repos' do expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet')) end diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb new file mode 100644 index 00000000000..f81dbcf62da --- /dev/null +++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +describe Gitlab::DependencyLinker::Parser::Gemfile do + describe '#parse' do + let(:file_content) do + <<-CONTENT.strip_heredoc + source 'https://rubygems.org' + + gem "rails", '4.2.6', github: "rails/rails" + gem 'rails-deprecated_sanitizer', '~> 1.0.3' + gem 'responders', '~> 2.0', :github => 'rails/responders' + gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets' + gem 'default_value_for', '~> 3.0.0' + CONTENT + end + + subject { described_class.new(file_content).parse(keyword: 'gem') } + + def fetch_package(name) + subject.find { |package| package.name == name } + end + + it 'returns parsed packages' do + expect(subject.size).to eq(5) + expect(subject).to all(be_a(Gitlab::DependencyLinker::Package)) + end + + it 'packages respond to name and external_ref accordingly' do + expect(fetch_package('rails')).to have_attributes(name: 'rails', + github_ref: 'rails/rails', + git_ref: nil) + + expect(fetch_package('sprockets')).to have_attributes(name: 'sprockets', + github_ref: nil, + git_ref: 'https://gitlab.example.com/gems/sprockets') + + expect(fetch_package('default_value_for')).to have_attributes(name: 'default_value_for', + github_ref: nil, + git_ref: nil) + end + end +end diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb index cdfd7ad9826..8f1b523653e 100644 --- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb @@ -43,7 +43,10 @@ describe Gitlab::DependencyLinker::PodfileLinker do it 'links packages' do expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking')) - expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar')) + end + + it 'links external packages' do + expect(subject).to include(link('Interstellar/Core', 'https://github.com/ashfurrow/Interstellar.git')) end it 'links Git repos' do diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb index ed60ab45955..bacec830103 100644 --- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb @@ -42,8 +42,8 @@ describe Gitlab::DependencyLinker::PodspecLinker do %{#{name}} end - it 'links the gem name' do - expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability')) + it 'does not link the pod name' do + expect(subject).not_to include(link('Reachability', 'https://cocoapods.org/pods/Reachability')) end it 'links the license' do -- cgit v1.2.1 From f6754010abf46fc65faade15054eef9343bb5622 Mon Sep 17 00:00:00 2001 From: Kotau Yauhen Date: Fri, 22 Feb 2019 08:57:27 +0000 Subject: Update external issue tracker services test, remove `prop_accessor :title` from YouTrack integration service --- .../services/user_activates_issue_tracker_spec.rb | 35 ++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb index 7cd5b12802b..74b9a2b20cd 100644 --- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb @@ -6,11 +6,17 @@ describe 'User activates issue tracker', :js do let(:url) { 'http://tracker.example.com' } - def fill_form(active = true) + def fill_short_form(active = true) check 'Active' if active fill_in 'service_project_url', with: url fill_in 'service_issues_url', with: "#{url}/:id" + end + + def fill_full_form(active = true) + fill_short_form(active) + check 'Active' if active + fill_in 'service_new_issue_url', with: url end @@ -21,14 +27,20 @@ describe 'User activates issue tracker', :js do visit project_settings_integrations_path(project) end - shared_examples 'external issue tracker activation' do |tracker:| + shared_examples 'external issue tracker activation' do |tracker:, skip_new_issue_url: false| describe 'user sets and activates the Service' do context 'when the connection test succeeds' do before do stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' }) click_link(tracker) - fill_form + + if skip_new_issue_url + fill_short_form + else + fill_full_form + end + click_button('Test settings and save changes') wait_for_requests end @@ -50,7 +62,13 @@ describe 'User activates issue tracker', :js do stub_request(:head, url).to_raise(HTTParty::Error) click_link(tracker) - fill_form + + if skip_new_issue_url + fill_short_form + else + fill_full_form + end + click_button('Test settings and save changes') wait_for_requests @@ -69,7 +87,13 @@ describe 'User activates issue tracker', :js do describe 'user sets the service but keeps it disabled' do before do click_link(tracker) - fill_form(false) + + if skip_new_issue_url + fill_short_form(false) + else + fill_full_form(false) + end + click_button('Save changes') end @@ -87,6 +111,7 @@ describe 'User activates issue tracker', :js do end it_behaves_like 'external issue tracker activation', tracker: 'Redmine' + it_behaves_like 'external issue tracker activation', tracker: 'YouTrack', skip_new_issue_url: true it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla' it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker' end -- cgit v1.2.1 From 2949479c67bf1058ad919452cfea6cadd52d4c41 Mon Sep 17 00:00:00 2001 From: Elias Werberich Date: Mon, 25 Feb 2019 08:57:41 +0100 Subject: Remove timezone conversion of issue due dates for issue board cards --- spec/javascripts/boards/components/issue_due_date_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/boards/components/issue_due_date_spec.js b/spec/javascripts/boards/components/issue_due_date_spec.js index 054cf8c5b7d..68e26b68f04 100644 --- a/spec/javascripts/boards/components/issue_due_date_spec.js +++ b/spec/javascripts/boards/components/issue_due_date_spec.js @@ -43,7 +43,7 @@ describe('Issue Due Date component', () => { date.setDate(date.getDate() + 5); vm = createComponent(date); - expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, 'dddd', true)); + expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, 'dddd')); }); it('should render month and day for other dates', () => { @@ -53,7 +53,7 @@ describe('Issue Due Date component', () => { const isDueInCurrentYear = today.getFullYear() === date.getFullYear(); const format = isDueInCurrentYear ? 'mmm d' : 'mmm d, yyyy'; - expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format, true)); + expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format)); }); it('should contain the correct `.text-danger` css class for overdue issue', () => { -- cgit v1.2.1 From 1ad699677fa4b24a9bc002c6dc20164b8832bca5 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 31 Jan 2019 16:32:44 -0200 Subject: Support merge to ref for merge-commit and squash Adds the ground work for writing into the merge ref refs/merge-requests/:iid/merge the merge result between source and target branches of a MR, without further side-effects such as mailing, MR updates and target branch changes. --- spec/lib/gitlab/git/repository_spec.rb | 31 ++++ spec/models/repository_spec.rb | 23 +++ .../merge_requests/merge_to_ref_service_spec.rb | 180 +++++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 spec/services/merge_requests/merge_to_ref_service_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 8a9e78ba3c3..b3a728c139e 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1704,6 +1704,37 @@ describe Gitlab::Git::Repository, :seed_helper do end end + describe '#merge_to_ref' do + let(:repository) { mutable_repository } + let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } + let(:left_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } + let(:right_branch) { 'test-master' } + let(:target_ref) { 'refs/merge-requests/999/merge' } + + before do + repository.create_branch(right_branch, branch_head) unless repository.branch_exists?(right_branch) + end + + def merge_to_ref + repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message') + end + + it 'generates a commit in the target_ref' do + expect(repository.ref_exists?(target_ref)).to be(false) + + commit_sha = merge_to_ref + ref_head = repository.commit(target_ref) + + expect(commit_sha).to be_present + expect(repository.ref_exists?(target_ref)).to be(true) + expect(ref_head.id).to eq(commit_sha) + end + + it 'does not change the right branch HEAD' do + expect { merge_to_ref }.not_to change { repository.find_branch(right_branch).target } + end + end + describe '#merge' do let(:repository) { mutable_repository } let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f78760bf567..17201d8b90a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1373,6 +1373,29 @@ describe Repository do end end + describe '#merge_to_ref' do + let(:merge_request) do + create(:merge_request, source_branch: 'feature', + target_branch: 'master', + source_project: project) + end + + it 'writes merge of source and target to MR merge_ref_path' do + merge_commit_id = repository.merge_to_ref(user, + merge_request.diff_head_sha, + merge_request, + merge_request.merge_ref_path, + 'Custom message') + + merge_commit = repository.commit(merge_commit_id) + + expect(merge_commit.message).to eq('Custom message') + expect(merge_commit.author_name).to eq(user.name) + expect(merge_commit.author_email).to eq(user.commit_email) + expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present + end + end + describe '#ff_merge' do before do repository.add_branch(user, 'ff-target', 'feature~5') diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb new file mode 100644 index 00000000000..435a863cbd4 --- /dev/null +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::MergeToRefService do + set(:user) { create(:user) } + let(:merge_request) { create(:merge_request, :simple) } + let(:project) { merge_request.project } + + before do + project.add_maintainer(user) + end + + describe '#execute' do + let(:service) do + described_class.new(project, user, + commit_message: 'Awesome message', + 'should_remove_source_branch' => true) + end + + def process_merge_to_ref + perform_enqueued_jobs do + service.execute(merge_request) + end + end + + it 'writes commit to merge ref' do + repository = project.repository + target_ref = merge_request.merge_ref_path + + expect(repository.ref_exists?(target_ref)).to be(false) + + result = service.execute(merge_request) + + ref_head = repository.commit(target_ref) + + expect(result[:status]).to eq(:success) + expect(result[:commit_id]).to be_present + expect(repository.ref_exists?(target_ref)).to be(true) + expect(ref_head.id).to eq(result[:commit_id]) + end + + it 'does not send any mail' do + expect { process_merge_to_ref }.not_to change { ActionMailer::Base.deliveries.count } + end + + it 'does not change the MR state' do + expect { process_merge_to_ref }.not_to change { merge_request.state } + end + + it 'does not create notes' do + expect { process_merge_to_ref }.not_to change { merge_request.notes.count } + end + + it 'does not delete the source branch' do + expect(DeleteBranchService).not_to receive(:new) + + process_merge_to_ref + end + + it 'returns an error when the failing to process the merge' do + allow(project.repository).to receive(:merge_to_ref).and_return(nil) + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Conflicts detected during merge') + end + + context 'commit history comparison with regular MergeService' do + let(:merge_ref_service) do + described_class.new(project, user, {}) + end + + let(:merge_service) do + MergeRequests::MergeService.new(project, user, {}) + end + + shared_examples_for 'MergeService for target ref' do + it 'target_ref has the same state of target branch' do + repo = merge_request.target_project.repository + + process_merge_to_ref + merge_service.execute(merge_request) + + ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3) + target_branch_commits = repo.commits(merge_request.target_branch, limit: 3) + + ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit| + expect(ref_commit.parents).to eq(target_branch_commit.parents) + end + end + end + + context 'when merge commit' do + it_behaves_like 'MergeService for target ref' + end + + context 'when merge commit with squash' do + before do + merge_request.update!(squash: true, source_branch: 'master', target_branch: 'feature') + end + + it_behaves_like 'MergeService for target ref' + end + end + + context 'merge pre-condition checks' do + before do + merge_request.project.update!(merge_method: merge_method) + end + + context 'when semi-linear merge method' do + let(:merge_method) { :rebase_merge } + + it 'return error when MR should be able to fast-forward' do + allow(merge_request).to receive(:should_be_rebased?) { true } + + error_message = 'Fast-forward merge is not possible. Please update your source branch.' + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end + end + + context 'when fast-forward merge method' do + let(:merge_method) { :ff } + + it 'returns error' do + error_message = "Fast-forward to #{merge_request.merge_ref_path} is currently not supported." + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end + end + + context 'when MR is not mergeable to ref' do + let(:merge_method) { :merge } + + it 'returns error' do + allow(merge_request).to receive(:mergeable_to_ref?) { false } + + error_message = "Merge request is not mergeable to #{merge_request.merge_ref_path}" + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end + end + end + + context 'does not close related todos' do + let(:merge_request) { create(:merge_request, assignee: user, author: user) } + let(:project) { merge_request.project } + let!(:todo) do + create(:todo, :assigned, + project: project, + author: user, + user: user, + target: merge_request) + end + + before do + allow(service).to receive(:execute_hooks) + + perform_enqueued_jobs do + service.execute(merge_request) + todo.reload + end + end + + it { expect(todo).not_to be_done } + end + end +end -- cgit v1.2.1 From 105212ce49007ffc3489c2039e55056d8df8fa95 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 11 Feb 2019 13:14:11 -0200 Subject: Check authorization in merge services Move authorization checks to merge services instead relying solely on external checks. --- spec/services/merge_requests/merge_service_spec.rb | 12 ++++++ .../merge_requests/merge_to_ref_service_spec.rb | 44 ++++++++++++++-------- 2 files changed, 40 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 04a62aa454d..ede79b87bcc 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -224,6 +224,18 @@ describe MergeRequests::MergeService do expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) end + it 'logs and saves error if user is not authorized' do + unauthorized_user = create(:user) + project.add_reporter(unauthorized_user) + + service = described_class.new(project, unauthorized_user) + + service.execute(merge_request) + + expect(merge_request.merge_error) + .to eq('You are not allowed to merge this merge request') + end + it 'logs and saves error if there is an PreReceiveError exception' do error_message = 'error message' diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 435a863cbd4..696f1b83157 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -3,6 +3,22 @@ require 'spec_helper' describe MergeRequests::MergeToRefService do + shared_examples_for 'MergeService for target ref' do + it 'target_ref has the same state of target branch' do + repo = merge_request.target_project.repository + + process_merge_to_ref + merge_service.execute(merge_request) + + ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3) + target_branch_commits = repo.commits(merge_request.target_branch, limit: 3) + + ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit| + expect(ref_commit.parents).to eq(target_branch_commit.parents) + end + end + end + set(:user) { create(:user) } let(:merge_request) { create(:merge_request, :simple) } let(:project) { merge_request.project } @@ -76,22 +92,6 @@ describe MergeRequests::MergeToRefService do MergeRequests::MergeService.new(project, user, {}) end - shared_examples_for 'MergeService for target ref' do - it 'target_ref has the same state of target branch' do - repo = merge_request.target_project.repository - - process_merge_to_ref - merge_service.execute(merge_request) - - ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3) - target_branch_commits = repo.commits(merge_request.target_branch, limit: 3) - - ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit| - expect(ref_commit.parents).to eq(target_branch_commit.parents) - end - end - end - context 'when merge commit' do it_behaves_like 'MergeService for target ref' end @@ -176,5 +176,17 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end + + it 'returns error when user has no authorization to admin the merge request' do + unauthorized_user = create(:user) + project.add_reporter(unauthorized_user) + + service = described_class.new(project, unauthorized_user) + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('You are not allowed to merge to this ref') + end end end -- cgit v1.2.1 From 4e16edbe0a26d95ea94ad61ebaadac4f7463352b Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 12 Feb 2019 08:22:44 -0200 Subject: Add feature-flag support Returns error in MergeToRefService when merge_to_tmp_merge_ref_path ff is disabled. --- spec/services/merge_requests/merge_to_ref_service_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 696f1b83157..96f2fde7117 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -74,6 +74,15 @@ describe MergeRequests::MergeToRefService do process_merge_to_ref end + it 'returns error when feature is disabled' do + stub_feature_flags(merge_to_tmp_merge_ref_path: false) + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Feature is not enabled') + end + it 'returns an error when the failing to process the merge' do allow(project.repository).to receive(:merge_to_ref).and_return(nil) -- cgit v1.2.1 From b81e7c52d3e82e0c053a39fac9c4fe21abcf6103 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Mon, 25 Feb 2019 21:36:28 +0800 Subject: Enable `:read_list` when `:read_group` is enabled --- spec/policies/group_policy_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index be1804c5ce0..af6d6f084a9 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -83,7 +83,7 @@ describe GroupPolicy do end it do - expect_allowed(:read_group, :read_label) + expect_allowed(:read_group, :read_list, :read_label) end context 'in subgroups', :nested_groups do @@ -91,7 +91,7 @@ describe GroupPolicy do let(:project) { create(:project, namespace: subgroup) } it do - expect_allowed(:read_group, :read_label) + expect_allowed(:read_group, :read_list, :read_label) end end end -- cgit v1.2.1 From 7e83acb8a2f7fe4a0c0acd6769114e0593c677bb Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 22 Feb 2019 11:31:02 -0300 Subject: Prevent disclosing project milestone titles Prevent unauthorized users having access to milestone titles through autocomplete endpoint. --- .../autocomplete_sources_controller_spec.rb | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/autocomplete_sources_controller_spec.rb b/spec/controllers/projects/autocomplete_sources_controller_spec.rb index 4bc72042710..a9a058e7e17 100644 --- a/spec/controllers/projects/autocomplete_sources_controller_spec.rb +++ b/spec/controllers/projects/autocomplete_sources_controller_spec.rb @@ -35,4 +35,35 @@ describe Projects::AutocompleteSourcesController do avatar_url: user.avatar_url) end end + + describe 'GET milestones' do + let(:group) { create(:group, :public) } + let(:project) { create(:project, :public, namespace: group) } + let!(:project_milestone) { create(:milestone, project: project) } + let!(:group_milestone) { create(:milestone, group: group) } + + before do + sign_in(user) + end + + it 'lists milestones' do + group.add_owner(user) + + get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path } + + milestone_titles = json_response.map { |milestone| milestone["title"] } + expect(milestone_titles).to match_array([project_milestone.title, group_milestone.title]) + end + + context 'when user cannot read project issues and merge requests' do + it 'renders 404' do + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE) + + get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path } + + expect(response).to have_gitlab_http_status(404) + end + end + end end -- cgit v1.2.1 From fa882a674a389fc41a155f5ba72e757a7ea02d7a Mon Sep 17 00:00:00 2001 From: Andrew Fontaine Date: Mon, 11 Feb 2019 10:25:10 -0500 Subject: Sort Environments in Table by Last Updated Ensure folders push to the top, if both have no last update, sort by name. The sorting algorithm should sort in the following priorities: 1. folders first, 2. last updated descending, 3. by name ascending, the sorting algorithm must: 1. Sort by name ascending, 2. Reverse (sort by name descending), 3. Sort by last deployment ascending, 4. Reverse (last deployment descending, name ascending), 5. Put folders first. It is done this way, as `underscore`'s sort API is very basic: simple comparisons, sorting by ascending only. --- .../environments/environment_table_spec.js | 220 +++++++++++++++++++++ 1 file changed, 220 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js index 52895f35f3a..ecd28594873 100644 --- a/spec/javascripts/environments/environment_table_spec.js +++ b/spec/javascripts/environments/environment_table_spec.js @@ -31,4 +31,224 @@ describe('Environment table', () => { expect(vm.$el.getAttribute('class')).toContain('ci-table'); }); + + describe('sortEnvironments', () => { + it('should sort environments by last updated', () => { + const mockItems = [ + { + name: 'old', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 0, 5).toISOString(), + }, + }, + { + name: 'new', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 1, 5).toISOString(), + }, + }, + { + name: 'older', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2018, 0, 5).toISOString(), + }, + }, + { + name: 'an environment with no deployment', + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [old, newer, older, noDeploy] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([newer, old, older, noDeploy]); + }); + + it('should push environments with no deployments to the bottom', () => { + const mockItems = [ + { + name: 'production', + size: 1, + id: 2, + state: 'available', + external_url: 'https://google.com/production', + environment_type: null, + last_deployment: null, + has_stop_action: false, + environment_path: '/Commit451/lab-coat/environments/2', + stop_path: '/Commit451/lab-coat/environments/2/stop', + folder_path: '/Commit451/lab-coat/environments/folders/production', + created_at: '2019-01-17T16:26:10.064Z', + updated_at: '2019-01-17T16:27:37.717Z', + can_stop: true, + }, + { + name: 'review/225addcibuildstatus', + size: 2, + isFolder: true, + isLoadingFolderContent: false, + folderName: 'review', + isOpen: false, + children: [], + id: 12, + state: 'available', + external_url: 'https://google.com/review/225addcibuildstatus', + environment_type: 'review', + last_deployment: null, + has_stop_action: false, + environment_path: '/Commit451/lab-coat/environments/12', + stop_path: '/Commit451/lab-coat/environments/12/stop', + folder_path: '/Commit451/lab-coat/environments/folders/review', + created_at: '2019-01-17T16:27:37.877Z', + updated_at: '2019-01-17T16:27:37.883Z', + can_stop: true, + }, + { + name: 'staging', + size: 1, + id: 1, + state: 'available', + external_url: 'https://google.com/staging', + environment_type: null, + last_deployment: { + created_at: '2019-01-17T16:26:15.125Z', + scheduled_actions: [], + }, + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [prod, review, staging] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([review, staging, prod]); + }); + + it('should sort environments by folder first', () => { + const mockItems = [ + { + name: 'old', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 0, 5).toISOString(), + }, + }, + { + name: 'new', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 1, 5).toISOString(), + }, + }, + { + name: 'older', + size: 3, + isFolder: true, + children: [], + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [old, newer, older] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]); + }); + + it('should break ties by name', () => { + const mockItems = [ + { + name: 'old', + isFolder: false, + }, + { + name: 'new', + isFolder: false, + }, + { + folderName: 'older', + isFolder: true, + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [old, newer, older] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]); + }); + }); + + describe('sortedEnvironments', () => { + it('it should sort children as well', () => { + const mockItems = [ + { + name: 'production', + last_deployment: null, + }, + { + name: 'review/225addcibuildstatus', + isFolder: true, + folderName: 'review', + isOpen: true, + children: [ + { + name: 'review/225addcibuildstatus', + last_deployment: { + created_at: '2019-01-17T16:26:15.125Z', + }, + }, + { + name: 'review/master', + last_deployment: { + created_at: '2019-02-17T16:26:15.125Z', + }, + }, + ], + }, + { + name: 'staging', + last_deployment: { + created_at: '2019-01-17T16:26:15.125Z', + }, + }, + ]; + const [production, review, staging] = mockItems; + const [addcibuildstatus, master] = mockItems[1].children; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + expect(vm.sortedEnvironments.map(env => env.name)).toEqual([ + review.name, + staging.name, + production.name, + ]); + + expect(vm.sortedEnvironments[0].children).toEqual([master, addcibuildstatus]); + }); + }); }); -- cgit v1.2.1 From e95b88a06d673ff95792d5c7d3934c88051691d9 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 25 Feb 2019 12:42:34 -0800 Subject: Revert "Merge branch 'filter-confidential-issues' into 'master'" This reverts commit d133bf84c668df3dfc9938bb04150754cb873c8b, reversing changes made to 7981c0292b07a0138b096fa082341fcb13e9ce2b. --- .../concerns/issuable_collections_spec.rb | 4 +-- .../issues/filtered_search/dropdown_hint_spec.rb | 11 +------ .../issues/filtered_search/search_bar_spec.rb | 2 +- spec/finders/issues_finder_spec.rb | 28 +---------------- spec/models/issue_spec.rb | 9 ------ spec/requests/api/issues_spec.rb | 36 ---------------------- 6 files changed, 4 insertions(+), 86 deletions(-) (limited to 'spec') diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb index 8580900215c..307c5d60c57 100644 --- a/spec/controllers/concerns/issuable_collections_spec.rb +++ b/spec/controllers/concerns/issuable_collections_spec.rb @@ -112,8 +112,7 @@ describe IssuableCollections do assignee_username: 'user1', author_id: '2', author_username: 'user2', - authorized_only: 'yes', - confidential: true, + authorized_only: 'true', due_date: '2017-01-01', group_id: '3', iids: '4', @@ -141,7 +140,6 @@ describe IssuableCollections do 'assignee_username' => 'user1', 'author_id' => '2', 'author_username' => 'user2', - 'confidential' => true, 'label_name' => 'foo', 'milestone_title' => 'bar', 'my_reaction_emoji' => 'thumbsup', diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 096756f19cc..0e296ab2109 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -66,7 +66,7 @@ describe 'Dropdown hint', :js do it 'filters with text' do filtered_search.set('a') - expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5) + expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4) end end @@ -119,15 +119,6 @@ describe 'Dropdown hint', :js do expect_tokens([{ name: 'my-reaction' }]) expect_filtered_search_input_empty end - - it 'opens the yes-no dropdown when you click on confidential' do - click_hint('confidential') - - expect(page).to have_css(js_dropdown_hint, visible: false) - expect(page).to have_css('#js-dropdown-confidential', visible: true) - expect_tokens([{ name: 'confidential' }]) - expect_filtered_search_input_empty - end end describe 'selecting from dropdown with some input' do diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index c4468922883..8abab3f35d6 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -100,7 +100,7 @@ describe 'Search bar', :js do find('.filtered-search-box .clear-search').click filtered_search.click - expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6) + expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5) expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset) end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 47e2548c3d6..fe8000e419b 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -490,32 +490,6 @@ describe IssuesFinder do end end - context 'filtering by confidential' do - set(:confidential_issue) { create(:issue, project: project1, confidential: true) } - - context 'no filtering' do - it 'returns all issues' do - expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, confidential_issue) - end - end - - context 'user filters confidential issues' do - let(:params) { { confidential: true } } - - it 'returns only confdential issues' do - expect(issues).to contain_exactly(confidential_issue) - end - end - - context 'user filters only public issues' do - let(:params) { { confidential: false } } - - it 'returns only confdential issues' do - expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) - end - end - end - context 'when the user is unauthorized' do let(:search_user) { nil } @@ -582,7 +556,7 @@ describe IssuesFinder do it 'returns the number of rows for the default state' do finder = described_class.new(user) - expect(finder.row_count).to eq(5) + expect(finder.row_count).to eq(4) end it 'returns the number of rows for a given state' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 6101df2e099..5d18e085a6f 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -765,15 +765,6 @@ describe Issue do end end - describe '.confidential_only' do - it 'only returns confidential_only issues' do - create(:issue) - confidential_issue = create(:issue, confidential: true) - - expect(described_class.confidential_only).to eq([confidential_issue]) - end - end - it_behaves_like 'throttled touch' do subject { create(:issue, updated_at: 1.hour.ago) } end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 1a4be2bd30f..d10ee6cc320 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -183,18 +183,6 @@ describe API::Issues do expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end - it 'returns only confidential issues' do - get api('/issues', user), params: { confidential: true, scope: 'all' } - - expect_paginated_array_response(confidential_issue.id) - end - - it 'returns only public issues' do - get api('/issues', user), params: { confidential: false } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - it 'returns issues reacted by the authenticated user' do issue2 = create(:issue, project: project, author: user, assignees: [user]) create(:award_emoji, awardable: issue2, user: user2, name: 'star') @@ -569,18 +557,6 @@ describe API::Issues do expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end - it 'returns only confidential issues' do - get api(base_url, user), params: { confidential: true } - - expect_paginated_array_response(group_confidential_issue.id) - end - - it 'returns only public issues' do - get api(base_url, user), params: { confidential: false } - - expect_paginated_array_response([group_closed_issue.id, group_issue.id]) - end - it 'returns an array of labeled group issues' do get api(base_url, user), params: { labels: group_label.title } @@ -806,18 +782,6 @@ describe API::Issues do expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end - it 'returns only confidential issues' do - get api("#{base_url}/issues", author), params: { confidential: true } - - expect_paginated_array_response(confidential_issue.id) - end - - it 'returns only public issues' do - get api("#{base_url}/issues", author), params: { confidential: false } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - it 'returns project confidential issues for assignee' do get api("#{base_url}/issues", assignee) -- cgit v1.2.1 From 9ffdd736f86b053fd02752e7f86f63229aebf602 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 25 Feb 2019 12:43:14 -0800 Subject: Revert "Merge branch '49449-add-an-api-endpoint-for-bulk-updating-issues-and-mrs' into 'master'" This reverts commit 7981c0292b07a0138b096fa082341fcb13e9ce2b, reversing changes made to 9202bbd129537a698b986e6295d0c783b5a84815. --- spec/requests/api/issuable_bulk_update_spec.rb | 154 ------------------------- 1 file changed, 154 deletions(-) delete mode 100644 spec/requests/api/issuable_bulk_update_spec.rb (limited to 'spec') diff --git a/spec/requests/api/issuable_bulk_update_spec.rb b/spec/requests/api/issuable_bulk_update_spec.rb deleted file mode 100644 index 6463f3f5d35..00000000000 --- a/spec/requests/api/issuable_bulk_update_spec.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe API::IssuableBulkUpdate do - set(:project) { create(:project) } - set(:user) { project.creator } - - shared_examples "PUT /projects/:id/:issuable/bulk_update" do |issuable| - def bulk_update(issuable, issuables, params, update_user = user) - put api("/projects/#{project.id}/#{issuable.pluralize}/bulk_update", update_user), - params: { issuable_ids: Array(issuables).map(&:id) }.merge(params) - end - - context 'with not enough permissions' do - it 'returns 403 for guest users' do - guest = create(:user) - project.add_guest(guest) - - bulk_update(issuable, issuables, { state_event: 'close' }, guest) - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'when modifying the state' do - it "closes #{issuable}" do - bulk_update(issuable, issuables, { state_event: 'close' }) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['message']).to eq("#{issuables.count} #{issuable.pluralize(issuables.count)} updated") - expect(project.public_send(issuable.pluralize).opened).to be_empty - expect(project.public_send(issuable.pluralize).closed).not_to be_empty - end - - it "opens #{issuable}" do - closed_issuables = create_list("closed_#{issuable}".to_sym, 2) - - bulk_update(issuable, closed_issuables, { state_event: 'reopen' }) - - expect(response).to have_gitlab_http_status(200) - expect(project.public_send(issuable.pluralize).closed).to be_empty - end - end - - context 'when modifying the milestone' do - let(:milestone) { create(:milestone, project: project) } - - it "adds a milestone #{issuable}" do - bulk_update(issuable, issuables, { milestone_id: milestone.id }) - - expect(response).to have_gitlab_http_status(200) - issuables.each do |issuable| - expect(issuable.reload.milestone).to eq(milestone) - end - end - - it 'removes a milestone' do - issuables.first.milestone = milestone - milestone_issuable = issuables.first - - bulk_update(issuable, [milestone_issuable], { milestone_id: 0 }) - - expect(response).to have_gitlab_http_status(200) - expect(milestone_issuable.reload.milestone).to eq(nil) - end - end - - context 'when modifying the subscription state' do - it "subscribes to #{issuable}" do - bulk_update(issuable, issuables, { subscription_event: 'subscribe' }) - - expect(response).to have_gitlab_http_status(200) - expect(issuables).to all(be_subscribed(user, project)) - end - - it 'unsubscribes from issues' do - issuables.each do |issuable| - issuable.subscriptions.create(user: user, project: project, subscribed: true) - end - - bulk_update(issuable, issuables, { subscription_event: 'unsubscribe' }) - - expect(response).to have_gitlab_http_status(200) - issuables.each do |issuable| - expect(issuable).not_to be_subscribed(user, project) - end - end - end - - context 'when modifying the assignee' do - it 'adds assignee to issues' do - params = issuable == 'issue' ? { assignee_ids: [user.id] } : { assignee_id: user.id } - - bulk_update(issuable, issuables, params) - - expect(response).to have_gitlab_http_status(200) - issuables.each do |issuable| - expect(issuable.reload.assignees).to eq([user]) - end - end - - it 'removes assignee' do - assigned_issuable = issuables.first - - if issuable == 'issue' - params = { assignee_ids: 0 } - assigned_issuable.assignees << user - else - params = { assignee_id: 0 } - assigned_issuable.update_attribute(:assignee, user) - end - - bulk_update(issuable, [assigned_issuable], params) - expect(assigned_issuable.reload.assignees).to eq([]) - end - end - - context 'when modifying labels' do - let(:bug) { create(:label, project: project) } - let(:regression) { create(:label, project: project) } - let(:feature) { create(:label, project: project) } - - it 'adds new labels' do - bulk_update(issuable, issuables, { add_label_ids: [bug.id, regression.id, feature.id] }) - - issuables.each do |issusable| - expect(issusable.reload.label_ids).to contain_exactly(bug.id, regression.id, feature.id) - end - end - - it 'removes labels' do - labled_issuable = issuables.first - labled_issuable.labels << bug - labled_issuable.labels << regression - labled_issuable.labels << feature - - bulk_update(issuable, [labled_issuable], { remove_label_ids: [bug.id, regression.id] }) - - expect(labled_issuable.reload.label_ids).to contain_exactly(feature.id) - end - end - end - - it_behaves_like 'PUT /projects/:id/:issuable/bulk_update', 'issue' do - let(:issuables) { create_list(:issue, 2, project: project) } - end - - it_behaves_like 'PUT /projects/:id/:issuable/bulk_update', 'merge_request' do - let(:merge_request_1) { create(:merge_request, source_project: project) } - let(:merge_request_2) { create(:merge_request, :simple, source_project: project) } - let(:issuables) { [merge_request_1, merge_request_2] } - end -end -- cgit v1.2.1 From ea6945b8113160f2009daa854e612eb009690a2a Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 25 Feb 2019 12:43:26 -0800 Subject: Revert "Merge branch 'sh-fix-cpp-templates-404' into 'master'" This reverts commit 4b282e9ce1ae246c4538b3ede18d1380ea778029, reversing changes made to b6f37434a7022be1be40c0d0519aea3ceca25ea5. --- spec/requests/api/project_templates_spec.rb | 28 ---------------------------- 1 file changed, 28 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb index 80e5033dab4..ab5d4de7ff7 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -92,22 +92,6 @@ describe API::ProjectTemplates do expect(json_response['name']).to eq('Actionscript') end - it 'returns C++ gitignore' do - get api("/projects/#{public_project.id}/templates/gitignores/C++") - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/template') - expect(json_response['name']).to eq('C++') - end - - it 'returns C++ gitignore for URL-encoded names' do - get api("/projects/#{public_project.id}/templates/gitignores/C%2B%2B") - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/template') - expect(json_response['name']).to eq('C++') - end - it 'returns a specific gitlab_ci_yml' do get api("/projects/#{public_project.id}/templates/gitlab_ci_ymls/Android") @@ -141,18 +125,6 @@ describe API::ProjectTemplates do expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/license') end - - shared_examples 'path traversal attempt' do |template_type| - it 'rejects invalid filenames' do - get api("/projects/#{public_project.id}/templates/#{template_type}/%2e%2e%2fPython%2ea") - - expect(response).to have_gitlab_http_status(500) - end - end - - TemplateFinder::VENDORED_TEMPLATES.each do |template_type, _| - it_behaves_like 'path traversal attempt', template_type - end end describe 'GET /projects/:id/templates/licenses/:key' do -- cgit v1.2.1 From cae2c336819d1838507b0c5d21c609710b21ac4f Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 25 Feb 2019 12:53:09 -0800 Subject: Revert "Merge branch 'fix-misspellings-app-comments' into 'master'" This reverts commit 9202bbd129537a698b986e6295d0c783b5a84815, reversing changes made to 4b282e9ce1ae246c4538b3ede18d1380ea778029. --- spec/features/issues/gfm_autocomplete_spec.rb | 2 +- spec/features/projects/blobs/edit_spec.rb | 2 +- spec/features/projects/wiki/markdown_preview_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 986f3823275..c22ad0d20ef 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -278,7 +278,7 @@ describe 'GFM autocomplete', :js do end end - # This context has just one example in each contexts in order to improve spec performance. + # This context has jsut one example in each contexts in order to improve spec performance. context 'labels', :quarantine do let!(:backend) { create(:label, project: project, title: 'backend') } let!(:bug) { create(:label, project: project, title: 'bug') } diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 1522a3361a1..6e6c299ee2e 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -77,7 +77,7 @@ describe 'Editing file blob', :js do click_link 'Preview' wait_for_requests - # the above generates two separate lists (not embedded) in CommonMark + # the above generates two seperate lists (not embedded) in CommonMark expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 49058d1372a..49244c53a91 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -170,7 +170,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do fill_in :wiki_content, with: "1. one\n - sublist\n" click_on "Preview" - # the above generates two separate lists (not embedded) in CommonMark + # the above generates two seperate lists (not embedded) in CommonMark expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end -- cgit v1.2.1 From ccb4edbca1aa7e94a76a5a8d361af02fd093e1b9 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Mon, 18 Feb 2019 14:19:49 +1300 Subject: Improve GraphQL Authorization DSL Previously GraphQL field authorization happened like this: class ProjectType field :my_field, MyFieldType do authorize :permission end end This change allowed us to authorize like this instead: class ProjectType field :my_field, MyFieldType, authorize: :permission end A new initializer registers the `authorize` metadata keyword on GraphQL Schema Objects and Fields, and we can collect this data within the context of Instrumentation like this: field.metadata[:authorize] The previous functionality of authorize is still being used for mutations, as the #authorize method here is called at during the code that executes during the mutation, rather than when a field resolves. https://gitlab.com/gitlab-org/gitlab-ce/issues/57828 --- spec/graphql/features/authorization_spec.rb | 106 +++++++++++++++++++++ .../graphql/authorize/authorize_resource_spec.rb | 18 ++++ spec/lib/gitlab/graphql/authorize_spec.rb | 20 ---- spec/support/matchers/graphql_matchers.rb | 4 +- 4 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 spec/graphql/features/authorization_spec.rb delete mode 100644 spec/lib/gitlab/graphql/authorize_spec.rb (limited to 'spec') diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb new file mode 100644 index 00000000000..a229d29afa6 --- /dev/null +++ b/spec/graphql/features/authorization_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Gitlab::Graphql::Authorization' do + set(:user) { create(:user) } + + let(:test_object) { double(name: 'My name') } + let(:object_type) { object_type_class } + let(:query_type) { query_type_class(object_type, test_object) } + let(:schema) { schema_class(query_type) } + + let(:execute) do + schema.execute( + query_string, + context: { current_user: user }, + variables: {} + ) + end + + let(:result) { execute['data'] } + + before do + # By default, disallow all permissions. + allow(Ability).to receive(:allowed?).and_return(false) + end + + describe 'authorizing with a single permission' do + let(:query_string) { '{ singlePermission() { name } }' } + + subject { result['singlePermission'] } + + it 'should return the protected field when user has permission' do + permit(:foo) + + expect(subject['name']).to eq(test_object.name) + end + + it 'should return nil when user is not authorized' do + expect(subject).to be_nil + end + end + + describe 'authorizing with an Array of permissions' do + let(:query_string) { '{ permissionCollection() { name } }' } + + subject { result['permissionCollection'] } + + it 'should return the protected field when user has all permissions' do + permit(:foo, :bar) + + expect(subject['name']).to eq(test_object.name) + end + + it 'should return nil when user only has one of the permissions' do + permit(:foo) + + expect(subject).to be_nil + end + + it 'should return nil when user only has none of the permissions' do + expect(subject).to be_nil + end + end + + private + + def permit(*permissions) + permissions.each do |permission| + allow(Ability).to receive(:allowed?).with(user, permission, test_object).and_return(true) + end + end + + def object_type_class + Class.new(Types::BaseObject) do + graphql_name 'TestObject' + + field :name, GraphQL::STRING_TYPE, null: true + end + end + + def query_type_class(type, object) + Class.new(Types::BaseObject) do + graphql_name 'TestQuery' + + field :single_permission, type, + null: true, + authorize: :foo, + resolve: ->(obj, args, ctx) { object } + + field :permission_collection, type, + null: true, + resolve: ->(obj, args, ctx) { object } do + authorize [:foo, :bar] + end + end + end + + def schema_class(query) + Class.new(GraphQL::Schema) do + use Gitlab::Graphql::Authorize + + query(query) + end + end +end diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb index 95bf7685ade..13cf52fd795 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb @@ -100,4 +100,22 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do expect { fake_class.new.find_object }.to raise_error(/Implement #find_object in #{fake_class.name}/) end end + + describe '#authorize' do + it 'adds permissions from subclasses to those of superclasses when used on classes' do + base_class = Class.new do + include Gitlab::Graphql::Authorize::AuthorizeResource + + authorize :base_authorization + end + + sub_class = Class.new(base_class) do + authorize :sub_authorization + end + + expect(base_class.required_permissions).to contain_exactly(:base_authorization) + expect(sub_class.required_permissions) + .to contain_exactly(:base_authorization, :sub_authorization) + end + end end diff --git a/spec/lib/gitlab/graphql/authorize_spec.rb b/spec/lib/gitlab/graphql/authorize_spec.rb deleted file mode 100644 index 9c17a3b0e4b..00000000000 --- a/spec/lib/gitlab/graphql/authorize_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Graphql::Authorize do - describe '#authorize' do - it 'adds permissions from subclasses to those of superclasses when used on classes' do - base_class = Class.new do - extend Gitlab::Graphql::Authorize - - authorize :base_authorization - end - sub_class = Class.new(base_class) do - authorize :sub_authorization - end - - expect(base_class.required_permissions).to contain_exactly(:base_authorization) - expect(sub_class.required_permissions) - .to contain_exactly(:base_authorization, :sub_authorization) - end - end -end diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index 7be84838e00..7894484f590 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -1,8 +1,6 @@ RSpec::Matchers.define :require_graphql_authorizations do |*expected| match do |field| - field_definition = field.metadata[:type_class] - expect(field_definition).to respond_to(:required_permissions) - expect(field_definition.required_permissions).to contain_exactly(*expected) + expect(field.metadata[:authorize]).to eq(*expected) end end -- cgit v1.2.1 From f31489204e402216483b1829cb1fdc9c8b0f2fc0 Mon Sep 17 00:00:00 2001 From: Anton Melser Date: Tue, 19 Feb 2019 12:55:02 +0000 Subject: Fix incorrect Pages Domains HTTPS checkkbox desc Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/56519 --- spec/features/projects/pages_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 435fb229b69..0d2a4ec1c8b 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -233,7 +233,7 @@ describe 'Pages' do it 'tries to change the setting' do visit project_pages_path(project) - expect(page).to have_content("Force domains with SSL certificates to use HTTPS") + expect(page).to have_content("Force HTTPS (requires valid certificates)") uncheck :project_pages_https_only @@ -282,7 +282,7 @@ describe 'Pages' do visit project_pages_path(project) expect(page).not_to have_field(:project_pages_https_only) - expect(page).not_to have_content('Force domains with SSL certificates to use HTTPS') + expect(page).not_to have_content('Force HTTPS (requires valid certificates)') expect(page).not_to have_button('Save') end end -- cgit v1.2.1 From df044542cb1563d7b7ba9a97b8c24e99a2488fcd Mon Sep 17 00:00:00 2001 From: John Cai Date: Mon, 25 Feb 2019 17:58:51 -0800 Subject: Fix project set_repository_writable! --- spec/models/project_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index bcbe687f4a2..65cf04de13c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2511,6 +2511,16 @@ describe Project do end end + describe '#set_repository_writable!' do + it 'sets repository_read_only to false' do + project = create(:project, :read_only) + + expect { project.set_repository_writable! } + .to change(project, :repository_read_only) + .from(true).to(false) + end + end + describe '#pushes_since_gc' do let(:project) { create(:project) } -- cgit v1.2.1 From b01d439733a958d533fa6f2abaa9daae476ce133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 26 Feb 2019 09:29:28 +0100 Subject: Quarantine spec/features/issues/filtered_search/search_bar_spec.rb:89 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/issues/filtered_search/search_bar_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 8abab3f35d6..891ef884682 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -86,7 +86,7 @@ describe 'Search bar', :js do expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size) end - it 'resets the dropdown filters' do + it 'resets the dropdown filters', :quarantine do filtered_search.click hint_offset = get_left_style(find('#js-dropdown-hint')['style']) -- cgit v1.2.1 From afd290a444d09e84092e5a5d738bcf28ec71f3c6 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 25 Feb 2019 10:41:50 +0000 Subject: Merge branch 'sh-fix-cpp-templates-404' into 'master' Fix 404s when C++ .gitignore template selected Closes #57857 See merge request gitlab-org/gitlab-ce!25416 --- spec/requests/api/project_templates_spec.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb index ab5d4de7ff7..80e5033dab4 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -92,6 +92,22 @@ describe API::ProjectTemplates do expect(json_response['name']).to eq('Actionscript') end + it 'returns C++ gitignore' do + get api("/projects/#{public_project.id}/templates/gitignores/C++") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('C++') + end + + it 'returns C++ gitignore for URL-encoded names' do + get api("/projects/#{public_project.id}/templates/gitignores/C%2B%2B") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('C++') + end + it 'returns a specific gitlab_ci_yml' do get api("/projects/#{public_project.id}/templates/gitlab_ci_ymls/Android") @@ -125,6 +141,18 @@ describe API::ProjectTemplates do expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/license') end + + shared_examples 'path traversal attempt' do |template_type| + it 'rejects invalid filenames' do + get api("/projects/#{public_project.id}/templates/#{template_type}/%2e%2e%2fPython%2ea") + + expect(response).to have_gitlab_http_status(500) + end + end + + TemplateFinder::VENDORED_TEMPLATES.each do |template_type, _| + it_behaves_like 'path traversal attempt', template_type + end end describe 'GET /projects/:id/templates/licenses/:key' do -- cgit v1.2.1 From a555a227dd7a846e5b8e8c84839120d40f9d1090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 25 Feb 2019 11:00:24 +0000 Subject: Merge branch 'filter-confidential-issues' into 'master' Ability to filter confidential issues Closes #50747 See merge request gitlab-org/gitlab-ce!24960 --- .../concerns/issuable_collections_spec.rb | 4 ++- .../issues/filtered_search/dropdown_hint_spec.rb | 11 ++++++- .../issues/filtered_search/search_bar_spec.rb | 2 +- spec/finders/issues_finder_spec.rb | 28 ++++++++++++++++- spec/models/issue_spec.rb | 9 ++++++ spec/requests/api/issues_spec.rb | 36 ++++++++++++++++++++++ 6 files changed, 86 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb index 307c5d60c57..8580900215c 100644 --- a/spec/controllers/concerns/issuable_collections_spec.rb +++ b/spec/controllers/concerns/issuable_collections_spec.rb @@ -112,7 +112,8 @@ describe IssuableCollections do assignee_username: 'user1', author_id: '2', author_username: 'user2', - authorized_only: 'true', + authorized_only: 'yes', + confidential: true, due_date: '2017-01-01', group_id: '3', iids: '4', @@ -140,6 +141,7 @@ describe IssuableCollections do 'assignee_username' => 'user1', 'author_id' => '2', 'author_username' => 'user2', + 'confidential' => true, 'label_name' => 'foo', 'milestone_title' => 'bar', 'my_reaction_emoji' => 'thumbsup', diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 0e296ab2109..096756f19cc 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -66,7 +66,7 @@ describe 'Dropdown hint', :js do it 'filters with text' do filtered_search.set('a') - expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4) + expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5) end end @@ -119,6 +119,15 @@ describe 'Dropdown hint', :js do expect_tokens([{ name: 'my-reaction' }]) expect_filtered_search_input_empty end + + it 'opens the yes-no dropdown when you click on confidential' do + click_hint('confidential') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-confidential', visible: true) + expect_tokens([{ name: 'confidential' }]) + expect_filtered_search_input_empty + end end describe 'selecting from dropdown with some input' do diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 891ef884682..da23aea1fc9 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -100,7 +100,7 @@ describe 'Search bar', :js do find('.filtered-search-box .clear-search').click filtered_search.click - expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5) + expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6) expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset) end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index fe8000e419b..47e2548c3d6 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -490,6 +490,32 @@ describe IssuesFinder do end end + context 'filtering by confidential' do + set(:confidential_issue) { create(:issue, project: project1, confidential: true) } + + context 'no filtering' do + it 'returns all issues' do + expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, confidential_issue) + end + end + + context 'user filters confidential issues' do + let(:params) { { confidential: true } } + + it 'returns only confdential issues' do + expect(issues).to contain_exactly(confidential_issue) + end + end + + context 'user filters only public issues' do + let(:params) { { confidential: false } } + + it 'returns only confdential issues' do + expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) + end + end + end + context 'when the user is unauthorized' do let(:search_user) { nil } @@ -556,7 +582,7 @@ describe IssuesFinder do it 'returns the number of rows for the default state' do finder = described_class.new(user) - expect(finder.row_count).to eq(4) + expect(finder.row_count).to eq(5) end it 'returns the number of rows for a given state' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 5d18e085a6f..6101df2e099 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -765,6 +765,15 @@ describe Issue do end end + describe '.confidential_only' do + it 'only returns confidential_only issues' do + create(:issue) + confidential_issue = create(:issue, confidential: true) + + expect(described_class.confidential_only).to eq([confidential_issue]) + end + end + it_behaves_like 'throttled touch' do subject { create(:issue, updated_at: 1.hour.ago) } end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index d10ee6cc320..1a4be2bd30f 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -183,6 +183,18 @@ describe API::Issues do expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end + it 'returns only confidential issues' do + get api('/issues', user), params: { confidential: true, scope: 'all' } + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns only public issues' do + get api('/issues', user), params: { confidential: false } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + it 'returns issues reacted by the authenticated user' do issue2 = create(:issue, project: project, author: user, assignees: [user]) create(:award_emoji, awardable: issue2, user: user2, name: 'star') @@ -557,6 +569,18 @@ describe API::Issues do expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end + it 'returns only confidential issues' do + get api(base_url, user), params: { confidential: true } + + expect_paginated_array_response(group_confidential_issue.id) + end + + it 'returns only public issues' do + get api(base_url, user), params: { confidential: false } + + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) + end + it 'returns an array of labeled group issues' do get api(base_url, user), params: { labels: group_label.title } @@ -782,6 +806,18 @@ describe API::Issues do expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end + it 'returns only confidential issues' do + get api("#{base_url}/issues", author), params: { confidential: true } + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns only public issues' do + get api("#{base_url}/issues", author), params: { confidential: false } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + it 'returns project confidential issues for assignee' do get api("#{base_url}/issues", assignee) -- cgit v1.2.1 From f49aeacdd0b20cc270423409efaf82b26ced4836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 25 Feb 2019 10:42:31 +0000 Subject: Merge branch 'fix-misspellings-app-comments' into 'master' Fix misspellings in app/spec comments See merge request gitlab-org/gitlab-ce!25517 --- spec/features/issues/gfm_autocomplete_spec.rb | 2 +- spec/features/projects/blobs/edit_spec.rb | 2 +- spec/features/projects/wiki/markdown_preview_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index c22ad0d20ef..986f3823275 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -278,7 +278,7 @@ describe 'GFM autocomplete', :js do end end - # This context has jsut one example in each contexts in order to improve spec performance. + # This context has just one example in each contexts in order to improve spec performance. context 'labels', :quarantine do let!(:backend) { create(:label, project: project, title: 'backend') } let!(:bug) { create(:label, project: project, title: 'bug') } diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 6e6c299ee2e..1522a3361a1 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -77,7 +77,7 @@ describe 'Editing file blob', :js do click_link 'Preview' wait_for_requests - # the above generates two seperate lists (not embedded) in CommonMark + # the above generates two separate lists (not embedded) in CommonMark expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 49244c53a91..49058d1372a 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -170,7 +170,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do fill_in :wiki_content, with: "1. one\n - sublist\n" click_on "Preview" - # the above generates two seperate lists (not embedded) in CommonMark + # the above generates two separate lists (not embedded) in CommonMark expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end -- cgit v1.2.1 From d19331833844b8f0bab46b4a0e5f1ae43b0eab0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 19 Feb 2019 17:33:49 +0100 Subject: Fix ETag caching not being used for AJAX requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/controllers/application_controller_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index c9e520317e8..dca74bd5f84 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -665,6 +665,14 @@ describe ApplicationController do expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store' end + + it 'does not set the "no-store" header for XHR requests' do + sign_in(user) + + get :index, xhr: true + + expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate' + end end end end -- cgit v1.2.1 From e7e5efd1319c1eb11e52e33f12f4c25c07682dc0 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 21 Feb 2019 14:37:36 -0800 Subject: Fix Ci::Pipeline#all_merge_requests missing forked MRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ci::Pipeline#all_merge_requests was only returning merge requests for that project, but it should include merge requests from forks as well. Signed-off-by: Rémy Coutable --- spec/models/ci/pipeline_spec.rb | 97 ++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 40 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b9567ab4d65..7ea701dd035 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Ci::Pipeline, :mailer do + include ProjectForksHelper + let(:user) { create(:user) } set(:project) { create(:project) } @@ -2114,66 +2116,81 @@ describe Ci::Pipeline, :mailer do describe "#all_merge_requests" do let(:project) { create(:project) } - let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master') } - it "returns all merge requests having the same source branch" do - merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref) + shared_examples 'a method that returns all merge requests for a given pipeline' do + let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: pipeline_project, ref: 'master') } - expect(pipeline.all_merge_requests).to eq([merge_request]) - end + it "returns all merge requests having the same source branch" do + merge_request = create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: pipeline.ref) - it "doesn't return merge requests having a different source branch" do - create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') - - expect(pipeline.all_merge_requests).to be_empty - end - - context 'when there is a merge request pipeline' do - let(:source_branch) { 'feature' } - let(:target_branch) { 'master' } - - let!(:pipeline) do - create(:ci_pipeline, - source: :merge_request, - project: project, - ref: source_branch, - merge_request: merge_request) + expect(pipeline.all_merge_requests).to eq([merge_request]) end - let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: source_branch, - target_project: project, - target_branch: target_branch) - end + it "doesn't return merge requests having a different source branch" do + create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: 'feature', target_branch: 'master') - it 'returns an associated merge request' do - expect(pipeline.all_merge_requests).to eq([merge_request]) + expect(pipeline.all_merge_requests).to be_empty end - context 'when there is another merge request pipeline that targets a different branch' do - let(:target_branch_2) { 'merge-test' } + context 'when there is a merge request pipeline' do + let(:source_branch) { 'feature' } + let(:target_branch) { 'master' } - let!(:pipeline_2) do + let!(:pipeline) do create(:ci_pipeline, source: :merge_request, - project: project, + project: pipeline_project, ref: source_branch, - merge_request: merge_request_2) + merge_request: merge_request) end - let(:merge_request_2) do + let(:merge_request) do create(:merge_request, - source_project: project, + source_project: pipeline_project, source_branch: source_branch, target_project: project, - target_branch: target_branch_2) + target_branch: target_branch) end - it 'does not return an associated merge request' do - expect(pipeline.all_merge_requests).not_to include(merge_request_2) + it 'returns an associated merge request' do + expect(pipeline.all_merge_requests).to eq([merge_request]) end + + context 'when there is another merge request pipeline that targets a different branch' do + let(:target_branch_2) { 'merge-test' } + + let!(:pipeline_2) do + create(:ci_pipeline, + source: :merge_request, + project: pipeline_project, + ref: source_branch, + merge_request: merge_request_2) + end + + let(:merge_request_2) do + create(:merge_request, + source_project: pipeline_project, + source_branch: source_branch, + target_project: project, + target_branch: target_branch_2) + end + + it 'does not return an associated merge request' do + expect(pipeline.all_merge_requests).not_to include(merge_request_2) + end + end + end + end + + it_behaves_like 'a method that returns all merge requests for a given pipeline' do + let(:pipeline_project) { project } + end + + context 'for a fork' do + let(:fork) { fork_project(project) } + + it_behaves_like 'a method that returns all merge requests for a given pipeline' do + let(:pipeline_project) { fork } end end end -- cgit v1.2.1 From e9b28a28a7494500f0295b87cf9f3389d48a8c30 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Feb 2019 17:29:22 +0000 Subject: Makes the Web IDE the default editor Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/42382 --- spec/features/projects/blobs/edit_spec.rb | 4 ++++ spec/features/projects/files/user_creates_files_spec.rb | 2 ++ spec/features/projects/files/user_edits_files_spec.rb | 2 ++ spec/helpers/blob_helper_spec.rb | 8 ++++++++ 4 files changed, 16 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 6e6c299ee2e..a8e9d951d5e 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -9,6 +9,10 @@ describe 'Editing file blob', :js do let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] } let(:readme_file_path) { 'README.md' } + before do + stub_feature_flags(web_ide_default: false) + end + context 'as a developer' do let(:user) { create(:user) } let(:role) { :developer } diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb index a4f94b7a76d..dd2964c2186 100644 --- a/spec/features/projects/files/user_creates_files_spec.rb +++ b/spec/features/projects/files/user_creates_files_spec.rb @@ -12,6 +12,8 @@ describe 'Projects > Files > User creates files' do let(:user) { create(:user) } before do + stub_feature_flags(web_ide_default: false) + project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index 9eb65ec159c..ec3930c26db 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -9,6 +9,8 @@ describe 'Projects > Files > User edits files', :js do let(:user) { create(:user) } before do + stub_feature_flags(web_ide_default: false) + sign_in(user) end diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index f709f152c92..2bc3933809f 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -50,12 +50,20 @@ describe BlobHelper do end it 'returns a link with the proper route' do + stub_feature_flags(web_ide_default: false) link = edit_blob_button(project, 'master', 'README.md') expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md") end + it 'returns a link with a Web IDE route' do + link = edit_blob_button(project, 'master', 'README.md') + + expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md") + end + it 'returns a link with the passed link_opts on the expected route' do + stub_feature_flags(web_ide_default: false) link = edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 }) expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10") -- cgit v1.2.1 From 224417e2b41b57ba9c0f1c6d14c1fefdb1410073 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 26 Feb 2019 12:26:15 +0000 Subject: Added GraphQL Jest transform --- spec/frontend/__mocks__/file_mock.js | 1 + .../issuable_suggestions/components/app_spec.js | 96 ++++++++++++++ .../issuable_suggestions/components/item_spec.js | 139 +++++++++++++++++++++ spec/frontend/issuable_suggestions/mock_data.js | 26 ++++ .../issuable_suggestions/components/app_spec.js | 96 -------------- .../issuable_suggestions/components/item_spec.js | 139 --------------------- spec/javascripts/issuable_suggestions/mock_data.js | 26 ---- 7 files changed, 262 insertions(+), 261 deletions(-) create mode 100644 spec/frontend/__mocks__/file_mock.js create mode 100644 spec/frontend/issuable_suggestions/components/app_spec.js create mode 100644 spec/frontend/issuable_suggestions/components/item_spec.js create mode 100644 spec/frontend/issuable_suggestions/mock_data.js delete mode 100644 spec/javascripts/issuable_suggestions/components/app_spec.js delete mode 100644 spec/javascripts/issuable_suggestions/components/item_spec.js delete mode 100644 spec/javascripts/issuable_suggestions/mock_data.js (limited to 'spec') diff --git a/spec/frontend/__mocks__/file_mock.js b/spec/frontend/__mocks__/file_mock.js new file mode 100644 index 00000000000..08d725cd4e4 --- /dev/null +++ b/spec/frontend/__mocks__/file_mock.js @@ -0,0 +1 @@ +export default ''; diff --git a/spec/frontend/issuable_suggestions/components/app_spec.js b/spec/frontend/issuable_suggestions/components/app_spec.js new file mode 100644 index 00000000000..7bb8e26b81a --- /dev/null +++ b/spec/frontend/issuable_suggestions/components/app_spec.js @@ -0,0 +1,96 @@ +import { shallowMount } from '@vue/test-utils'; +import App from '~/issuable_suggestions/components/app.vue'; +import Suggestion from '~/issuable_suggestions/components/item.vue'; + +describe('Issuable suggestions app component', () => { + let vm; + + function createComponent(search = 'search') { + vm = shallowMount(App, { + propsData: { + search, + projectPath: 'project', + }, + }); + } + + afterEach(() => { + vm.destroy(); + }); + + it('does not render with empty search', () => { + createComponent(''); + + expect(vm.isVisible()).toBe(false); + }); + + describe('with data', () => { + let data; + + beforeEach(() => { + data = { issues: [{ id: 1 }, { id: 2 }] }; + }); + + it('renders component', () => { + createComponent(); + vm.setData(data); + + expect(vm.isEmpty()).toBe(false); + }); + + it('does not render with empty search', () => { + createComponent(''); + vm.setData(data); + + expect(vm.isVisible()).toBe(false); + }); + + it('does not render when loading', () => { + createComponent(); + vm.setData({ + ...data, + loading: 1, + }); + + expect(vm.isVisible()).toBe(false); + }); + + it('does not render with empty issues data', () => { + createComponent(); + vm.setData({ issues: [] }); + + expect(vm.isVisible()).toBe(false); + }); + + it('renders list of issues', () => { + createComponent(); + vm.setData(data); + + expect(vm.findAll(Suggestion).length).toBe(2); + }); + + it('adds margin class to first item', () => { + createComponent(); + vm.setData(data); + + expect( + vm + .findAll('li') + .at(0) + .is('.append-bottom-default'), + ).toBe(true); + }); + + it('does not add margin class to last item', () => { + createComponent(); + vm.setData(data); + + expect( + vm + .findAll('li') + .at(1) + .is('.append-bottom-default'), + ).toBe(false); + }); + }); +}); diff --git a/spec/frontend/issuable_suggestions/components/item_spec.js b/spec/frontend/issuable_suggestions/components/item_spec.js new file mode 100644 index 00000000000..7bd1fe678f4 --- /dev/null +++ b/spec/frontend/issuable_suggestions/components/item_spec.js @@ -0,0 +1,139 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlTooltip, GlLink } from '@gitlab/ui'; +import Icon from '~/vue_shared/components/icon.vue'; +import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; +import Suggestion from '~/issuable_suggestions/components/item.vue'; +import mockData from '../mock_data'; + +describe('Issuable suggestions suggestion component', () => { + let vm; + + function createComponent(suggestion = {}) { + vm = shallowMount(Suggestion, { + propsData: { + suggestion: { + ...mockData(), + ...suggestion, + }, + }, + }); + } + + afterEach(() => { + vm.destroy(); + }); + + it('renders title', () => { + createComponent(); + + expect(vm.text()).toContain('Test issue'); + }); + + it('renders issue link', () => { + createComponent(); + + const link = vm.find(GlLink); + + expect(link.attributes('href')).toBe(`${gl.TEST_HOST}/test/issue/1`); + }); + + it('renders IID', () => { + createComponent(); + + expect(vm.text()).toContain('#1'); + }); + + describe('opened state', () => { + it('renders icon', () => { + createComponent(); + + const icon = vm.find(Icon); + + expect(icon.props('name')).toBe('issue-open-m'); + }); + + it('renders created timeago', () => { + createComponent({ + closedAt: '', + }); + + const tooltip = vm.find(GlTooltip); + + expect(tooltip.find('.d-block').text()).toContain('Opened'); + expect(tooltip.text()).toContain('3 days ago'); + }); + }); + + describe('closed state', () => { + it('renders icon', () => { + createComponent({ + state: 'closed', + }); + + const icon = vm.find(Icon); + + expect(icon.props('name')).toBe('issue-close'); + }); + + it('renders closed timeago', () => { + createComponent(); + + const tooltip = vm.find(GlTooltip); + + expect(tooltip.find('.d-block').text()).toContain('Opened'); + expect(tooltip.text()).toContain('1 day ago'); + }); + }); + + describe('author', () => { + it('renders author info', () => { + createComponent(); + + const link = vm.findAll(GlLink).at(1); + + expect(link.text()).toContain('Author Name'); + expect(link.text()).toContain('@author.username'); + }); + + it('renders author image', () => { + createComponent(); + + const image = vm.find(UserAvatarImage); + + expect(image.props('imgSrc')).toBe(`${gl.TEST_HOST}/avatar`); + }); + }); + + describe('counts', () => { + it('renders upvotes count', () => { + createComponent(); + + const count = vm.findAll('.suggestion-counts span').at(0); + + expect(count.text()).toContain('1'); + expect(count.find(Icon).props('name')).toBe('thumb-up'); + }); + + it('renders notes count', () => { + createComponent(); + + const count = vm.findAll('.suggestion-counts span').at(1); + + expect(count.text()).toContain('2'); + expect(count.find(Icon).props('name')).toBe('comment'); + }); + }); + + describe('confidential', () => { + it('renders confidential icon', () => { + createComponent({ + confidential: true, + }); + + const icon = vm.find(Icon); + + expect(icon.props('name')).toBe('eye-slash'); + expect(icon.attributes('data-original-title')).toBe('Confidential'); + }); + }); +}); diff --git a/spec/frontend/issuable_suggestions/mock_data.js b/spec/frontend/issuable_suggestions/mock_data.js new file mode 100644 index 00000000000..4f0f9ef8d62 --- /dev/null +++ b/spec/frontend/issuable_suggestions/mock_data.js @@ -0,0 +1,26 @@ +function getDate(daysMinus) { + const today = new Date(); + today.setDate(today.getDate() - daysMinus); + + return today.toISOString(); +} + +export default () => ({ + id: 1, + iid: 1, + state: 'opened', + upvotes: 1, + userNotesCount: 2, + closedAt: getDate(1), + createdAt: getDate(3), + updatedAt: getDate(2), + confidential: false, + webUrl: `${gl.TEST_HOST}/test/issue/1`, + title: 'Test issue', + author: { + avatarUrl: `${gl.TEST_HOST}/avatar`, + name: 'Author Name', + username: 'author.username', + webUrl: `${gl.TEST_HOST}/author`, + }, +}); diff --git a/spec/javascripts/issuable_suggestions/components/app_spec.js b/spec/javascripts/issuable_suggestions/components/app_spec.js deleted file mode 100644 index 7bb8e26b81a..00000000000 --- a/spec/javascripts/issuable_suggestions/components/app_spec.js +++ /dev/null @@ -1,96 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import App from '~/issuable_suggestions/components/app.vue'; -import Suggestion from '~/issuable_suggestions/components/item.vue'; - -describe('Issuable suggestions app component', () => { - let vm; - - function createComponent(search = 'search') { - vm = shallowMount(App, { - propsData: { - search, - projectPath: 'project', - }, - }); - } - - afterEach(() => { - vm.destroy(); - }); - - it('does not render with empty search', () => { - createComponent(''); - - expect(vm.isVisible()).toBe(false); - }); - - describe('with data', () => { - let data; - - beforeEach(() => { - data = { issues: [{ id: 1 }, { id: 2 }] }; - }); - - it('renders component', () => { - createComponent(); - vm.setData(data); - - expect(vm.isEmpty()).toBe(false); - }); - - it('does not render with empty search', () => { - createComponent(''); - vm.setData(data); - - expect(vm.isVisible()).toBe(false); - }); - - it('does not render when loading', () => { - createComponent(); - vm.setData({ - ...data, - loading: 1, - }); - - expect(vm.isVisible()).toBe(false); - }); - - it('does not render with empty issues data', () => { - createComponent(); - vm.setData({ issues: [] }); - - expect(vm.isVisible()).toBe(false); - }); - - it('renders list of issues', () => { - createComponent(); - vm.setData(data); - - expect(vm.findAll(Suggestion).length).toBe(2); - }); - - it('adds margin class to first item', () => { - createComponent(); - vm.setData(data); - - expect( - vm - .findAll('li') - .at(0) - .is('.append-bottom-default'), - ).toBe(true); - }); - - it('does not add margin class to last item', () => { - createComponent(); - vm.setData(data); - - expect( - vm - .findAll('li') - .at(1) - .is('.append-bottom-default'), - ).toBe(false); - }); - }); -}); diff --git a/spec/javascripts/issuable_suggestions/components/item_spec.js b/spec/javascripts/issuable_suggestions/components/item_spec.js deleted file mode 100644 index 7bd1fe678f4..00000000000 --- a/spec/javascripts/issuable_suggestions/components/item_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlTooltip, GlLink } from '@gitlab/ui'; -import Icon from '~/vue_shared/components/icon.vue'; -import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; -import Suggestion from '~/issuable_suggestions/components/item.vue'; -import mockData from '../mock_data'; - -describe('Issuable suggestions suggestion component', () => { - let vm; - - function createComponent(suggestion = {}) { - vm = shallowMount(Suggestion, { - propsData: { - suggestion: { - ...mockData(), - ...suggestion, - }, - }, - }); - } - - afterEach(() => { - vm.destroy(); - }); - - it('renders title', () => { - createComponent(); - - expect(vm.text()).toContain('Test issue'); - }); - - it('renders issue link', () => { - createComponent(); - - const link = vm.find(GlLink); - - expect(link.attributes('href')).toBe(`${gl.TEST_HOST}/test/issue/1`); - }); - - it('renders IID', () => { - createComponent(); - - expect(vm.text()).toContain('#1'); - }); - - describe('opened state', () => { - it('renders icon', () => { - createComponent(); - - const icon = vm.find(Icon); - - expect(icon.props('name')).toBe('issue-open-m'); - }); - - it('renders created timeago', () => { - createComponent({ - closedAt: '', - }); - - const tooltip = vm.find(GlTooltip); - - expect(tooltip.find('.d-block').text()).toContain('Opened'); - expect(tooltip.text()).toContain('3 days ago'); - }); - }); - - describe('closed state', () => { - it('renders icon', () => { - createComponent({ - state: 'closed', - }); - - const icon = vm.find(Icon); - - expect(icon.props('name')).toBe('issue-close'); - }); - - it('renders closed timeago', () => { - createComponent(); - - const tooltip = vm.find(GlTooltip); - - expect(tooltip.find('.d-block').text()).toContain('Opened'); - expect(tooltip.text()).toContain('1 day ago'); - }); - }); - - describe('author', () => { - it('renders author info', () => { - createComponent(); - - const link = vm.findAll(GlLink).at(1); - - expect(link.text()).toContain('Author Name'); - expect(link.text()).toContain('@author.username'); - }); - - it('renders author image', () => { - createComponent(); - - const image = vm.find(UserAvatarImage); - - expect(image.props('imgSrc')).toBe(`${gl.TEST_HOST}/avatar`); - }); - }); - - describe('counts', () => { - it('renders upvotes count', () => { - createComponent(); - - const count = vm.findAll('.suggestion-counts span').at(0); - - expect(count.text()).toContain('1'); - expect(count.find(Icon).props('name')).toBe('thumb-up'); - }); - - it('renders notes count', () => { - createComponent(); - - const count = vm.findAll('.suggestion-counts span').at(1); - - expect(count.text()).toContain('2'); - expect(count.find(Icon).props('name')).toBe('comment'); - }); - }); - - describe('confidential', () => { - it('renders confidential icon', () => { - createComponent({ - confidential: true, - }); - - const icon = vm.find(Icon); - - expect(icon.props('name')).toBe('eye-slash'); - expect(icon.attributes('data-original-title')).toBe('Confidential'); - }); - }); -}); diff --git a/spec/javascripts/issuable_suggestions/mock_data.js b/spec/javascripts/issuable_suggestions/mock_data.js deleted file mode 100644 index 4f0f9ef8d62..00000000000 --- a/spec/javascripts/issuable_suggestions/mock_data.js +++ /dev/null @@ -1,26 +0,0 @@ -function getDate(daysMinus) { - const today = new Date(); - today.setDate(today.getDate() - daysMinus); - - return today.toISOString(); -} - -export default () => ({ - id: 1, - iid: 1, - state: 'opened', - upvotes: 1, - userNotesCount: 2, - closedAt: getDate(1), - createdAt: getDate(3), - updatedAt: getDate(2), - confidential: false, - webUrl: `${gl.TEST_HOST}/test/issue/1`, - title: 'Test issue', - author: { - avatarUrl: `${gl.TEST_HOST}/avatar`, - name: 'Author Name', - username: 'author.username', - webUrl: `${gl.TEST_HOST}/author`, - }, -}); -- cgit v1.2.1 From c9125c4786970fc3c3745c7ea7bff45ef7a62e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 26 Feb 2019 14:03:42 +0000 Subject: Add Maskable concern for CI variables This adds a concern that abstracts the concept of masking a variable, including the RegEx for validation. --- spec/factories/ci/group_variables.rb | 1 + spec/factories/ci/variables.rb | 1 + spec/features/group_variables_spec.rb | 2 +- spec/features/project_variables_spec.rb | 2 +- .../gitlab/ci/variables/collection/item_spec.rb | 20 ++- spec/lib/gitlab/ci/variables/collection_spec.rb | 4 +- spec/models/ci/build_spec.rb | 176 ++++++++++----------- spec/models/ci/group_variable_spec.rb | 1 + spec/models/ci/variable_spec.rb | 1 + spec/models/concerns/has_variable_spec.rb | 2 +- spec/models/concerns/maskable_spec.rb | 76 +++++++++ spec/requests/api/group_variables_spec.rb | 4 +- spec/requests/api/runner_spec.rb | 18 +-- spec/requests/api/variables_spec.rb | 4 +- .../features/variable_list_shared_examples.rb | 8 +- 15 files changed, 207 insertions(+), 113 deletions(-) create mode 100644 spec/models/concerns/maskable_spec.rb (limited to 'spec') diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb index 64716842b12..9bf520a2c0a 100644 --- a/spec/factories/ci/group_variables.rb +++ b/spec/factories/ci/group_variables.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :ci_group_variable, class: Ci::GroupVariable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' + masked false trait(:protected) do protected true diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index 3d014b9b54f..97a7c9ba252 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :ci_variable, class: Ci::Variable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' + masked false trait(:protected) do protected true diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 57e3ddfb39c..1a53e7c9512 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test value', group: group) } + let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) } let(:page_path) { group_settings_ci_cd_path(group) } before do diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index a93df3696d2..6bdf5df1036 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Project variables', :js do let(:user) { create(:user) } let(:project) { create(:project) } - let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } + let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') } let(:page_path) { project_settings_ci_cd_path(project) } before do diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index 8bf44acb228..3ff2fe18c15 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Variables::Collection::Item do let(:expected_value) { variable_value } let(:variable) do - { key: variable_key, value: variable_value, public: true } + { key: variable_key, value: variable_value, public: true, masked: false } end describe '.new' do @@ -88,7 +88,7 @@ describe Gitlab::Ci::Variables::Collection::Item do resource = described_class.fabricate(variable) expect(resource).to be_a(described_class) - expect(resource).to eq(key: 'CI_VAR', value: '123', public: false) + expect(resource).to eq(key: 'CI_VAR', value: '123', public: false, masked: false) end it 'supports using another collection item' do @@ -134,7 +134,21 @@ describe Gitlab::Ci::Variables::Collection::Item do .to_runner_variable expect(runner_variable) - .to eq(key: 'VAR', value: 'value', public: true, file: true) + .to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false) + end + end + + context 'when variable masking is disabled' do + before do + stub_feature_flags(variable_masking: false) + end + + it 'does not expose the masked field to the runner' do + runner_variable = described_class + .new(key: 'VAR', value: 'value', masked: true) + .to_runner_variable + + expect(runner_variable).to eq(key: 'VAR', value: 'value', public: true) end end end diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index 5c91816a586..edb209c0cf4 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Variables::Collection do describe '.new' do it 'can be initialized with an array' do - variable = { key: 'VAR', value: 'value', public: true } + variable = { key: 'VAR', value: 'value', public: true, masked: false } collection = described_class.new([variable]) @@ -93,7 +93,7 @@ describe Gitlab::Ci::Variables::Collection do collection = described_class.new([{ key: 'TEST', value: '1' }]) expect(collection.to_runner_variables) - .to eq [{ key: 'TEST', value: '1', public: true }] + .to eq [{ key: 'TEST', value: '1', public: true, masked: false }] end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 17540443688..81ff727b458 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2114,55 +2114,55 @@ describe Ci::Build do context 'returns variables' do let(:predefined_variables) do [ - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, - { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true }, - { key: 'CI_JOB_ID', value: build.id.to_s, public: true }, - { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true }, - { key: 'CI_JOB_TOKEN', value: 'my-token', public: false }, - { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, - { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false }, - { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, - { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false }, - { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }, - { key: 'CI', value: 'true', public: true }, - { key: 'GITLAB_CI', value: 'true', public: true }, - { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true }, - { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, - { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, - { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true }, - { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true }, - { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true }, - { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true }, - { key: 'CI_JOB_NAME', value: 'test', public: true }, - { key: 'CI_JOB_STAGE', value: 'test', public: true }, - { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, - { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true }, - { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true }, - { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true }, - { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, - { key: 'CI_NODE_TOTAL', value: '1', public: true }, - { key: 'CI_BUILD_REF', value: build.sha, public: true }, - { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, - { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true }, - { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true }, - { key: 'CI_BUILD_NAME', value: 'test', public: true }, - { key: 'CI_BUILD_STAGE', value: 'test', public: true }, - { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true }, - { key: 'CI_PROJECT_NAME', value: project.path, public: true }, - { key: 'CI_PROJECT_PATH', value: project.full_path, public: true }, - { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true }, - { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, - { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, - { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, - { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true }, - { key: 'CI_PAGES_URL', value: project.pages_url, public: true }, - { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true }, - { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true }, - { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, - { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }, - { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true }, - { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true }, - { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true } + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false }, + { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true, masked: false }, + { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false }, + { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false }, + { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: false }, + { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false }, + { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: false }, + { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false }, + { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: false }, + { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false }, + { key: 'CI', value: 'true', public: true, masked: false }, + { key: 'GITLAB_CI', value: 'true', public: true, masked: false }, + { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false }, + { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false }, + { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false }, + { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false }, + { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false }, + { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false }, + { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false }, + { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false }, + { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false }, + { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false }, + { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false }, + { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, + { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false }, + { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false }, + { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false }, + { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false }, + { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, + { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false }, + { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false }, + { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false }, + { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false }, + { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false }, + { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false }, + { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false }, + { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false }, + { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false }, + { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false }, + { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false }, + { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false }, + { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false }, + { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false }, + { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false }, + { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true, masked: false }, + { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false }, + { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, + { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, + { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false } ] end @@ -2175,10 +2175,10 @@ describe Ci::Build do describe 'variables ordering' do context 'when variables hierarchy is stubbed' do - let(:build_pre_var) { { key: 'build', value: 'value', public: true } } - let(:project_pre_var) { { key: 'project', value: 'value', public: true } } - let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true } } - let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true } } + let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } } + let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } } + let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } } + let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } } before do allow(build).to receive(:predefined_variables) { [build_pre_var] } @@ -2200,7 +2200,7 @@ describe Ci::Build do project_pre_var, pipeline_pre_var, build_yaml_var, - { key: 'secret', value: 'value', public: false }]) + { key: 'secret', value: 'value', public: false, masked: false }]) end end @@ -2233,10 +2233,10 @@ describe Ci::Build do context 'when build has user' do let(:user_variables) do [ - { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, - { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }, - { key: 'GITLAB_USER_LOGIN', value: user.username, public: true }, - { key: 'GITLAB_USER_NAME', value: user.name, public: true } + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true, masked: false }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true, masked: false }, + { key: 'GITLAB_USER_LOGIN', value: user.username, public: true, masked: false }, + { key: 'GITLAB_USER_NAME', value: user.name, public: true, masked: false } ] end @@ -2250,8 +2250,8 @@ describe Ci::Build do context 'when build has an environment' do let(:environment_variables) do [ - { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true }, - { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true } + { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false }, + { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false } ] end @@ -2286,7 +2286,7 @@ describe Ci::Build do before do environment_variables << - { key: 'CI_ENVIRONMENT_URL', value: url, public: true } + { key: 'CI_ENVIRONMENT_URL', value: url, public: true, masked: false } end context 'when the URL was set from the job' do @@ -2323,7 +2323,7 @@ describe Ci::Build do end let(:manual_variable) do - { key: 'CI_JOB_MANUAL', value: 'true', public: true } + { key: 'CI_JOB_MANUAL', value: 'true', public: true, masked: false } end it { is_expected.to include(manual_variable) } @@ -2331,7 +2331,7 @@ describe Ci::Build do context 'when build is for tag' do let(:tag_variable) do - { key: 'CI_COMMIT_TAG', value: 'master', public: true } + { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false } end before do @@ -2343,7 +2343,7 @@ describe Ci::Build do context 'when CI variable is defined' do let(:ci_variable) do - { key: 'SECRET_KEY', value: 'secret_value', public: false } + { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } end before do @@ -2358,7 +2358,7 @@ describe Ci::Build do let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } let(:protected_variable) do - { key: 'PROTECTED_KEY', value: 'protected_value', public: false } + { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } end before do @@ -2390,7 +2390,7 @@ describe Ci::Build do context 'when group CI variable is defined' do let(:ci_variable) do - { key: 'SECRET_KEY', value: 'secret_value', public: false } + { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } end before do @@ -2405,7 +2405,7 @@ describe Ci::Build do let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } let(:protected_variable) do - { key: 'PROTECTED_KEY', value: 'protected_value', public: false } + { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } end before do @@ -2444,11 +2444,11 @@ describe Ci::Build do let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) } let(:user_trigger_variable) do - { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false } + { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false } end let(:predefined_trigger_variable) do - { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true } + { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false } end before do @@ -2480,7 +2480,7 @@ describe Ci::Build do context 'when pipeline has a variable' do let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) } - it { is_expected.to include(pipeline_variable.to_runner_variable) } + it { is_expected.to include(key: pipeline_variable.key, value: pipeline_variable.value, public: false, masked: false) } end context 'when a job was triggered by a pipeline schedule' do @@ -2497,16 +2497,16 @@ describe Ci::Build do pipeline_schedule.reload end - it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) } + it { is_expected.to include(key: pipeline_schedule_variable.key, value: pipeline_schedule_variable.value, public: false, masked: false) } end context 'when container registry is enabled' do let(:container_registry_enabled) { true } let(:ci_registry) do - { key: 'CI_REGISTRY', value: 'registry.example.com', public: true } + { key: 'CI_REGISTRY', value: 'registry.example.com', public: true, masked: false } end let(:ci_registry_image) do - { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true } + { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true, masked: false } end context 'and is disabled for project' do @@ -2535,13 +2535,13 @@ describe Ci::Build do build.update(runner: runner) end - it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) } - it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) } - it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) } + it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true, masked: false }) } + it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true, masked: false }) } end context 'when build is for a deployment' do - let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } } + let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false, masked: false } } before do build.environment = 'production' @@ -2555,7 +2555,7 @@ describe Ci::Build do end context 'when project has custom CI config path' do - let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } } + let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } } before do project.update(ci_config_path: 'custom') @@ -2572,7 +2572,7 @@ describe Ci::Build do it "includes AUTO_DEVOPS_DOMAIN" do is_expected.to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }) + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) end end @@ -2583,7 +2583,7 @@ describe Ci::Build do it "includes AUTO_DEVOPS_DOMAIN" do is_expected.not_to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }) + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) end end end @@ -2598,9 +2598,9 @@ describe Ci::Build do variables = subject.reverse.uniq { |variable| variable[:key] }.reverse expect(variables) - .not_to include(key: 'MYVAR', value: 'myvar', public: true) + .not_to include(key: 'MYVAR', value: 'myvar', public: true, masked: false) expect(variables) - .to include(key: 'MYVAR', value: 'pipeline value', public: false) + .to include(key: 'MYVAR', value: 'pipeline value', public: false, masked: false) end end @@ -2616,13 +2616,13 @@ describe Ci::Build do it 'includes CI_NODE_INDEX' do is_expected.to include( - { key: 'CI_NODE_INDEX', value: index.to_s, public: true } + { key: 'CI_NODE_INDEX', value: index.to_s, public: true, masked: false } ) end it 'includes correct CI_NODE_TOTAL' do is_expected.to include( - { key: 'CI_NODE_TOTAL', value: total.to_s, public: true } + { key: 'CI_NODE_TOTAL', value: total.to_s, public: true, masked: false } ) end end @@ -2641,7 +2641,7 @@ describe Ci::Build do it 'returns static predefined variables' do expect(build.variables.size).to be >= 28 expect(build.variables) - .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true) + .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) expect(build).not_to be_persisted end end @@ -2651,8 +2651,8 @@ describe Ci::Build do let(:deploy_token_variables) do [ - { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true }, - { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false } + { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true, masked: false }, + { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false, masked: false } ] end @@ -2711,7 +2711,7 @@ describe Ci::Build do end expect(variables) - .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true) + .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) end it 'does not return prohibited variables' do diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index 1b10501701c..21d96bf3454 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -5,6 +5,7 @@ describe Ci::GroupVariable do it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Presentable) } + it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) } describe '.unprotected' do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 875e8b2b682..02c07a2bd83 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -6,6 +6,7 @@ describe Ci::Variable do describe 'validations' do it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Presentable) } + it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) } end diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb index 3fbe86c5b56..bff96e12ffa 100644 --- a/spec/models/concerns/has_variable_spec.rb +++ b/spec/models/concerns/has_variable_spec.rb @@ -57,7 +57,7 @@ describe HasVariable do describe '#to_runner_variable' do it 'returns a hash for the runner' do expect(subject.to_runner_variable) - .to eq(key: subject.key, value: subject.value, public: false) + .to include(key: subject.key, value: subject.value, public: false) end end end diff --git a/spec/models/concerns/maskable_spec.rb b/spec/models/concerns/maskable_spec.rb new file mode 100644 index 00000000000..aeba7ad862f --- /dev/null +++ b/spec/models/concerns/maskable_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Maskable do + let(:variable) { build(:ci_variable) } + + describe 'masked value validations' do + subject { variable } + + context 'when variable is masked' do + before do + subject.masked = true + end + + it { is_expected.not_to allow_value('hello').for(:value) } + it { is_expected.not_to allow_value('hello world').for(:value) } + it { is_expected.not_to allow_value('hello$VARIABLEworld').for(:value) } + it { is_expected.not_to allow_value('hello\rworld').for(:value) } + it { is_expected.to allow_value('helloworld').for(:value) } + end + + context 'when variable is not masked' do + before do + subject.masked = false + end + + it { is_expected.to allow_value('hello').for(:value) } + it { is_expected.to allow_value('hello world').for(:value) } + it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) } + it { is_expected.to allow_value('hello\rworld').for(:value) } + it { is_expected.to allow_value('helloworld').for(:value) } + end + end + + describe 'REGEX' do + subject { Maskable::REGEX } + + it 'does not match strings shorter than 8 letters' do + expect(subject.match?('hello')).to eq(false) + end + + it 'does not match strings with spaces' do + expect(subject.match?('hello world')).to eq(false) + end + + it 'does not match strings with shell variables' do + expect(subject.match?('hello$VARIABLEworld')).to eq(false) + end + + it 'does not match strings with escape characters' do + expect(subject.match?('hello\rworld')).to eq(false) + end + + it 'does not match strings that span more than one line' do + string = <<~EOS + hello + world + EOS + + expect(subject.match?(string)).to eq(false) + end + + it 'matches valid strings' do + expect(subject.match?('helloworld')).to eq(true) + end + end + + describe '#to_runner_variable' do + subject { variable.to_runner_variable } + + it 'exposes the masked attribute' do + expect(subject).to include(:masked) + end + end +end diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index e52f4c70407..66b9aae4b58 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -87,12 +87,12 @@ describe API::GroupVariables do it 'creates variable' do expect do - post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } + post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true } end.to change {group.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') - expect(json_response['value']).to eq('VALUE_2') + expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index e6c235ca26e..43c06f7c973 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -436,9 +436,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end let(:expected_variables) do - [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, - { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, - { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }] + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false }, + { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }] end let(:expected_artifacts) do @@ -740,12 +740,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when triggered job is available' do let(:expected_variables) do - [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, - { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, - { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true }, - { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }, - { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false }, - { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }] + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false }, + { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true, 'masked' => false }, + { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }, + { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false, 'masked' => false }, + { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false, 'masked' => false }] end let(:trigger) { create(:ci_trigger, project: project) } diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index cdac5b2f400..5df6baf0ddf 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -73,12 +73,12 @@ describe API::Variables do context 'authorized user with proper permissions' do it 'creates variable' do expect do - post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } + post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true } end.to change {project.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') - expect(json_response['value']).to eq('VALUE_2') + expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy end diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 0a464d77cb7..73156d18c1b 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -8,7 +8,7 @@ shared_examples 'variable list' do it 'adds new CI variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('key value') + find('.js-ci-variable-input-value').set('key_value') end click_button('Save variables') @@ -19,7 +19,7 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') end end @@ -44,7 +44,7 @@ shared_examples 'variable list' do it 'adds new protected variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('key value') + find('.js-ci-variable-input-value').set('key_value') find('.ci-variable-protected-item .js-project-feature-toggle').click expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -58,7 +58,7 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end end -- cgit v1.2.1 From 2dd22ecb4601b546b630d1bdd7203dc4d87959e7 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Tue, 26 Feb 2019 14:04:15 +0000 Subject: Add support for toggling discussion filter from notes section Adds discussion note style section under notes app from where user can toggle discussion when they have selected a filter to show only system notes. --- .../components/discussion_filter_note_spec.js | 93 ++++++++++++++++++++++ .../notes/components/discussion_filter_spec.js | 22 +++-- spec/javascripts/notes/components/note_app_spec.js | 7 ++ 3 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 spec/javascripts/notes/components/discussion_filter_note_spec.js (limited to 'spec') diff --git a/spec/javascripts/notes/components/discussion_filter_note_spec.js b/spec/javascripts/notes/components/discussion_filter_note_spec.js new file mode 100644 index 00000000000..52d2e7ce947 --- /dev/null +++ b/spec/javascripts/notes/components/discussion_filter_note_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue'; +import eventHub from '~/notes/event_hub'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('DiscussionFilterNote component', () => { + let vm; + + const createComponent = () => { + const Component = Vue.extend(DiscussionFilterNote); + + return mountComponent(Component); + }; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('timelineContent', () => { + it('returns string containing instruction for switching feed type', () => { + expect(vm.timelineContent).toBe( + "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.", + ); + }); + }); + }); + + describe('methods', () => { + describe('selectFilter', () => { + it('emits `dropdownSelect` event on `eventHub` with provided param', () => { + spyOn(eventHub, '$emit'); + + vm.selectFilter(1); + + expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1); + }); + }); + }); + + describe('template', () => { + it('renders component container element', () => { + expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true); + }); + + it('renders comment icon element', () => { + expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain( + 'comment', + ); + }); + + it('renders filter information note', () => { + expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain( + "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.", + ); + }); + + it('renders filter buttons', () => { + const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions'); + + expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain( + 'Show all activity', + ); + + expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain( + 'Show comments only', + ); + }); + + it('clicking `Show all activity` button calls `selectFilter("all")` method', () => { + const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child'); + spyOn(vm, 'selectFilter'); + + showAllBtn.dispatchEvent(new Event('click')); + + expect(vm.selectFilter).toHaveBeenCalledWith(0); + }); + + it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => { + const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child'); + spyOn(vm, 'selectFilter'); + + showAllBtn.dispatchEvent(new Event('click')); + + expect(vm.selectFilter).toHaveBeenCalledWith(1); + }); + }); +}); diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js index 91dab58ba7f..1c366aee8e2 100644 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import createStore from '~/notes/stores'; import DiscussionFilter from '~/notes/components/discussion_filter.vue'; -import { DISCUSSION_FILTERS_DEFAULT_VALUE } from '~/notes/constants'; +import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants'; import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { discussionFiltersMock, discussionMock } from '../mock_data'; @@ -54,14 +54,18 @@ describe('DiscussionFilter component', () => { }); it('updates to the selected item', () => { - const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`, + ); filterItem.click(); expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim()); }); it('only updates when selected filter changes', () => { - const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`, + ); spyOn(vm, 'filterDiscussion'); filterItem.click(); @@ -70,21 +74,27 @@ describe('DiscussionFilter component', () => { }); it('disables commenting when "Show history only" filter is applied', () => { - const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] 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'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] 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'); + const defaultFilter = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`, + ); 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 82f58dafc78..d716ece3766 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -126,6 +126,13 @@ describe('note_app', () => { expect(wrapper.find('.js-main-target-form').exists()).toBe(false); }); + it('should render discussion filter note `commentsDisabled` is true', () => { + store.state.commentsDisabled = true; + wrapper = mountComponent(); + + expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true); + }); + it('should render form comment button as disabled', () => { expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled'); }); -- cgit v1.2.1 From d133bd7fb487dbba62de1dadce1d21e991c9a295 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 31 Jan 2019 16:32:44 -0200 Subject: Support merge to ref for merge-commit and squash Adds the ground work for writing into the merge ref refs/merge-requests/:iid/merge the merge result between source and target branches of a MR, without further side-effects such as mailing, MR updates and target branch changes. --- .../merge_requests/merge_to_ref_service_spec.rb | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'spec') diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 96f2fde7117..f13e3524f42 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe MergeRequests::MergeToRefService do +<<<<<<< HEAD shared_examples_for 'MergeService for target ref' do it 'target_ref has the same state of target branch' do repo = merge_request.target_project.repository @@ -19,6 +20,8 @@ describe MergeRequests::MergeToRefService do end end +======= +>>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash set(:user) { create(:user) } let(:merge_request) { create(:merge_request, :simple) } let(:project) { merge_request.project } @@ -74,6 +77,7 @@ describe MergeRequests::MergeToRefService do process_merge_to_ref end +<<<<<<< HEAD it 'returns error when feature is disabled' do stub_feature_flags(merge_to_tmp_merge_ref_path: false) @@ -83,6 +87,8 @@ describe MergeRequests::MergeToRefService do expect(result[:message]).to eq('Feature is not enabled') end +======= +>>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash it 'returns an error when the failing to process the merge' do allow(project.repository).to receive(:merge_to_ref).and_return(nil) @@ -101,6 +107,25 @@ describe MergeRequests::MergeToRefService do MergeRequests::MergeService.new(project, user, {}) end +<<<<<<< HEAD +======= + shared_examples_for 'MergeService for target ref' do + it 'target_ref has the same state of target branch' do + repo = merge_request.target_project.repository + + process_merge_to_ref + merge_service.execute(merge_request) + + ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3) + target_branch_commits = repo.commits(merge_request.target_branch, limit: 3) + + ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit| + expect(ref_commit.parents).to eq(target_branch_commit.parents) + end + end + end + +>>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash context 'when merge commit' do it_behaves_like 'MergeService for target ref' end @@ -185,6 +210,7 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end +<<<<<<< HEAD it 'returns error when user has no authorization to admin the merge request' do unauthorized_user = create(:user) @@ -197,5 +223,7 @@ describe MergeRequests::MergeToRefService do expect(result[:status]).to eq(:error) expect(result[:message]).to eq('You are not allowed to merge to this ref') end +======= +>>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash end end -- cgit v1.2.1 From e8fbc070e01b0c527c66d803f9be813b4c2cdc0a Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 8 Feb 2019 19:25:26 -0200 Subject: Add API support for refreshing merge ref path Add a merge_requests/:iid/merge_to_ref API which make use of the groundwork to write merge results into refs/merge-requests/:iid/merge. --- spec/requests/api/merge_requests_spec.rb | 61 ++++++++++++++++++++++ .../merge_requests/merge_to_ref_service_spec.rb | 28 ---------- 2 files changed, 61 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index b4cd3130dc5..273c52bd719 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1001,6 +1001,67 @@ describe API::MergeRequests do end end + describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref" do + let(:pipeline) { create(:ci_pipeline_without_jobs) } + let(:url) do + "/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge_to_ref" + end + + it 'returns the generated ID from the merge service in case of success' do + put api(url, user), params: { merge_commit_message: 'Custom message' } + + commit = project.commit(json_response['commit_id']) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['commit_id']).to be_present + expect(commit.message).to eq('Custom message') + end + + it "returns 400 if branch can't be merged" do + merge_request.update!(state: 'merged') + + put api(url, user) + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']) + .to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}") + end + + it 'returns 403 if user has no permissions to merge to the ref' do + user2 = create(:user) + project.add_reporter(user2) + + put api(url, user2) + + expect(response).to have_gitlab_http_status(403) + expect(json_response['message']).to eq('403 Forbidden') + end + + it 'returns 404 for an invalid merge request IID' do + put api("/projects/#{project.id}/merge_requests/12345/merge_to_ref", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 if the merge request id is used instead of iid" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 400 when merge method is not supported" do + merge_request.project.update!(merge_method: 'ff') + + put api(url, user) + + expected_error = + 'Fast-forward to refs/merge-requests/1/merge is currently not supported.' + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq(expected_error) + end + end + describe "PUT /projects/:id/merge_requests/:merge_request_iid" do context "to close a MR" do it "returns merge_request" do diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index f13e3524f42..96f2fde7117 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' describe MergeRequests::MergeToRefService do -<<<<<<< HEAD shared_examples_for 'MergeService for target ref' do it 'target_ref has the same state of target branch' do repo = merge_request.target_project.repository @@ -20,8 +19,6 @@ describe MergeRequests::MergeToRefService do end end -======= ->>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash set(:user) { create(:user) } let(:merge_request) { create(:merge_request, :simple) } let(:project) { merge_request.project } @@ -77,7 +74,6 @@ describe MergeRequests::MergeToRefService do process_merge_to_ref end -<<<<<<< HEAD it 'returns error when feature is disabled' do stub_feature_flags(merge_to_tmp_merge_ref_path: false) @@ -87,8 +83,6 @@ describe MergeRequests::MergeToRefService do expect(result[:message]).to eq('Feature is not enabled') end -======= ->>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash it 'returns an error when the failing to process the merge' do allow(project.repository).to receive(:merge_to_ref).and_return(nil) @@ -107,25 +101,6 @@ describe MergeRequests::MergeToRefService do MergeRequests::MergeService.new(project, user, {}) end -<<<<<<< HEAD -======= - shared_examples_for 'MergeService for target ref' do - it 'target_ref has the same state of target branch' do - repo = merge_request.target_project.repository - - process_merge_to_ref - merge_service.execute(merge_request) - - ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3) - target_branch_commits = repo.commits(merge_request.target_branch, limit: 3) - - ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit| - expect(ref_commit.parents).to eq(target_branch_commit.parents) - end - end - end - ->>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash context 'when merge commit' do it_behaves_like 'MergeService for target ref' end @@ -210,7 +185,6 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end -<<<<<<< HEAD it 'returns error when user has no authorization to admin the merge request' do unauthorized_user = create(:user) @@ -223,7 +197,5 @@ describe MergeRequests::MergeToRefService do expect(result[:status]).to eq(:error) expect(result[:message]).to eq('You are not allowed to merge to this ref') end -======= ->>>>>>> 89c57ca2673... Support merge to ref for merge-commit and squash end end -- cgit v1.2.1 From 4c974f50f6e85165f9750cc42760e4a2fbd81e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Tue, 26 Feb 2019 16:44:31 +0000 Subject: Get rid of ScheduleInstallationService - deletes schedule_installation_service.rb - moves schedule_installation_service.rb logic to create_service.rb - moves specs as well Removes code duplication Remove unecessary spec block Abide review suggestions Test installable applications which are not associated to a cluster Fix a typo Removes duplciated expectation Reuse variable instead of redefining Remove method in favor of a local scoped lambda Improve 'failing service' shared examples Test the increase of status count Remove duplicated test Enable fronzen literal --- spec/lib/gitlab/profiler_spec.rb | 8 +- .../clusters/applications/create_service_spec.rb | 222 ++++++++++++++------- .../schedule_installation_service_spec.rb | 77 ------- spec/support/matchers/not_changed_matcher.rb | 3 + 4 files changed, 153 insertions(+), 157 deletions(-) delete mode 100644 spec/services/clusters/applications/schedule_installation_service_spec.rb create mode 100644 spec/support/matchers/not_changed_matcher.rb (limited to 'spec') diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index 8bb0c1a0b8a..9f2214f7ce7 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Gitlab::Profiler do - RSpec::Matchers.define_negated_matcher :not_change, :change - let(:null_logger) { Logger.new('/dev/null') } let(:private_token) { 'private' } @@ -187,7 +185,7 @@ describe Gitlab::Profiler do end it 'does not modify the standard Rails loggers' do - expect { described_class.with_custom_logger(nil) { } } + expect { described_class.with_custom_logger(nil) {} } .to not_change { ActiveRecord::Base.logger } .and not_change { ActionController::Base.logger } .and not_change { ActiveSupport::LogSubscriber.colorize_logging } @@ -204,7 +202,7 @@ describe Gitlab::Profiler do end it 'cleans up ApplicationController afterwards' do - expect { described_class.with_user(user) { } } + expect { described_class.with_user(user) {} } .to not_change { ActionController.instance_methods(false) } end end @@ -213,7 +211,7 @@ describe Gitlab::Profiler do it 'does not define methods on ApplicationController' do expect(ApplicationController).not_to receive(:define_method) - described_class.with_user(nil) { } + described_class.with_user(nil) {} end end end diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb index 3f621ed5944..cbdef008b07 100644 --- a/spec/services/clusters/applications/create_service_spec.rb +++ b/spec/services/clusters/applications/create_service_spec.rb @@ -26,12 +26,6 @@ describe Clusters::Applications::CreateService do end.to change(cluster, :application_helm) end - it 'schedules an install via worker' do - expect(ClusterInstallAppWorker).to receive(:perform_async).with('helm', anything).once - - subject - end - context 'application already installed' do let!(:application) { create(:clusters_applications_helm, :installed, cluster: cluster) } @@ -42,88 +36,101 @@ describe Clusters::Applications::CreateService do end it 'schedules an upgrade for the application' do - expect(Clusters::Applications::ScheduleInstallationService).to receive(:new).with(application).and_call_original + expect(ClusterUpgradeAppWorker).to receive(:perform_async) subject end end - context 'cert manager application' do - let(:params) do - { - application: 'cert_manager', - email: 'test@example.com' - } - end - + context 'known applications' do before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) + create(:clusters_applications_helm, :installed, cluster: cluster) end - it 'creates the application' do - expect do - subject + context 'cert manager application' do + let(:params) do + { + application: 'cert_manager', + email: 'test@example.com' + } + end - cluster.reload - end.to change(cluster, :application_cert_manager) - end + before do + expect_any_instance_of(Clusters::Applications::CertManager) + .to receive(:make_scheduled!) + .and_call_original + end - it 'sets the email' do - expect(subject.email).to eq('test@example.com') - end - end + it 'creates the application' do + expect do + subject - context 'jupyter application' do - let(:params) do - { - application: 'jupyter', - hostname: 'example.com' - } - end + cluster.reload + end.to change(cluster, :application_cert_manager) + end - before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) + it 'sets the email' do + expect(subject.email).to eq('test@example.com') + end end - it 'creates the application' do - expect do - subject + context 'jupyter application' do + let(:params) do + { + application: 'jupyter', + hostname: 'example.com' + } + end - cluster.reload - end.to change(cluster, :application_jupyter) - end + before do + create(:clusters_applications_ingress, :installed, external_ip: "127.0.0.0", cluster: cluster) + expect_any_instance_of(Clusters::Applications::Jupyter) + .to receive(:make_scheduled!) + .and_call_original + end - it 'sets the hostname' do - expect(subject.hostname).to eq('example.com') - end + it 'creates the application' do + expect do + subject - it 'sets the oauth_application' do - expect(subject.oauth_application).to be_present - end - end + cluster.reload + end.to change(cluster, :application_jupyter) + end - context 'knative application' do - let(:params) do - { - application: 'knative', - hostname: 'example.com' - } - end + it 'sets the hostname' do + expect(subject.hostname).to eq('example.com') + end - before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) + it 'sets the oauth_application' do + expect(subject.oauth_application).to be_present + end end - it 'creates the application' do - expect do - subject + context 'knative application' do + let(:params) do + { + application: 'knative', + hostname: 'example.com' + } + end - cluster.reload - end.to change(cluster, :application_knative) - end + before do + expect_any_instance_of(Clusters::Applications::Knative) + .to receive(:make_scheduled!) + .and_call_original + end - it 'sets the hostname' do - expect(subject.hostname).to eq('example.com') + it 'creates the application' do + expect do + subject + + cluster.reload + end.to change(cluster, :application_knative) + end + + it 'sets the hostname' do + expect(subject.hostname).to eq('example.com') + end end end @@ -140,19 +147,21 @@ describe Clusters::Applications::CreateService do using RSpec::Parameterized::TableSyntax - before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) - end - - where(:application, :association, :allowed) do - 'helm' | :application_helm | true - 'ingress' | :application_ingress | true - 'runner' | :application_runner | false - 'jupyter' | :application_jupyter | false - 'prometheus' | :application_prometheus | false + where(:application, :association, :allowed, :pre_create_helm) do + 'helm' | :application_helm | true | false + 'ingress' | :application_ingress | true | true + 'runner' | :application_runner | false | true + 'jupyter' | :application_jupyter | false | true + 'prometheus' | :application_prometheus | false | true end with_them do + before do + klass = "Clusters::Applications::#{application.titleize}" + allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original + create(:clusters_applications_helm, :installed, cluster: cluster) if pre_create_helm + end + let(:params) { { application: application } } it 'executes for each application' do @@ -168,5 +177,68 @@ describe Clusters::Applications::CreateService do end end end + + context 'when application is installable' do + shared_examples 'installable applications' do + it 'makes the application scheduled' do + expect do + subject + end.to change { Clusters::Applications::Helm.with_status(:scheduled).count }.by(1) + end + + it 'schedules an install via worker' do + expect(ClusterInstallAppWorker) + .to receive(:perform_async) + .with(*worker_arguments) + .once + + subject + end + end + + context 'when application is associated with a cluster' do + let(:application) { create(:clusters_applications_helm, :installable, cluster: cluster) } + let(:worker_arguments) { [application.name, application.id] } + + it_behaves_like 'installable applications' + end + + context 'when application is not associated with a cluster' do + let(:worker_arguments) { [params[:application], kind_of(Numeric)] } + + it_behaves_like 'installable applications' + end + end + + context 'when installation is already in progress' do + let!(:application) { create(:clusters_applications_helm, :installing, cluster: cluster) } + + it 'raises an exception' do + expect { subject } + .to raise_exception(StateMachines::InvalidTransition) + .and not_change(application.class.with_status(:scheduled), :count) + end + + it 'does not schedule a cluster worker' do + expect(ClusterInstallAppWorker).not_to receive(:perform_async) + end + end + + context 'when application is installed' do + %i(installed updated).each do |status| + let(:application) { create(:clusters_applications_helm, status, cluster: cluster) } + + it 'schedules an upgrade via worker' do + expect(ClusterUpgradeAppWorker) + .to receive(:perform_async) + .with(application.name, application.id) + .once + + subject + + expect(application.reload).to be_scheduled + end + end + end end end diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb deleted file mode 100644 index 8380932dfaa..00000000000 --- a/spec/services/clusters/applications/schedule_installation_service_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'spec_helper' - -describe Clusters::Applications::ScheduleInstallationService do - def count_scheduled - application&.class&.with_status(:scheduled)&.count || 0 - end - - shared_examples 'a failing service' do - it 'raise an exception' do - expect(ClusterInstallAppWorker).not_to receive(:perform_async) - count_before = count_scheduled - - expect { service.execute }.to raise_error(StandardError) - expect(count_scheduled).to eq(count_before) - end - end - - describe '#execute' do - let(:service) { described_class.new(application) } - - context 'when application is installable' do - let(:application) { create(:clusters_applications_helm, :installable) } - - it 'make the application scheduled' do - expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once - - expect { service.execute }.to change { application.class.with_status(:scheduled).count }.by(1) - end - end - - context 'when installation is already in progress' do - let(:application) { create(:clusters_applications_helm, :installing) } - - it_behaves_like 'a failing service' - end - - context 'when application is nil' do - let(:application) { nil } - - it_behaves_like 'a failing service' - end - - context 'when application cannot be persisted' do - let(:application) { create(:clusters_applications_helm) } - - before do - expect(application).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid) - end - - it_behaves_like 'a failing service' - end - - context 'when application is installed' do - let(:application) { create(:clusters_applications_helm, :installed) } - - it 'schedules an upgrade via worker' do - expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once - - service.execute - - expect(application).to be_scheduled - end - end - - context 'when application is updated' do - let(:application) { create(:clusters_applications_helm, :updated) } - - it 'schedules an upgrade via worker' do - expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once - - service.execute - - expect(application).to be_scheduled - end - end - end -end diff --git a/spec/support/matchers/not_changed_matcher.rb b/spec/support/matchers/not_changed_matcher.rb new file mode 100644 index 00000000000..8ef4694982d --- /dev/null +++ b/spec/support/matchers/not_changed_matcher.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +RSpec::Matchers.define_negated_matcher :not_change, :change -- cgit v1.2.1 From 35c10922820e3e07a5c571e3ad81e90045b2da2b Mon Sep 17 00:00:00 2001 From: Alexander Koval Date: Tue, 26 Feb 2019 17:13:45 +0000 Subject: Reduce code duplication Move logic of obtaining Issuable data to separate method --- spec/lib/gitlab/issuable_metadata_spec.rb | 8 ++++---- spec/requests/api/merge_requests_spec.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb index 42635a68ee1..6ec86163233 100644 --- a/spec/lib/gitlab/issuable_metadata_spec.rb +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -28,12 +28,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[issue.id].upvotes).to eq(1) expect(data[issue.id].downvotes).to eq(0) - expect(data[issue.id].notes_count).to eq(0) + expect(data[issue.id].user_notes_count).to eq(0) expect(data[issue.id].merge_requests_count).to eq(1) expect(data[closed_issue.id].upvotes).to eq(0) expect(data[closed_issue.id].downvotes).to eq(1) - expect(data[closed_issue.id].notes_count).to eq(0) + expect(data[closed_issue.id].user_notes_count).to eq(0) expect(data[closed_issue.id].merge_requests_count).to eq(0) end end @@ -51,12 +51,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[merge_request.id].upvotes).to eq(1) expect(data[merge_request.id].downvotes).to eq(1) - expect(data[merge_request.id].notes_count).to eq(1) + expect(data[merge_request.id].user_notes_count).to eq(1) expect(data[merge_request.id].merge_requests_count).to eq(0) expect(data[merge_request_closed.id].upvotes).to eq(0) expect(data[merge_request_closed.id].downvotes).to eq(0) - expect(data[merge_request_closed.id].notes_count).to eq(0) + expect(data[merge_request_closed.id].user_notes_count).to eq(0) expect(data[merge_request_closed.id].merge_requests_count).to eq(0) end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index b4cd3130dc5..edeb52adbb2 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -320,6 +320,18 @@ describe API::MergeRequests do expect(json_response.first['title']).to eq merge_request_closed.title expect(json_response.first['id']).to eq merge_request_closed.id end + + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new do + get api("/projects/#{project.id}/merge_requests", user) + end.count + + create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, created_at: base_time) + + expect do + get api("/projects/#{project.id}/merge_requests", user) + end.not_to exceed_query_limit(control) + end end describe "GET /groups/:id/merge_requests" do -- cgit v1.2.1 From 7b445f9b15c31f7b2b53561901183ab23db2d636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Tue, 26 Feb 2019 17:32:23 +0000 Subject: Revert "Merge branch '13784-simple-masking-of-protected-variables-in-logs' into 'master'" This reverts merge request !25293 --- spec/factories/ci/group_variables.rb | 1 - spec/factories/ci/variables.rb | 1 - spec/features/group_variables_spec.rb | 2 +- spec/features/project_variables_spec.rb | 2 +- .../gitlab/ci/variables/collection/item_spec.rb | 20 +-- spec/lib/gitlab/ci/variables/collection_spec.rb | 4 +- spec/models/ci/build_spec.rb | 176 ++++++++++----------- spec/models/ci/group_variable_spec.rb | 1 - spec/models/ci/variable_spec.rb | 1 - spec/models/concerns/has_variable_spec.rb | 2 +- spec/models/concerns/maskable_spec.rb | 76 --------- spec/requests/api/group_variables_spec.rb | 4 +- spec/requests/api/runner_spec.rb | 18 +-- spec/requests/api/variables_spec.rb | 4 +- .../features/variable_list_shared_examples.rb | 8 +- 15 files changed, 113 insertions(+), 207 deletions(-) delete mode 100644 spec/models/concerns/maskable_spec.rb (limited to 'spec') diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb index 9bf520a2c0a..64716842b12 100644 --- a/spec/factories/ci/group_variables.rb +++ b/spec/factories/ci/group_variables.rb @@ -2,7 +2,6 @@ FactoryBot.define do factory :ci_group_variable, class: Ci::GroupVariable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' - masked false trait(:protected) do protected true diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index 97a7c9ba252..3d014b9b54f 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -2,7 +2,6 @@ FactoryBot.define do factory :ci_variable, class: Ci::Variable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' - masked false trait(:protected) do protected true diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 1a53e7c9512..57e3ddfb39c 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) } + let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test value', group: group) } let(:page_path) { group_settings_ci_cd_path(group) } before do diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 6bdf5df1036..a93df3696d2 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Project variables', :js do let(:user) { create(:user) } let(:project) { create(:project) } - let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') } + let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } let(:page_path) { project_settings_ci_cd_path(project) } before do diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index 3ff2fe18c15..8bf44acb228 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Variables::Collection::Item do let(:expected_value) { variable_value } let(:variable) do - { key: variable_key, value: variable_value, public: true, masked: false } + { key: variable_key, value: variable_value, public: true } end describe '.new' do @@ -88,7 +88,7 @@ describe Gitlab::Ci::Variables::Collection::Item do resource = described_class.fabricate(variable) expect(resource).to be_a(described_class) - expect(resource).to eq(key: 'CI_VAR', value: '123', public: false, masked: false) + expect(resource).to eq(key: 'CI_VAR', value: '123', public: false) end it 'supports using another collection item' do @@ -134,21 +134,7 @@ describe Gitlab::Ci::Variables::Collection::Item do .to_runner_variable expect(runner_variable) - .to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false) - end - end - - context 'when variable masking is disabled' do - before do - stub_feature_flags(variable_masking: false) - end - - it 'does not expose the masked field to the runner' do - runner_variable = described_class - .new(key: 'VAR', value: 'value', masked: true) - .to_runner_variable - - expect(runner_variable).to eq(key: 'VAR', value: 'value', public: true) + .to eq(key: 'VAR', value: 'value', public: true, file: true) end end end diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index edb209c0cf4..5c91816a586 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Variables::Collection do describe '.new' do it 'can be initialized with an array' do - variable = { key: 'VAR', value: 'value', public: true, masked: false } + variable = { key: 'VAR', value: 'value', public: true } collection = described_class.new([variable]) @@ -93,7 +93,7 @@ describe Gitlab::Ci::Variables::Collection do collection = described_class.new([{ key: 'TEST', value: '1' }]) expect(collection.to_runner_variables) - .to eq [{ key: 'TEST', value: '1', public: true, masked: false }] + .to eq [{ key: 'TEST', value: '1', public: true }] end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 81ff727b458..17540443688 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2114,55 +2114,55 @@ describe Ci::Build do context 'returns variables' do let(:predefined_variables) do [ - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false }, - { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true, masked: false }, - { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false }, - { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false }, - { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: false }, - { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false }, - { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: false }, - { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false }, - { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: false }, - { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false }, - { key: 'CI', value: 'true', public: true, masked: false }, - { key: 'GITLAB_CI', value: 'true', public: true, masked: false }, - { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false }, - { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false }, - { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false }, - { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false }, - { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false }, - { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false }, - { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false }, - { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false }, - { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false }, - { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false }, - { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false }, - { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, - { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false }, - { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false }, - { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false }, - { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false }, - { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, - { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false }, - { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false }, - { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false }, - { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false }, - { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false }, - { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false }, - { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false }, - { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false }, - { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false }, - { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false }, - { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false }, - { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false }, - { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false }, - { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false }, - { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false }, - { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true, masked: false }, - { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false }, - { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, - { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, - { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false } + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, + { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true }, + { key: 'CI_JOB_ID', value: build.id.to_s, public: true }, + { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true }, + { key: 'CI_JOB_TOKEN', value: 'my-token', public: false }, + { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, + { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false }, + { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, + { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false }, + { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }, + { key: 'CI', value: 'true', public: true }, + { key: 'GITLAB_CI', value: 'true', public: true }, + { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true }, + { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, + { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, + { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true }, + { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true }, + { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true }, + { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true }, + { key: 'CI_JOB_NAME', value: 'test', public: true }, + { key: 'CI_JOB_STAGE', value: 'test', public: true }, + { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, + { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true }, + { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true }, + { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true }, + { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, + { key: 'CI_NODE_TOTAL', value: '1', public: true }, + { key: 'CI_BUILD_REF', value: build.sha, public: true }, + { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, + { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true }, + { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true }, + { key: 'CI_BUILD_NAME', value: 'test', public: true }, + { key: 'CI_BUILD_STAGE', value: 'test', public: true }, + { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true }, + { key: 'CI_PROJECT_NAME', value: project.path, public: true }, + { key: 'CI_PROJECT_PATH', value: project.full_path, public: true }, + { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true }, + { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, + { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, + { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, + { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true }, + { key: 'CI_PAGES_URL', value: project.pages_url, public: true }, + { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true }, + { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true }, + { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, + { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }, + { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true }, + { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true }, + { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true } ] end @@ -2175,10 +2175,10 @@ describe Ci::Build do describe 'variables ordering' do context 'when variables hierarchy is stubbed' do - let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } } - let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } } - let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } } - let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } } + let(:build_pre_var) { { key: 'build', value: 'value', public: true } } + let(:project_pre_var) { { key: 'project', value: 'value', public: true } } + let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true } } + let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true } } before do allow(build).to receive(:predefined_variables) { [build_pre_var] } @@ -2200,7 +2200,7 @@ describe Ci::Build do project_pre_var, pipeline_pre_var, build_yaml_var, - { key: 'secret', value: 'value', public: false, masked: false }]) + { key: 'secret', value: 'value', public: false }]) end end @@ -2233,10 +2233,10 @@ describe Ci::Build do context 'when build has user' do let(:user_variables) do [ - { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true, masked: false }, - { key: 'GITLAB_USER_EMAIL', value: user.email, public: true, masked: false }, - { key: 'GITLAB_USER_LOGIN', value: user.username, public: true, masked: false }, - { key: 'GITLAB_USER_NAME', value: user.name, public: true, masked: false } + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }, + { key: 'GITLAB_USER_LOGIN', value: user.username, public: true }, + { key: 'GITLAB_USER_NAME', value: user.name, public: true } ] end @@ -2250,8 +2250,8 @@ describe Ci::Build do context 'when build has an environment' do let(:environment_variables) do [ - { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false }, - { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false } + { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true }, + { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true } ] end @@ -2286,7 +2286,7 @@ describe Ci::Build do before do environment_variables << - { key: 'CI_ENVIRONMENT_URL', value: url, public: true, masked: false } + { key: 'CI_ENVIRONMENT_URL', value: url, public: true } end context 'when the URL was set from the job' do @@ -2323,7 +2323,7 @@ describe Ci::Build do end let(:manual_variable) do - { key: 'CI_JOB_MANUAL', value: 'true', public: true, masked: false } + { key: 'CI_JOB_MANUAL', value: 'true', public: true } end it { is_expected.to include(manual_variable) } @@ -2331,7 +2331,7 @@ describe Ci::Build do context 'when build is for tag' do let(:tag_variable) do - { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false } + { key: 'CI_COMMIT_TAG', value: 'master', public: true } end before do @@ -2343,7 +2343,7 @@ describe Ci::Build do context 'when CI variable is defined' do let(:ci_variable) do - { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } + { key: 'SECRET_KEY', value: 'secret_value', public: false } end before do @@ -2358,7 +2358,7 @@ describe Ci::Build do let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } let(:protected_variable) do - { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } + { key: 'PROTECTED_KEY', value: 'protected_value', public: false } end before do @@ -2390,7 +2390,7 @@ describe Ci::Build do context 'when group CI variable is defined' do let(:ci_variable) do - { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } + { key: 'SECRET_KEY', value: 'secret_value', public: false } end before do @@ -2405,7 +2405,7 @@ describe Ci::Build do let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } let(:protected_variable) do - { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } + { key: 'PROTECTED_KEY', value: 'protected_value', public: false } end before do @@ -2444,11 +2444,11 @@ describe Ci::Build do let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) } let(:user_trigger_variable) do - { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false } + { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false } end let(:predefined_trigger_variable) do - { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false } + { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true } end before do @@ -2480,7 +2480,7 @@ describe Ci::Build do context 'when pipeline has a variable' do let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) } - it { is_expected.to include(key: pipeline_variable.key, value: pipeline_variable.value, public: false, masked: false) } + it { is_expected.to include(pipeline_variable.to_runner_variable) } end context 'when a job was triggered by a pipeline schedule' do @@ -2497,16 +2497,16 @@ describe Ci::Build do pipeline_schedule.reload end - it { is_expected.to include(key: pipeline_schedule_variable.key, value: pipeline_schedule_variable.value, public: false, masked: false) } + it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) } end context 'when container registry is enabled' do let(:container_registry_enabled) { true } let(:ci_registry) do - { key: 'CI_REGISTRY', value: 'registry.example.com', public: true, masked: false } + { key: 'CI_REGISTRY', value: 'registry.example.com', public: true } end let(:ci_registry_image) do - { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true, masked: false } + { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true } end context 'and is disabled for project' do @@ -2535,13 +2535,13 @@ describe Ci::Build do build.update(runner: runner) end - it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) } - it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true, masked: false }) } - it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true, masked: false }) } + it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) } end context 'when build is for a deployment' do - let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false, masked: false } } + let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } } before do build.environment = 'production' @@ -2555,7 +2555,7 @@ describe Ci::Build do end context 'when project has custom CI config path' do - let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } } + let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } } before do project.update(ci_config_path: 'custom') @@ -2572,7 +2572,7 @@ describe Ci::Build do it "includes AUTO_DEVOPS_DOMAIN" do is_expected.to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }) end end @@ -2583,7 +2583,7 @@ describe Ci::Build do it "includes AUTO_DEVOPS_DOMAIN" do is_expected.not_to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }) end end end @@ -2598,9 +2598,9 @@ describe Ci::Build do variables = subject.reverse.uniq { |variable| variable[:key] }.reverse expect(variables) - .not_to include(key: 'MYVAR', value: 'myvar', public: true, masked: false) + .not_to include(key: 'MYVAR', value: 'myvar', public: true) expect(variables) - .to include(key: 'MYVAR', value: 'pipeline value', public: false, masked: false) + .to include(key: 'MYVAR', value: 'pipeline value', public: false) end end @@ -2616,13 +2616,13 @@ describe Ci::Build do it 'includes CI_NODE_INDEX' do is_expected.to include( - { key: 'CI_NODE_INDEX', value: index.to_s, public: true, masked: false } + { key: 'CI_NODE_INDEX', value: index.to_s, public: true } ) end it 'includes correct CI_NODE_TOTAL' do is_expected.to include( - { key: 'CI_NODE_TOTAL', value: total.to_s, public: true, masked: false } + { key: 'CI_NODE_TOTAL', value: total.to_s, public: true } ) end end @@ -2641,7 +2641,7 @@ describe Ci::Build do it 'returns static predefined variables' do expect(build.variables.size).to be >= 28 expect(build.variables) - .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) + .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true) expect(build).not_to be_persisted end end @@ -2651,8 +2651,8 @@ describe Ci::Build do let(:deploy_token_variables) do [ - { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true, masked: false }, - { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false, masked: false } + { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true }, + { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false } ] end @@ -2711,7 +2711,7 @@ describe Ci::Build do end expect(variables) - .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) + .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true) end it 'does not return prohibited variables' do diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index 21d96bf3454..1b10501701c 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -5,7 +5,6 @@ describe Ci::GroupVariable do it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Presentable) } - it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) } describe '.unprotected' do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 02c07a2bd83..875e8b2b682 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -6,7 +6,6 @@ describe Ci::Variable do describe 'validations' do it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Presentable) } - it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) } end diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb index bff96e12ffa..3fbe86c5b56 100644 --- a/spec/models/concerns/has_variable_spec.rb +++ b/spec/models/concerns/has_variable_spec.rb @@ -57,7 +57,7 @@ describe HasVariable do describe '#to_runner_variable' do it 'returns a hash for the runner' do expect(subject.to_runner_variable) - .to include(key: subject.key, value: subject.value, public: false) + .to eq(key: subject.key, value: subject.value, public: false) end end end diff --git a/spec/models/concerns/maskable_spec.rb b/spec/models/concerns/maskable_spec.rb deleted file mode 100644 index aeba7ad862f..00000000000 --- a/spec/models/concerns/maskable_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Maskable do - let(:variable) { build(:ci_variable) } - - describe 'masked value validations' do - subject { variable } - - context 'when variable is masked' do - before do - subject.masked = true - end - - it { is_expected.not_to allow_value('hello').for(:value) } - it { is_expected.not_to allow_value('hello world').for(:value) } - it { is_expected.not_to allow_value('hello$VARIABLEworld').for(:value) } - it { is_expected.not_to allow_value('hello\rworld').for(:value) } - it { is_expected.to allow_value('helloworld').for(:value) } - end - - context 'when variable is not masked' do - before do - subject.masked = false - end - - it { is_expected.to allow_value('hello').for(:value) } - it { is_expected.to allow_value('hello world').for(:value) } - it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) } - it { is_expected.to allow_value('hello\rworld').for(:value) } - it { is_expected.to allow_value('helloworld').for(:value) } - end - end - - describe 'REGEX' do - subject { Maskable::REGEX } - - it 'does not match strings shorter than 8 letters' do - expect(subject.match?('hello')).to eq(false) - end - - it 'does not match strings with spaces' do - expect(subject.match?('hello world')).to eq(false) - end - - it 'does not match strings with shell variables' do - expect(subject.match?('hello$VARIABLEworld')).to eq(false) - end - - it 'does not match strings with escape characters' do - expect(subject.match?('hello\rworld')).to eq(false) - end - - it 'does not match strings that span more than one line' do - string = <<~EOS - hello - world - EOS - - expect(subject.match?(string)).to eq(false) - end - - it 'matches valid strings' do - expect(subject.match?('helloworld')).to eq(true) - end - end - - describe '#to_runner_variable' do - subject { variable.to_runner_variable } - - it 'exposes the masked attribute' do - expect(subject).to include(:masked) - end - end -end diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index 66b9aae4b58..e52f4c70407 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -87,12 +87,12 @@ describe API::GroupVariables do it 'creates variable' do expect do - post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true } + post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } end.to change {group.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') - expect(json_response['value']).to eq('PROTECTED_VALUE_2') + expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_truthy end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 43c06f7c973..e6c235ca26e 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -436,9 +436,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end let(:expected_variables) do - [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, - { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false }, - { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }] + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, + { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }] end let(:expected_artifacts) do @@ -740,12 +740,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when triggered job is available' do let(:expected_variables) do - [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, - { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false }, - { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true, 'masked' => false }, - { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }, - { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false, 'masked' => false }, - { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false, 'masked' => false }] + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, + { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true }, + { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }, + { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false }, + { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }] end let(:trigger) { create(:ci_trigger, project: project) } diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 5df6baf0ddf..cdac5b2f400 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -73,12 +73,12 @@ describe API::Variables do context 'authorized user with proper permissions' do it 'creates variable' do expect do - post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true } + post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } end.to change {project.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') - expect(json_response['value']).to eq('PROTECTED_VALUE_2') + expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_truthy end diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 73156d18c1b..0a464d77cb7 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -8,7 +8,7 @@ shared_examples 'variable list' do it 'adds new CI variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('key_value') + find('.js-ci-variable-input-value').set('key value') end click_button('Save variables') @@ -19,7 +19,7 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') end end @@ -44,7 +44,7 @@ shared_examples 'variable list' do it 'adds new protected variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('key_value') + find('.js-ci-variable-input-value').set('key value') find('.ci-variable-protected-item .js-project-feature-toggle').click expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -58,7 +58,7 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end end -- cgit v1.2.1 From f4be2d4e1bd4ac456610c72862f20ff29b3820a8 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Tue, 26 Feb 2019 17:59:26 +0000 Subject: Revert "Merge branch '56726-fix-n+1-in-issues-and-merge-requests-api' into 'master'" This reverts merge request !25042 --- spec/lib/gitlab/issuable_metadata_spec.rb | 8 ++++---- spec/requests/api/merge_requests_spec.rb | 12 ------------ 2 files changed, 4 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb index 6ec86163233..42635a68ee1 100644 --- a/spec/lib/gitlab/issuable_metadata_spec.rb +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -28,12 +28,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[issue.id].upvotes).to eq(1) expect(data[issue.id].downvotes).to eq(0) - expect(data[issue.id].user_notes_count).to eq(0) + expect(data[issue.id].notes_count).to eq(0) expect(data[issue.id].merge_requests_count).to eq(1) expect(data[closed_issue.id].upvotes).to eq(0) expect(data[closed_issue.id].downvotes).to eq(1) - expect(data[closed_issue.id].user_notes_count).to eq(0) + expect(data[closed_issue.id].notes_count).to eq(0) expect(data[closed_issue.id].merge_requests_count).to eq(0) end end @@ -51,12 +51,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[merge_request.id].upvotes).to eq(1) expect(data[merge_request.id].downvotes).to eq(1) - expect(data[merge_request.id].user_notes_count).to eq(1) + expect(data[merge_request.id].notes_count).to eq(1) expect(data[merge_request.id].merge_requests_count).to eq(0) expect(data[merge_request_closed.id].upvotes).to eq(0) expect(data[merge_request_closed.id].downvotes).to eq(0) - expect(data[merge_request_closed.id].user_notes_count).to eq(0) + expect(data[merge_request_closed.id].notes_count).to eq(0) expect(data[merge_request_closed.id].merge_requests_count).to eq(0) end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index edeb52adbb2..b4cd3130dc5 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -320,18 +320,6 @@ describe API::MergeRequests do expect(json_response.first['title']).to eq merge_request_closed.title expect(json_response.first['id']).to eq merge_request_closed.id end - - it 'avoids N+1 queries' do - control = ActiveRecord::QueryRecorder.new do - get api("/projects/#{project.id}/merge_requests", user) - end.count - - create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, created_at: base_time) - - expect do - get api("/projects/#{project.id}/merge_requests", user) - end.not_to exceed_query_limit(control) - end end describe "GET /groups/:id/merge_requests" do -- cgit v1.2.1 From 25c10abb80799e24e616e2bb8c0081096b201c29 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Tue, 26 Feb 2019 19:13:09 +0000 Subject: Add name_without_type to environments.json It's needed to show shorter names in environments "folders" --- .../projects/environments_controller_spec.rb | 10 +++++----- spec/fixtures/api/schemas/environment.json | 1 + spec/models/environment_spec.rb | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index aa97a417a98..36ce1119100 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -54,9 +54,9 @@ describe Projects::EnvironmentsController do it 'responds with a flat payload describing available environments' do expect(environments.count).to eq 3 - expect(environments.first['name']).to eq 'production' - expect(environments.second['name']).to eq 'staging/review-1' - expect(environments.third['name']).to eq 'staging/review-2' + expect(environments.first).to include('name' => 'production', 'name_without_type' => 'production') + expect(environments.second).to include('name' => 'staging/review-1', 'name_without_type' => 'review-1') + expect(environments.third).to include('name' => 'staging/review-2', 'name_without_type' => 'review-2') expect(json_response['available_count']).to eq 3 expect(json_response['stopped_count']).to eq 1 end @@ -155,9 +155,9 @@ describe Projects::EnvironmentsController do expect(response).to be_ok expect(response).not_to render_template 'folder' expect(json_response['environments'][0]) - .to include('name' => 'staging-1.0/review') + .to include('name' => 'staging-1.0/review', 'name_without_type' => 'review') expect(json_response['environments'][1]) - .to include('name' => 'staging-1.0/zzz') + .to include('name' => 'staging-1.0/zzz', 'name_without_type' => 'zzz') end end end diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json index f1d33e3ce7b..9a10ab18c30 100644 --- a/spec/fixtures/api/schemas/environment.json +++ b/spec/fixtures/api/schemas/environment.json @@ -20,6 +20,7 @@ "state": { "type": "string" }, "external_url": { "$ref": "types/nullable_string.json" }, "environment_type": { "$ref": "types/nullable_string.json" }, + "name_without_type": { "type": "string" }, "has_stop_action": { "type": "boolean" }, "environment_path": { "type": "string" }, "stop_path": { "type": "string" }, diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 2d554326f05..ab1b306e597 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -164,6 +164,28 @@ describe Environment do end end + describe '#name_without_type' do + context 'when it is inside a folder' do + subject(:environment) do + create(:environment, name: 'staging/review-1') + end + + it 'returns name without folder' do + expect(environment.name_without_type).to eq 'review-1' + end + end + + context 'when the environment if a top-level item itself' do + subject(:environment) do + create(:environment, name: 'production') + end + + it 'returns full name' do + expect(environment.name_without_type).to eq 'production' + end + end + end + describe '#nullify_external_url' do it 'replaces a blank url with nil' do env = build(:environment, external_url: "") -- cgit v1.2.1 From e116fcab4151d61087afae17b6bf6cac58be29fc Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 20 Feb 2019 18:20:08 -0300 Subject: Always fetch MR latest version when creating suggestions This is an issue that can only be seen through EE. Further details can be seen on https://gitlab.com/gitlab-org/gitlab-ee/issues/9876. In general we should always use the latest diff version of a file in order to both create and apply suggestions. --- spec/services/suggestions/apply_service_spec.rb | 11 ++++++ spec/services/suggestions/create_service_spec.rb | 47 +++++++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index 8e77d582eb4..fe85b5c9065 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -362,6 +362,17 @@ describe Suggestions::ApplyService do project.add_maintainer(user) end + context 'diff file was not found' do + it 'returns error message' do + expect(suggestion.note).to receive(:latest_diff_file) { nil } + + result = subject.execute(suggestion) + + expect(result).to eq(message: 'The file was not found', + status: :error) + end + end + context 'suggestion was already applied' do it 'returns success status' do result = subject.execute(suggestion) diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb index f1142c88a69..1b4b15b8eaa 100644 --- a/spec/services/suggestions/create_service_spec.rb +++ b/spec/services/suggestions/create_service_spec.rb @@ -9,14 +9,18 @@ describe Suggestions::CreateService do target_project: project_with_repo) end - let(:position) do - Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 14, - diff_refs: merge_request.diff_refs) + def build_position(args = {}) + default_args = { old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: merge_request.diff_refs } + + Gitlab::Diff::Position.new(default_args.merge(args)) end + let(:position) { build_position } + let(:markdown) do <<-MARKDOWN.strip_heredoc ```suggestion @@ -74,6 +78,21 @@ describe Suggestions::CreateService do end end + context 'should not create suggestions' do + let(:note) do + create(:diff_note_on_merge_request, project: project_with_repo, + noteable: merge_request, + position: position, + note: markdown) + end + + it 'creates no suggestion when diff file is not found' do + expect(note).to receive(:latest_diff_file) { nil } + + expect { subject.execute }.not_to change(Suggestion, :count) + end + end + context 'should create suggestions' do let(:note) do create(:diff_note_on_merge_request, project: project_with_repo, @@ -104,6 +123,22 @@ describe Suggestions::CreateService do expect(suggestion_2).to have_attributes(from_content: " vars = {\n", to_content: " xpto\n baz\n") end + + context 'outdated position note' do + let!(:outdated_diff) { merge_request.merge_request_diff } + let!(:latest_diff) { merge_request.create_merge_request_diff } + let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) } + let(:position) { build_position(diff_refs: latest_diff.diff_refs) } + + it 'uses the correct position when creating the suggestion' do + expect(note.position) + .to receive(:diff_file) + .with(project_with_repo.repository) + .and_call_original + + subject.execute + end + end end end end -- cgit v1.2.1 From ebb284c0dc59e2fd5bc76921609b60034f000c53 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 26 Feb 2019 11:30:43 -0800 Subject: Remove N+1 query for tags in /admin/runners page As discussed in https://github.com/mbleigh/acts-as-taggable-on/issues/91, we can avoid N+1 queries if we use `tags` instead of `tag_list`. Seen while reviewing https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19740. --- spec/controllers/admin/runners_controller_spec.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb index 4cf14030ca1..82e24213408 100644 --- a/spec/controllers/admin/runners_controller_spec.rb +++ b/spec/controllers/admin/runners_controller_spec.rb @@ -1,18 +1,35 @@ require 'spec_helper' describe Admin::RunnersController do - let(:runner) { create(:ci_runner) } + let!(:runner) { create(:ci_runner) } before do sign_in(create(:admin)) end describe '#index' do + render_views + it 'lists all runners' do get :index expect(response).to have_gitlab_http_status(200) end + + it 'avoids N+1 queries', :request_store do + get :index + + control_count = ActiveRecord::QueryRecorder.new { get :index }.count + + create(:ci_runner, :tagged_only) + + # There is still an N+1 query for `runner.builds.count` + expect { get :index }.not_to exceed_query_limit(control_count + 1) + + expect(response).to have_gitlab_http_status(200) + expect(response.body).to have_content('tag1') + expect(response.body).to have_content('tag2') + end end describe '#show' do -- cgit v1.2.1 From f9e38cd39f7bbe4fc9a9e437d745270f01edc68b Mon Sep 17 00:00:00 2001 From: Nermin Vehabovic Date: Tue, 26 Feb 2019 23:18:40 +0000 Subject: Added: Documentation for the API Added: Specs for the API action --- spec/requests/api/issues_spec.rb | 10 ++-- spec/requests/api/keys_spec.rb | 2 +- spec/requests/api/merge_request_diffs_spec.rb | 4 +- spec/requests/api/merge_requests_spec.rb | 6 +-- spec/requests/api/namespaces_spec.rb | 2 +- spec/requests/api/project_milestones_spec.rb | 74 ++++++++++++++++++++++++++- spec/requests/api/projects_spec.rb | 6 +-- spec/requests/api/runners_spec.rb | 12 ++--- spec/requests/api/search_spec.rb | 4 +- spec/requests/api/users_spec.rb | 28 +++++----- 10 files changed, 109 insertions(+), 39 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 1a4be2bd30f..01bab2a1361 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -366,7 +366,7 @@ describe API::Issues do end it 'returns an empty array if iid does not exist' do - get api("/issues", user), params: { iids: [99999] } + get api("/issues", user), params: { iids: [0] } expect_paginated_array_response([]) end @@ -627,7 +627,7 @@ describe API::Issues do end it 'returns an empty array if iid does not exist' do - get api(base_url, user), params: { iids: [99999] } + get api(base_url, user), params: { iids: [0] } expect_paginated_array_response([]) end @@ -873,7 +873,7 @@ describe API::Issues do end it 'returns an empty array if iid does not exist' do - get api("#{base_url}/issues", user), params: { iids: [99999] } + get api("#{base_url}/issues", user), params: { iids: [0] } expect_paginated_array_response([]) end @@ -1909,7 +1909,7 @@ describe API::Issues do end it "returns 404 when issue doesn't exists" do - get api("/projects/#{project.id}/issues/9999/closed_by", user) + get api("/projects/#{project.id}/issues/0/closed_by", user) expect(response).to have_gitlab_http_status(404) end @@ -1994,7 +1994,7 @@ describe API::Issues do end it "returns 404 when issue doesn't exists" do - get_related_merge_requests(project.id, 999999, user) + get_related_merge_requests(project.id, 0, user) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 3c4719964b6..f37d84fddef 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -16,7 +16,7 @@ describe API::Keys do context 'when authenticated' do it 'returns 404 for non-existing key' do - get api('/keys/999999', admin) + get api('/keys/0', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index 6530dc956cb..8a67d98fc4c 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -30,7 +30,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do end it 'returns a 404 when merge_request_iid not found' do - get api("/projects/#{project.id}/merge_requests/999/versions", user) + get api("/projects/#{project.id}/merge_requests/0/versions", user) expect(response).to have_gitlab_http_status(404) end end @@ -53,7 +53,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do end it 'returns a 404 when merge_request version_id is not found' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/999", user) + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/0", user) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index b4cd3130dc5..db56739af2f 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -441,7 +441,7 @@ describe API::MergeRequests do end it "returns a 404 error if merge_request_iid not found" do - get api("/projects/#{project.id}/merge_requests/999", user) + get api("/projects/#{project.id}/merge_requests/0", user) expect(response).to have_gitlab_http_status(404) end @@ -531,7 +531,7 @@ describe API::MergeRequests do end it 'returns a 404 when merge_request_iid not found' do - get api("/projects/#{project.id}/merge_requests/999/commits", user) + get api("/projects/#{project.id}/merge_requests/0/commits", user) expect(response).to have_gitlab_http_status(404) end @@ -551,7 +551,7 @@ describe API::MergeRequests do end it 'returns a 404 when merge_request_iid not found' do - get api("/projects/#{project.id}/merge_requests/999/changes", user) + get api("/projects/#{project.id}/merge_requests/0/changes", user) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 145356c4df5..2e376109b42 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -149,7 +149,7 @@ describe API::Namespaces do context "when namespace doesn't exist" do it 'returns not-found' do - get api('/namespaces/9999', request_actor) + get api('/namespaces/0', request_actor) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 49b5dfb0b33..895f05a98e8 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -23,13 +23,13 @@ describe API::ProjectMilestones do end it 'returns 404 response when the project does not exists' do - delete api("/projects/999/milestones/#{milestone.id}", user) + delete api("/projects/0/milestones/#{milestone.id}", user) expect(response).to have_gitlab_http_status(404) end it 'returns 404 response when the milestone does not exists' do - delete api("/projects/#{project.id}/milestones/999", user) + delete api("/projects/#{project.id}/milestones/0", user) expect(response).to have_gitlab_http_status(404) end @@ -49,4 +49,74 @@ describe API::ProjectMilestones do params: { state_event: 'close' } end end + + describe 'POST /projects/:id/milestones/:milestone_id/promote' do + let(:group) { create(:group) } + + before do + project.update(namespace: group) + end + + context 'when user does not have permission to promote milestone' do + before do + group.add_guest(user) + end + + it 'returns 403' do + post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(403) + end + end + + context 'when user has permission' do + before do + group.add_developer(user) + end + + it 'returns 200' do + post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(200) + expect(group.milestones.first.title).to eq(milestone.title) + end + + it 'returns 200 for closed milestone' do + post api("/projects/#{project.id}/milestones/#{closed_milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(200) + expect(group.milestones.first.title).to eq(closed_milestone.title) + end + end + + context 'when no such resources' do + before do + group.add_developer(user) + end + + it 'returns 404 response when the project does not exist' do + post api("/projects/0/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 response when the milestone does not exist' do + post api("/projects/#{project.id}/milestones/0/promote", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when project does not belong to group' do + before do + project.update(namespace: user.namespace) + end + + it 'returns 403' do + post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(403) + end + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 792abdb2972..856fe1bbe89 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -778,7 +778,7 @@ describe API::Projects do let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) } it 'returns error when user not found' do - get api('/users/9999/projects/') + get api('/users/0/projects/') expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1385,7 +1385,7 @@ describe API::Projects do end it 'fails if forked_from project which does not exist' do - post api("/projects/#{project_fork_target.id}/fork/9999", admin) + post api("/projects/#{project_fork_target.id}/fork/0", admin) expect(response).to have_gitlab_http_status(404) end @@ -1936,7 +1936,7 @@ describe API::Projects do end it 'returns not_found(404) for not existing project' do - get api("/projects/9999999999/languages", user) + get api("/projects/0/languages", user) expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 7f11c8c9fe8..5ca442bc448 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -241,7 +241,7 @@ describe API::Runners do end it 'returns 404 if runner does not exists' do - get api('/runners/9999', admin) + get api('/runners/0', admin) expect(response).to have_gitlab_http_status(404) end @@ -394,7 +394,7 @@ describe API::Runners do end it 'returns 404 if runner does not exists' do - update_runner(9999, admin, description: 'test') + update_runner(0, admin, description: 'test') expect(response).to have_gitlab_http_status(404) end @@ -468,7 +468,7 @@ describe API::Runners do end it 'returns 404 if runner does not exists' do - delete api('/runners/9999', admin) + delete api('/runners/0', admin) expect(response).to have_gitlab_http_status(404) end @@ -573,7 +573,7 @@ describe API::Runners do context "when runner doesn't exist" do it 'returns 404' do - get api('/runners/9999/jobs', admin) + get api('/runners/0/jobs', admin) expect(response).to have_gitlab_http_status(404) end @@ -626,7 +626,7 @@ describe API::Runners do context "when runner doesn't exist" do it 'returns 404' do - get api('/runners/9999/jobs', user) + get api('/runners/0/jobs', user) expect(response).to have_gitlab_http_status(404) end @@ -857,7 +857,7 @@ describe API::Runners do end it 'returns 404 is runner is not found' do - delete api("/projects/#{project.id}/runners/9999", user) + delete api("/projects/#{project.id}/runners/0", user) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 831f47debeb..c48ca832c85 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -126,7 +126,7 @@ describe API::Search do context 'when group does not exist' do it 'returns 404 error' do - get api('/groups/9999/search', user), params: { scope: 'issues', search: 'awesome' } + get api('/groups/0/search', user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end @@ -222,7 +222,7 @@ describe API::Search do context 'when project does not exist' do it 'returns 404 error' do - get api('/projects/9999/search', user), params: { scope: 'issues', search: 'awesome' } + get api('/projects/0/search', user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index b381431306d..a879426589d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -335,7 +335,7 @@ describe API::Users do end it "returns a 404 error if user id not found" do - get api("/users/9999", user) + get api("/users/0", user) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -732,7 +732,7 @@ describe API::Users do end it "returns 404 for non-existing user" do - put api("/users/999999", admin), params: { bio: 'update should fail' } + put api("/users/0", admin), params: { bio: 'update should fail' } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -836,7 +836,7 @@ describe API::Users do end it "returns 400 for invalid ID" do - post api("/users/999999/keys", admin) + post api("/users/0/keys", admin) expect(response).to have_gitlab_http_status(400) end end @@ -895,7 +895,7 @@ describe API::Users do it 'returns 404 error if user not found' do user.keys << key user.save - delete api("/users/999999/keys/#{key.id}", admin) + delete api("/users/0/keys/#{key.id}", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -930,7 +930,7 @@ describe API::Users do end it 'returns 400 for invalid ID' do - post api('/users/999999/gpg_keys', admin) + post api('/users/0/gpg_keys', admin) expect(response).to have_gitlab_http_status(400) end @@ -951,7 +951,7 @@ describe API::Users do context 'when authenticated' do it 'returns 404 for non-existing user' do - get api('/users/999999/gpg_keys', admin) + get api('/users/0/gpg_keys', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1007,7 +1007,7 @@ describe API::Users do user.keys << key user.save - delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin) + delete api("/users/0/gpg_keys/#{gpg_key.id}", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1051,7 +1051,7 @@ describe API::Users do user.gpg_keys << gpg_key user.save - post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin) + post api("/users/0/gpg_keys/#{gpg_key.id}/revoke", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1089,7 +1089,7 @@ describe API::Users do end it "returns a 400 for invalid ID" do - post api("/users/999999/emails", admin) + post api("/users/0/emails", admin) expect(response).to have_gitlab_http_status(400) end @@ -1121,7 +1121,7 @@ describe API::Users do context 'when authenticated' do it 'returns 404 for non-existing user' do - get api('/users/999999/emails', admin) + get api('/users/0/emails', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1177,7 +1177,7 @@ describe API::Users do it 'returns 404 error if user not found' do user.emails << email user.save - delete api("/users/999999/emails/#{email.id}", admin) + delete api("/users/0/emails/#{email.id}", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1227,7 +1227,7 @@ describe API::Users do end it "returns 404 for non-existing user" do - perform_enqueued_jobs { delete api("/users/999999", admin) } + perform_enqueued_jobs { delete api("/users/0", admin) } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1778,7 +1778,7 @@ describe API::Users do end it 'returns a 404 error if user id not found' do - post api('/users/9999/block', admin) + post api('/users/0/block', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1816,7 +1816,7 @@ describe API::Users do end it 'returns a 404 error if user id not found' do - post api('/users/9999/block', admin) + post api('/users/0/block', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end -- cgit v1.2.1 From 46da2764ce37dc0fb43b86460fd98a5af330c434 Mon Sep 17 00:00:00 2001 From: Diana Stanley Date: Tue, 26 Feb 2019 16:57:01 -0800 Subject: Add test for nil due_on value during import --- .../importer/milestones_importer_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb index 471f499dd19..35c37f9499d 100644 --- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb @@ -21,6 +21,19 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis ) end + let(:milestone2) do + double( + :milestone, + number: 1, + title: '1.0', + description: 'The first release', + state: 'open', + due_on: nil, + created_at: created_at, + updated_at: updated_at + ) + end + describe '#execute' do it 'imports the milestones in bulk' do milestone_hash = { number: 1, title: '1.0' } @@ -114,6 +127,14 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis end end + describe '#nil_due_on' do + let(:milestone_hash) { importer.build(milestone2) } + + it 'should handle missing due_on correctly' do + expect(milestone_hash[:due_date]).to be nil + end + end + describe '#each_milestone' do it 'returns the milestones' do expect(client) -- cgit v1.2.1 From 43ac2a964ffe5f2713cb3093b4192216ec8525ad Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 26 Feb 2019 22:17:50 -0800 Subject: Add pagination spec and improve filtering of null values --- .../features/projects/environments/environments_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 0c517d5f490..66c6545204b 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -38,6 +38,23 @@ describe 'Environments page', :js do end end + describe 'with environments spanning multiple pages', :js do + before do + allow(Kaminari.config).to receive(:default_per_page).and_return(3) + create_list(:environment, 4, project: project, state: :available) + end + + it 'should render second page of pipelines' do + visit_environments(project, scope: 'available') + + find('.js-next-button').click + wait_for_requests + + expect(page).to have_selector('.gl-pagination .page', count: 2) + expect(find('.gl-pagination .page-item.active .page-link').text).to eq("2") + end + end + describe 'in stopped tab page' do it 'should show no environments' do visit_environments(project, scope: 'stopped') -- cgit v1.2.1 From 4be7be6dbc3cf0ed6b86d36712713458a04a1d90 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 25 Feb 2019 14:41:19 +0100 Subject: Add JSON fixture for labels autocomplete source --- spec/javascripts/fixtures/autocomplete_sources.rb | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 spec/javascripts/fixtures/autocomplete_sources.rb (limited to 'spec') diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb new file mode 100644 index 00000000000..c117fb7cd24 --- /dev/null +++ b/spec/javascripts/fixtures/autocomplete_sources.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + set(:admin) { create(:admin) } + set(:group) { create(:group, name: 'frontend-fixtures') } + set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') } + set(:issue) { create(:issue, project: project) } + + before(:all) do + clean_frontend_fixtures('autocomplete_sources/') + end + + before do + sign_in(admin) + end + + it 'autocomplete_sources/labels.json' do |example| + issue.labels << create(:label, project: project, title: 'bug') + issue.labels << create(:label, project: project, title: 'critical') + + create(:label, project: project, title: 'feature') + create(:label, project: project, title: 'documentation') + + get :labels, + format: :json, + params: { + namespace_id: group.path, + project_id: project.path, + type: issue.class.name, + type_id: issue.id + } + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end -- cgit v1.2.1 From f524dff3e9b345909d83eb652e3301c965e2acfd Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 25 Feb 2019 21:56:06 +0100 Subject: Add tests for labels dropdown to gfm_auto_complete_spec.js --- spec/frontend/gfm_auto_complete_spec.js | 94 ++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index c7008c780d6..b79e6e0fe7b 100644 --- a/spec/frontend/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -6,17 +6,21 @@ import GfmAutoComplete from '~/gfm_auto_complete'; import 'vendor/jquery.caret'; import 'vendor/jquery.atwho'; +import { TEST_HOST } from 'helpers/test_constants'; +import labelsFixture from 'fixtures/autocomplete_sources/labels.json'; // eslint-disable-line import/no-unresolved + describe('GfmAutoComplete', () => { const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({ fetchData: () => {}, }); let atwhoInstance; - let items; let sorterValue; describe('DefaultOptions.sorter', () => { describe('assets loading', () => { + let items; + beforeEach(() => { jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true); @@ -61,7 +65,7 @@ describe('GfmAutoComplete', () => { atwhoInstance = { setting: {} }; const query = 'query'; - items = []; + const items = []; const searchKey = 'searchKey'; gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, query, items, searchKey); @@ -250,4 +254,90 @@ describe('GfmAutoComplete', () => { ).toBe('
  • grp/proj#5 Some Issue
  • '); }); }); + + describe('labels', () => { + const dataSources = { + labels: `${TEST_HOST}/autocomplete_sources/labels`, + }; + + const allLabels = labelsFixture; + const assignedLabels = allLabels.filter(label => label.set); + const unassignedLabels = allLabels.filter(label => !label.set); + + let autocomplete; + let $textarea; + + beforeEach(() => { + autocomplete = new GfmAutoComplete(dataSources); + $textarea = $(''); + autocomplete.setup($textarea, { labels: true }); + }); + + afterEach(() => { + autocomplete.destroy(); + }); + + const triggerDropdown = text => { + $textarea + .trigger('focus') + .val(text) + .caret('pos', -1); + $textarea.trigger('keyup'); + + return new Promise(window.requestAnimationFrame); + }; + + const getDropdownItems = () => { + const dropdown = document.getElementById('at-view-labels'); + const items = dropdown.getElementsByTagName('li'); + return [].map.call(items, item => item.textContent.trim()); + }; + + const expectLabels = ({ input, output }) => + triggerDropdown(input).then(() => { + expect(getDropdownItems()).toEqual(output.map(label => label.title)); + }); + + describe('with no labels assigned', () => { + beforeEach(() => { + autocomplete.cachedData['~'] = [...unassignedLabels]; + }); + + it.each` + input | output + ${'~'} | ${unassignedLabels} + ${'/label ~'} | ${unassignedLabels} + ${'/relabel ~'} | ${unassignedLabels} + ${'/unlabel ~'} | ${[]} + `('$input shows $output.length labels', expectLabels); + }); + + describe('with some labels assigned', () => { + beforeEach(() => { + autocomplete.cachedData['~'] = allLabels; + }); + + it.each` + input | output + ${'~'} | ${allLabels} + ${'/label ~'} | ${unassignedLabels} + ${'/relabel ~'} | ${allLabels} + ${'/unlabel ~'} | ${assignedLabels} + `('$input shows $output.length labels', expectLabels); + }); + + describe('with all labels assigned', () => { + beforeEach(() => { + autocomplete.cachedData['~'] = [...assignedLabels]; + }); + + it.each` + input | output + ${'~'} | ${assignedLabels} + ${'/label ~'} | ${[]} + ${'/relabel ~'} | ${assignedLabels} + ${'/unlabel ~'} | ${assignedLabels} + `('$input shows $output.length labels', expectLabels); + }); + }); }); -- cgit v1.2.1 From 752926e6df4b2364ddeac76f741207af1e8af351 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 25 Feb 2019 21:59:17 +0100 Subject: Remove tests for labels dropdown from gfm_autocomplete_spec.rb --- spec/features/issues/gfm_autocomplete_spec.rb | 84 +-------------------------- 1 file changed, 1 insertion(+), 83 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 986f3823275..8eb413bdd8d 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -278,12 +278,7 @@ describe 'GFM autocomplete', :js do end end - # This context has just one example in each contexts in order to improve spec performance. - context 'labels', :quarantine do - let!(:backend) { create(:label, project: project, title: 'backend') } - let!(:bug) { create(:label, project: project, title: 'bug') } - let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') } - + context 'labels' do it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do create(:label, project: project, title: label_xss_title) @@ -298,83 +293,6 @@ describe 'GFM autocomplete', :js do expect(find('.atwho-view-ul').text).to have_content('alert label') end end - - context 'when no labels are assigned' do - it 'shows labels' do - note = find('#note-body') - - # It should show all the labels on "~". - type(note, '~') - wait_for_requests - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/label ~". - type(note, '/label ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/relabel ~". - type(note, '/relabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show no labels on "/unlabel ~". - type(note, '/unlabel ~') - expect_labels(not_shown: [backend, bug, feature_proposal]) - end - end - - context 'when some labels are assigned' do - before do - issue.labels << [backend] - end - - it 'shows labels' do - note = find('#note-body') - - # It should show all the labels on "~". - type(note, '~') - wait_for_requests - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show only unset labels on "/label ~". - type(note, '/label ~') - expect_labels(shown: [bug, feature_proposal], not_shown: [backend]) - - # It should show all the labels on "/relabel ~". - type(note, '/relabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show only set labels on "/unlabel ~". - type(note, '/unlabel ~') - expect_labels(shown: [backend], not_shown: [bug, feature_proposal]) - end - end - - context 'when all labels are assigned' do - before do - issue.labels << [backend, bug, feature_proposal] - end - - it 'shows labels' do - note = find('#note-body') - - # It should show all the labels on "~". - type(note, '~') - wait_for_requests - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show no labels on "/label ~". - type(note, '/label ~') - expect_labels(not_shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/relabel ~". - type(note, '/relabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/unlabel ~". - type(note, '/unlabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - end - end end shared_examples 'autocomplete suggestions' do -- cgit v1.2.1 From 314062fec5a1d1f56a63202fa16fc7dacc876083 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 20 Feb 2019 15:37:49 +0900 Subject: Persist source sha and target sha for merge pipelines source_sha and target_sha are used for merge request pipelines --- spec/lib/gitlab/ci/pipeline/chain/build_spec.rb | 7 ++ spec/lib/gitlab/ci/pipeline/chain/command_spec.rb | 48 ++++++++ .../gitlab/import_export/safe_model_attributes.yml | 2 + spec/models/ci/pipeline_spec.rb | 130 ++++++++++++++++++++- spec/services/ci/create_pipeline_service_spec.rb | 34 +++++- 5 files changed, 217 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index fab071405df..c9d1d09a938 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -101,6 +101,8 @@ describe Gitlab::Ci::Pipeline::Chain::Build do checkout_sha: project.commit.id, after_sha: nil, before_sha: nil, + source_sha: merge_request.diff_head_sha, + target_sha: merge_request.target_branch_sha, trigger_request: nil, schedule: nil, merge_request: merge_request, @@ -118,5 +120,10 @@ describe Gitlab::Ci::Pipeline::Chain::Build do expect(pipeline).to be_merge_request expect(pipeline.merge_request).to eq(merge_request) end + + it 'correctly sets souce sha and target sha to pipeline' do + expect(pipeline.source_sha).to eq(merge_request.diff_head_sha) + expect(pipeline.target_sha).to eq(merge_request.target_branch_sha) + end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index 6aa802ce6fd..dab0fb51bcc 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -161,6 +161,54 @@ describe Gitlab::Ci::Pipeline::Chain::Command do end end + describe '#source_sha' do + subject { command.source_sha } + + let(:command) do + described_class.new(project: project, + source_sha: source_sha, + merge_request: merge_request) + end + + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project) + end + + let(:source_sha) { nil } + + context 'when source_sha is specified' do + let(:source_sha) { 'abc' } + + it 'returns the specified value' do + is_expected.to eq('abc') + end + end + end + + describe '#target_sha' do + subject { command.target_sha } + + let(:command) do + described_class.new(project: project, + target_sha: target_sha, + merge_request: merge_request) + end + + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project) + end + + let(:target_sha) { nil } + + context 'when target_sha is specified' do + let(:target_sha) { 'abc' } + + it 'returns the specified value' do + is_expected.to eq('abc') + end + end + end + describe '#protected_ref?' do let(:command) { described_class.new(project: project, origin_ref: 'my-branch') } diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index baca8f6d542..ee96e5c4d42 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -235,6 +235,8 @@ Ci::Pipeline: - ref - sha - before_sha +- source_sha +- target_sha - push_data - created_at - updated_at diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7ea701dd035..ee400bec04b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -130,6 +130,132 @@ describe Ci::Pipeline, :mailer do end end + describe '.detached_merge_request_pipelines' do + subject { described_class.detached_merge_request_pipelines(merge_request) } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { nil } + + it 'returns detached merge request pipelines' do + is_expected.to eq([pipeline]) + end + + context 'when target sha exists' do + let(:target_sha) { merge_request.target_branch_sha } + + it 'returns empty array' do + is_expected.to be_empty + end + end + end + + describe '#detached_merge_request_pipeline?' do + subject { pipeline.detached_merge_request_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { nil } + + it { is_expected.to be_truthy } + + context 'when target sha exists' do + let(:target_sha) { merge_request.target_branch_sha } + + it { is_expected.to be_falsy } + end + end + + describe '.merge_request_pipelines' do + subject { described_class.merge_request_pipelines(merge_request) } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it 'returns merge pipelines' do + is_expected.to eq([pipeline]) + end + + context 'when target sha is empty' do + let(:target_sha) { nil } + + it 'returns empty array' do + is_expected.to be_empty + end + end + end + + describe '#merge_request_pipeline?' do + subject { pipeline.merge_request_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it { is_expected.to be_truthy } + + context 'when target sha is empty' do + let(:target_sha) { nil } + + it { is_expected.to be_falsy } + end + end + + describe '.mergeable_merge_request_pipelines' do + subject { described_class.mergeable_merge_request_pipelines(merge_request) } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it 'returns mergeable merge pipelines' do + is_expected.to eq([pipeline]) + end + + context 'when target sha does not point the head of the target branch' do + let(:target_sha) { merge_request.diff_head_sha } + + it 'returns empty array' do + is_expected.to be_empty + end + end + end + + describe '#mergeable_merge_request_pipeline?' do + subject { pipeline.mergeable_merge_request_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it { is_expected.to be_truthy } + + context 'when target sha does not point the head of the target branch' do + let(:target_sha) { merge_request.diff_head_sha } + + it { is_expected.to be_falsy } + end + end + describe '.merge_request' do subject { described_class.merge_request } @@ -400,10 +526,12 @@ describe Ci::Pipeline, :mailer do 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path, 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url, 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s, + 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => pipeline.target_sha.to_s, 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s, 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path, 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s) + 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s, + 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s) end context 'when source project does not exist' do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 8497e90bd8b..93349ba7b5b 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -12,6 +12,7 @@ describe Ci::CreatePipelineService do end describe '#execute' do + # rubocop:disable Metrics/ParameterLists def execute_service( source: :push, after: project.commit.id, @@ -20,17 +21,22 @@ describe Ci::CreatePipelineService do trigger_request: nil, variables_attributes: nil, merge_request: nil, - push_options: nil) + push_options: nil, + source_sha: nil, + target_sha: nil) params = { ref: ref, before: '00000000', after: after, commits: [{ message: message }], variables_attributes: variables_attributes, - push_options: push_options } + push_options: push_options, + source_sha: source_sha, + target_sha: target_sha } described_class.new(project, user, params).execute( source, trigger_request: trigger_request, merge_request: merge_request) end + # rubocop:enable Metrics/ParameterLists context 'valid params' do let(:pipeline) { execute_service } @@ -679,7 +685,11 @@ describe Ci::CreatePipelineService do describe 'Merge request pipelines' do let(:pipeline) do - execute_service(source: source, merge_request: merge_request, ref: ref_name) + execute_service(source: source, + merge_request: merge_request, + ref: ref_name, + source_sha: source_sha, + target_sha: target_sha) end before do @@ -687,6 +697,8 @@ describe Ci::CreatePipelineService do end let(:ref_name) { 'refs/heads/feature' } + let(:source_sha) { project.commit(ref_name).id } + let(:target_sha) { nil } context 'when source is merge request' do let(:source) { :merge_request } @@ -727,6 +739,22 @@ describe Ci::CreatePipelineService do expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test]) end + it 'persists the specified source sha' do + expect(pipeline.source_sha).to eq(source_sha) + end + + it 'does not persist target sha for detached merge request pipeline' do + expect(pipeline.target_sha).to be_nil + end + + context 'when target sha is specified' do + let(:target_sha) { merge_request.target_branch_sha } + + it 'persists the target sha' do + expect(pipeline.target_sha).to eq(target_sha) + end + end + context 'when ref is tag' do let(:ref_name) { 'refs/tags/v1.1.0' } -- cgit v1.2.1 From 0aff8e27530d391e4c819838b46d77f6231ecf70 Mon Sep 17 00:00:00 2001 From: Denys Mishunov Date: Wed, 27 Feb 2019 09:12:13 +0000 Subject: Set up basic keyboard next/previous navigation in diff list Mousetrap is used as the help-tool to listen to keystrokes Added currentDiffIndex getter to store that holds the index of currently active diff file in the list Instead of computing it on the component, we will take advantage of it being available for all components in DiffsApp Testing keyboard navigation and jumpToFile() --- spec/javascripts/diffs/components/app_spec.js | 224 +++++++++++++++++++++++--- spec/javascripts/diffs/store/actions_spec.js | 19 +-- spec/javascripts/diffs/store/getters_spec.js | 20 +++ 3 files changed, 227 insertions(+), 36 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 5abdfe695d0..bce6113f75a 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -4,12 +4,13 @@ import { TEST_HOST } from 'spec/test_constants'; import App from '~/diffs/components/app.vue'; import NoChanges from '~/diffs/components/no_changes.vue'; import DiffFile from '~/diffs/components/diff_file.vue'; +import Mousetrap from 'mousetrap'; import createDiffsStore from '../create_diffs_store'; describe('diffs/components/app', () => { const oldMrTabs = window.mrTabs; let store; - let vm; + let wrapper; function createComponent(props = {}, extendStore = () => {}) { const localVue = createLocalVue(); @@ -21,7 +22,7 @@ describe('diffs/components/app', () => { extendStore(store); - vm = shallowMount(localVue.extend(App), { + wrapper = shallowMount(localVue.extend(App), { localVue, propsData: { endpoint: `${TEST_HOST}/diff/endpoint`, @@ -38,7 +39,6 @@ describe('diffs/components/app', () => { // setup globals (needed for component to mount :/) window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); window.mrTabs.expandViewContainer = jasmine.createSpy(); - window.location.hash = 'ABC_123'; }); afterEach(() => { @@ -46,25 +46,44 @@ describe('diffs/components/app', () => { window.mrTabs = oldMrTabs; // reset component - vm.destroy(); + wrapper.destroy(); }); it('does not show commit info', () => { createComponent(); - expect(vm.contains('.blob-commit-info')).toBe(false); + expect(wrapper.contains('.blob-commit-info')).toBe(false); }); - it('sets highlighted row if hash exists in location object', done => { - createComponent({ - shouldShow: true, + describe('row highlighting', () => { + beforeEach(() => { + window.location.hash = 'ABC_123'; }); - // Component uses $nextTick so we wait until that has finished - setTimeout(() => { - expect(store.state.diffs.highlightedRow).toBe('ABC_123'); + it('sets highlighted row if hash exists in location object', done => { + createComponent({ + shouldShow: true, + }); - done(); + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.highlightedRow).toBe('ABC_123'); + + done(); + }); + }); + + it('marks current diff file based on currently highlighted row', done => { + createComponent({ + shouldShow: true, + }); + + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.currentDiffFileId).toBe('ABC'); + + done(); + }); }); }); @@ -76,7 +95,7 @@ describe('diffs/components/app', () => { it('sets initial width when no localStorage has been set', () => { createComponent(); - expect(vm.vm.treeWidth).toEqual(320); + expect(wrapper.vm.treeWidth).toEqual(320); }); it('sets initial width to localStorage size', () => { @@ -84,13 +103,26 @@ describe('diffs/components/app', () => { createComponent(); - expect(vm.vm.treeWidth).toEqual(200); + expect(wrapper.vm.treeWidth).toEqual(200); }); it('sets width of tree list', () => { createComponent(); - expect(vm.find('.js-diff-tree-list').element.style.width).toEqual('320px'); + expect(wrapper.find('.js-diff-tree-list').element.style.width).toEqual('320px'); + }); + }); + + it('marks current diff file based on currently highlighted row', done => { + createComponent({ + shouldShow: true, + }); + + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.currentDiffFileId).toBe('ABC'); + + done(); }); }); @@ -98,7 +130,7 @@ describe('diffs/components/app', () => { it('renders empty state when no diff files exist', () => { createComponent(); - expect(vm.contains(NoChanges)).toBe(true); + expect(wrapper.contains(NoChanges)).toBe(true); }); it('does not render empty state when diff files exist', () => { @@ -108,8 +140,8 @@ describe('diffs/components/app', () => { }); }); - expect(vm.contains(NoChanges)).toBe(false); - expect(vm.findAll(DiffFile).length).toBe(1); + expect(wrapper.contains(NoChanges)).toBe(false); + expect(wrapper.findAll(DiffFile).length).toBe(1); }); it('does not render empty state when versions match', () => { @@ -118,7 +150,161 @@ describe('diffs/components/app', () => { store.state.diffs.mergeRequestDiff = { version_index: 1 }; }); - expect(vm.contains(NoChanges)).toBe(false); + expect(wrapper.contains(NoChanges)).toBe(false); + }); + }); + + describe('keyboard shortcut navigation', () => { + const mappings = { + '[': -1, + k: -1, + ']': +1, + j: +1, + }; + let spy; + + describe('visible app', () => { + beforeEach(() => { + spy = jasmine.createSpy('spy'); + + createComponent({ + shouldShow: true, + }); + wrapper.setMethods({ + jumpToFile: spy, + }); + }); + + it('calls `jumpToFile()` with correct parameter whenever pre-defined key is pressed', done => { + wrapper.vm + .$nextTick() + .then(() => { + Object.keys(mappings).forEach(function(key) { + Mousetrap.trigger(key); + + expect(spy.calls.mostRecent().args).toEqual([mappings[key]]); + }); + + expect(spy.calls.count()).toEqual(Object.keys(mappings).length); + }) + .then(done) + .catch(done.fail); + }); + + it('does not call `jumpToFile()` when unknown key is pressed', done => { + wrapper.vm + .$nextTick() + .then(() => { + Mousetrap.trigger('d'); + + expect(spy).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('hideen app', () => { + beforeEach(() => { + spy = jasmine.createSpy('spy'); + + createComponent({ + shouldShow: false, + }); + wrapper.setMethods({ + jumpToFile: spy, + }); + }); + + it('stops calling `jumpToFile()` when application is hidden', done => { + wrapper.vm + .$nextTick() + .then(() => { + Object.keys(mappings).forEach(function(key) { + Mousetrap.trigger(key); + + expect(spy).not.toHaveBeenCalled(); + }); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('jumpToFile', () => { + let spy; + + beforeEach(() => { + spy = jasmine.createSpy(); + + createComponent({}, () => { + store.state.diffs.diffFiles = [ + { file_hash: '111', file_path: '111.js' }, + { file_hash: '222', file_path: '222.js' }, + { file_hash: '333', file_path: '333.js' }, + ]; + }); + + wrapper.setMethods({ + scrollToFile: spy, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('jumps to next and previous files in the list', done => { + wrapper.vm + .$nextTick() + .then(() => { + wrapper.vm.jumpToFile(+1); + + expect(spy.calls.mostRecent().args).toEqual(['222.js']); + store.state.diffs.currentDiffFileId = '222'; + wrapper.vm.jumpToFile(+1); + + expect(spy.calls.mostRecent().args).toEqual(['333.js']); + store.state.diffs.currentDiffFileId = '333'; + wrapper.vm.jumpToFile(-1); + + expect(spy.calls.mostRecent().args).toEqual(['222.js']); + }) + .then(done) + .catch(done.fail); + }); + + it('does not jump to previous file from the first one', done => { + wrapper.vm + .$nextTick() + .then(() => { + store.state.diffs.currentDiffFileId = '333'; + + expect(wrapper.vm.currentDiffIndex).toEqual(2); + + wrapper.vm.jumpToFile(+1); + + expect(wrapper.vm.currentDiffIndex).toEqual(2); + expect(spy).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not jump to next file from the last one', done => { + wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.vm.currentDiffIndex).toEqual(0); + + wrapper.vm.jumpToFile(-1); + + expect(wrapper.vm.currentDiffIndex).toEqual(0); + expect(spy).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index acff80bca62..e47c7906fcb 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -100,9 +100,10 @@ describe('DiffsStoreActions', () => { }); describe('setHighlightedRow', () => { - it('should set lineHash and fileHash of highlightedRow', () => { + it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => { testAction(setHighlightedRow, 'ABC_123', {}, [ { type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' }, + { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'ABC' }, ]); }); }); @@ -713,22 +714,6 @@ describe('DiffsStoreActions', () => { expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test'); }); - - it('resets currentDiffId after timeout', () => { - const state = { - treeEntries: { - path: { - fileHash: 'test', - }, - }, - }; - - scrollToFile({ state, commit }, 'path'); - - jasmine.clock().tick(1000); - - expect(commit.calls.argsFor(1)).toEqual([types.UPDATE_CURRENT_DIFF_FILE_ID, '']); - }); }); describe('toggleShowTreeList', () => { diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js index 0ab88e6b2aa..eab5703dfb2 100644 --- a/spec/javascripts/diffs/store/getters_spec.js +++ b/spec/javascripts/diffs/store/getters_spec.js @@ -270,4 +270,24 @@ describe('Diffs Module Getters', () => { expect(getters.diffFilesLength(localState)).toBe(2); }); }); + + describe('currentDiffIndex', () => { + it('returns index of currently selected diff in diffList', () => { + localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }]; + localState.currentDiffFileId = '222'; + + expect(getters.currentDiffIndex(localState)).toEqual(1); + + localState.currentDiffFileId = '333'; + + expect(getters.currentDiffIndex(localState)).toEqual(2); + }); + + it('returns 0 if no diff is selected yet or diff is not found', () => { + localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }]; + localState.currentDiffFileId = ''; + + expect(getters.currentDiffIndex(localState)).toEqual(0); + }); + }); }); -- cgit v1.2.1 From d63483a2dad8c42e47484ea340bd0f94bc898e27 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Feb 2019 10:16:46 +0000 Subject: Fix inconsistent pagination styles --- spec/features/projects/pipelines/pipelines_spec.rb | 2 +- spec/javascripts/commit/pipelines/pipelines_spec.js | 2 +- spec/javascripts/environments/environments_app_spec.js | 2 +- .../environments/folder/environments_folder_view_spec.js | 2 +- spec/javascripts/pipelines/pipelines_spec.js | 2 +- .../vue_shared/components/table_pagination_spec.js | 12 ++++++------ 6 files changed, 11 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index ffa165c5440..88d7c9ef8bd 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -485,7 +485,7 @@ describe 'Pipelines', :js do it 'should show updated content' do visit project_pipelines_path(project) wait_for_requests - page.find('.js-next-button a').click + page.find('.js-next-button .page-link').click expect(page).to have_selector('.gl-pagination .page', count: 2) end diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index 04c8ab44405..fec01b1f0a3 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -87,7 +87,7 @@ describe('Pipelines table in Commits and Merge requests', function() { }; vm.$nextTick(() => { - vm.$el.querySelector('.js-next-button a').click(); + vm.$el.querySelector('.js-next-button .page-link').click(); expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' }); done(); diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index 9220f7a264f..b6a244f7cd3 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -94,7 +94,7 @@ describe('Environment', () => { it('should make an API request when page is clicked', done => { spyOn(component, 'updateContent'); setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); + component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click(); expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); done(); diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index d9ee7e74e28..69ddd26eef1 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -107,7 +107,7 @@ describe('Environments Folder View', () => { it('should make an API request when changing page', done => { spyOn(component, 'updateContent'); setTimeout(() => { - component.$el.querySelector('.gl-pagination .js-last-button a').click(); + component.$el.querySelector('.gl-pagination .js-last-button .page-link').click(); expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index 97ded16db69..78187b69563 100644 --- a/spec/javascripts/pipelines/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -446,7 +446,7 @@ describe('Pipelines', () => { }; vm.$nextTick(() => { - vm.$el.querySelector('.js-next-button a').click(); + vm.$el.querySelector('.js-next-button .page-link').click(); expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' }); diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js index 0dcb712e720..407d1d59f83 100644 --- a/spec/javascripts/vue_shared/components/table_pagination_spec.js +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js @@ -53,7 +53,7 @@ describe('Pagination component', () => { component.$el.querySelector('.js-previous-button').classList.contains('disabled'), ).toEqual(true); - component.$el.querySelector('.js-previous-button a').click(); + component.$el.querySelector('.js-previous-button .page-link').click(); expect(spy).not.toHaveBeenCalled(); }); @@ -71,7 +71,7 @@ describe('Pagination component', () => { change: spy, }); - component.$el.querySelector('.js-previous-button a').click(); + component.$el.querySelector('.js-previous-button .page-link').click(); expect(spy).toHaveBeenCalledWith(1); }); @@ -91,7 +91,7 @@ describe('Pagination component', () => { change: spy, }); - const button = component.$el.querySelector('.js-first-button a'); + const button = component.$el.querySelector('.js-first-button .page-link'); expect(button.textContent.trim()).toEqual('« First'); @@ -115,7 +115,7 @@ describe('Pagination component', () => { change: spy, }); - const button = component.$el.querySelector('.js-last-button a'); + const button = component.$el.querySelector('.js-last-button .page-link'); expect(button.textContent.trim()).toEqual('Last »'); @@ -141,7 +141,7 @@ describe('Pagination component', () => { expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next'); - component.$el.querySelector('.js-next-button a').click(); + component.$el.querySelector('.js-next-button .page-link').click(); expect(spy).not.toHaveBeenCalled(); }); @@ -159,7 +159,7 @@ describe('Pagination component', () => { change: spy, }); - component.$el.querySelector('.js-next-button a').click(); + component.$el.querySelector('.js-next-button .page-link').click(); expect(spy).toHaveBeenCalledWith(4); }); -- cgit v1.2.1 From 1874aa9a3b3151ece76030d620017425e3cafc2f Mon Sep 17 00:00:00 2001 From: Alexander Koval Date: Wed, 27 Feb 2019 10:28:53 +0000 Subject: Reduce code duplication Move logic of obtaining Issuable data to separate method --- spec/lib/gitlab/issuable_metadata_spec.rb | 8 ++++---- spec/requests/api/merge_requests_spec.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb index 42635a68ee1..6ec86163233 100644 --- a/spec/lib/gitlab/issuable_metadata_spec.rb +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -28,12 +28,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[issue.id].upvotes).to eq(1) expect(data[issue.id].downvotes).to eq(0) - expect(data[issue.id].notes_count).to eq(0) + expect(data[issue.id].user_notes_count).to eq(0) expect(data[issue.id].merge_requests_count).to eq(1) expect(data[closed_issue.id].upvotes).to eq(0) expect(data[closed_issue.id].downvotes).to eq(1) - expect(data[closed_issue.id].notes_count).to eq(0) + expect(data[closed_issue.id].user_notes_count).to eq(0) expect(data[closed_issue.id].merge_requests_count).to eq(0) end end @@ -51,12 +51,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[merge_request.id].upvotes).to eq(1) expect(data[merge_request.id].downvotes).to eq(1) - expect(data[merge_request.id].notes_count).to eq(1) + expect(data[merge_request.id].user_notes_count).to eq(1) expect(data[merge_request.id].merge_requests_count).to eq(0) expect(data[merge_request_closed.id].upvotes).to eq(0) expect(data[merge_request_closed.id].downvotes).to eq(0) - expect(data[merge_request_closed.id].notes_count).to eq(0) + expect(data[merge_request_closed.id].user_notes_count).to eq(0) expect(data[merge_request_closed.id].merge_requests_count).to eq(0) end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index db56739af2f..8aba0bc8e18 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -320,6 +320,18 @@ describe API::MergeRequests do expect(json_response.first['title']).to eq merge_request_closed.title expect(json_response.first['id']).to eq merge_request_closed.id end + + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new do + get api("/projects/#{project.id}/merge_requests", user) + end.count + + create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, created_at: base_time) + + expect do + get api("/projects/#{project.id}/merge_requests", user) + end.not_to exceed_query_limit(control) + end end describe "GET /groups/:id/merge_requests" do -- cgit v1.2.1 From 44c4aad983570ea1832aa08c39f46dbc1b475fd3 Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Sat, 23 Feb 2019 19:18:44 +0100 Subject: Filter active sessions belonging to an admin impersonating the user --- spec/features/profiles/active_sessions_spec.rb | 23 +++++++++++++++++++++++ spec/models/active_session_spec.rb | 5 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb index d3050760c06..3fd3222fa0a 100644 --- a/spec/features/profiles/active_sessions_spec.rb +++ b/spec/features/profiles/active_sessions_spec.rb @@ -7,6 +7,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do end end + let(:admin) { create(:admin) } + around do |example| Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do example.run @@ -16,6 +18,7 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do it 'User sees their active sessions' do Capybara::Session.new(:session1) Capybara::Session.new(:session2) + Capybara::Session.new(:session3) # note: headers can only be set on the non-js (aka. rack-test) driver using_session :session1 do @@ -37,9 +40,27 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do gitlab_sign_in(user) end + # set an admin session impersonating the user + using_session :session3 do + Capybara.page.driver.header( + 'User-Agent', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36' + ) + + gitlab_sign_in(admin) + + visit admin_user_path(user) + + click_link 'Impersonate' + end + using_session :session1 do visit profile_active_sessions_path + expect(page).to( + have_selector('ul.list-group li.list-group-item', { text: 'Signed in on', + count: 2 })) + expect(page).to have_content( '127.0.0.1 ' \ 'This is your current session ' \ @@ -57,6 +78,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do ) expect(page).to have_selector '[title="Smartphone"]', count: 1 + + expect(page).not_to have_content('Chrome on Windows') end end diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index 129b2f92683..e128fe8a4b7 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -7,7 +7,10 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do end end - let(:session) { double(:session, id: '6919a6f1bb119dd7396fadc38fd18d0d') } + let(:session) do + double(:session, { id: '6919a6f1bb119dd7396fadc38fd18d0d', + '[]': {} }) + end let(:request) do double(:request, { -- cgit v1.2.1 From 038d530565bc64729706bbd9afad275699be459d Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Mon, 25 Feb 2019 14:52:40 +0100 Subject: Remove ability to revoke active session Session ID is used as a parameter for the revoke session endpoint but it should never be included in the HTML as an attacker could obtain it via XSS. --- spec/features/profiles/active_sessions_spec.rb | 27 -------------------------- 1 file changed, 27 deletions(-) (limited to 'spec') diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb index 3fd3222fa0a..2aa0177af5d 100644 --- a/spec/features/profiles/active_sessions_spec.rb +++ b/spec/features/profiles/active_sessions_spec.rb @@ -82,31 +82,4 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do expect(page).not_to have_content('Chrome on Windows') end end - - it 'User can revoke a session', :js, :redis_session_store do - Capybara::Session.new(:session1) - Capybara::Session.new(:session2) - - # set an additional session in another browser - using_session :session2 do - gitlab_sign_in(user) - end - - using_session :session1 do - gitlab_sign_in(user) - visit profile_active_sessions_path - - expect(page).to have_link('Revoke', count: 1) - - accept_confirm { click_on 'Revoke' } - - expect(page).not_to have_link('Revoke') - end - - using_session :session2 do - visit profile_active_sessions_path - - expect(page).to have_content('You need to sign in or sign up before continuing.') - end - end end -- cgit v1.2.1 From f497c0f236fdb4714eade2054c48fa60f4d91791 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 27 Feb 2019 10:52:19 +0000 Subject: Danger ignores gitlab.pot --- spec/lib/gitlab/danger/helper_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 00cb1e6446a..66cd8171c12 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -265,6 +265,7 @@ describe Gitlab::Danger::Helper do 'changelogs/foo' | :none 'ee/changelogs/foo' | :none + 'locale/gitlab.pot' | :none 'FOO' | :unknown 'foo' | :unknown -- cgit v1.2.1 From 5ae9a44aa17c8929627cc450f936cd960c143e25 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 13 Dec 2018 20:26:56 +0100 Subject: Add project http fetch statistics API The API get projects/:id/traffic/fetches allows user with write access to the repository to get the number of clones for the last 30 days. --- spec/factories/project_daily_statistics.rb | 8 +++ spec/models/project_daily_statistic_spec.rb | 7 +++ spec/models/project_spec.rb | 12 +++++ spec/policies/project_policy_spec.rb | 1 + spec/requests/api/project_statistics_spec.rb | 62 ++++++++++++++++++++++ .../fetch_statistics_increment_service_spec.rb | 36 +++++++++++++ .../project_daily_statistics_worker_spec.rb | 35 ++++++++++++ 7 files changed, 161 insertions(+) create mode 100644 spec/factories/project_daily_statistics.rb create mode 100644 spec/models/project_daily_statistic_spec.rb create mode 100644 spec/requests/api/project_statistics_spec.rb create mode 100644 spec/services/projects/fetch_statistics_increment_service_spec.rb create mode 100644 spec/workers/project_daily_statistics_worker_spec.rb (limited to 'spec') diff --git a/spec/factories/project_daily_statistics.rb b/spec/factories/project_daily_statistics.rb new file mode 100644 index 00000000000..7e4142fa401 --- /dev/null +++ b/spec/factories/project_daily_statistics.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :project_daily_statistic do + project + fetch_count 1 + end +end diff --git a/spec/models/project_daily_statistic_spec.rb b/spec/models/project_daily_statistic_spec.rb new file mode 100644 index 00000000000..86210af15d8 --- /dev/null +++ b/spec/models/project_daily_statistic_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectDailyStatistic do + it { is_expected.to belong_to(:project) } +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1f9088c2e6b..4a218cdc24f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2335,6 +2335,18 @@ describe Project do end end + describe '#daily_statistics_enabled?' do + it { is_expected.to be_daily_statistics_enabled } + + context 'when :project_daily_statistics is disabled for the project' do + before do + stub_feature_flags(project_daily_statistics: { thing: subject, enabled: false }) + end + + it { is_expected.not_to be_daily_statistics_enabled } + end + end + describe '#change_head' do let(:project) { create(:project, :repository) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 93a468f585b..997bdc82af6 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -49,6 +49,7 @@ describe ProjectPolicy do admin_project_member admin_note admin_wiki admin_project admin_commit_status admin_build admin_container_image admin_pipeline admin_environment admin_deployment destroy_release add_cluster + daily_statistics ] end diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb new file mode 100644 index 00000000000..184d0a72c37 --- /dev/null +++ b/spec/requests/api/project_statistics_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::ProjectStatistics do + let(:maintainer) { create(:user) } + let(:public_project) { create(:project, :public) } + + before do + public_project.add_maintainer(maintainer) + end + + describe 'GET /projects/:id/statistics' do + let!(:fetch_statistics1) { create(:project_daily_statistic, project: public_project, fetch_count: 30, date: 29.days.ago) } + let!(:fetch_statistics2) { create(:project_daily_statistic, project: public_project, fetch_count: 4, date: 3.days.ago) } + let!(:fetch_statistics3) { create(:project_daily_statistic, project: public_project, fetch_count: 3, date: 2.days.ago) } + let!(:fetch_statistics4) { create(:project_daily_statistic, project: public_project, fetch_count: 2, date: 1.day.ago) } + let!(:fetch_statistics5) { create(:project_daily_statistic, project: public_project, fetch_count: 1, date: Date.today) } + let!(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) } + + it 'returns the fetch statistics of the last 30 days' do + get api("/projects/#{public_project.id}/statistics", maintainer) + + expect(response).to have_gitlab_http_status(200) + fetches = json_response['fetches'] + expect(fetches['total']).to eq(40) + expect(fetches['days'].length).to eq(5) + expect(fetches['days'].first).to eq({ 'count' => fetch_statistics5.fetch_count, 'date' => fetch_statistics5.date.to_s }) + expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s }) + end + + it 'excludes the fetch statistics older than 30 days' do + create(:project_daily_statistic, fetch_count: 31, project: public_project, date: 30.days.ago) + + get api("/projects/#{public_project.id}/statistics", maintainer) + + expect(response).to have_gitlab_http_status(200) + fetches = json_response['fetches'] + expect(fetches['total']).to eq(40) + expect(fetches['days'].length).to eq(5) + expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s }) + end + + it 'responds with 403 when the user is not a maintainer of the repository' do + developer = create(:user) + public_project.add_developer(developer) + + get api("/projects/#{public_project.id}/statistics", developer) + + expect(response).to have_gitlab_http_status(403) + expect(json_response['message']).to eq('403 Forbidden') + end + + it 'responds with 404 when daily_statistics_enabled? is false' do + stub_feature_flags(project_daily_statistics: { thing: public_project, enabled: false }) + + get api("/projects/#{public_project.id}/statistics", maintainer) + + expect(response).to have_gitlab_http_status(404) + end + end +end diff --git a/spec/services/projects/fetch_statistics_increment_service_spec.rb b/spec/services/projects/fetch_statistics_increment_service_spec.rb new file mode 100644 index 00000000000..fcfb138aad6 --- /dev/null +++ b/spec/services/projects/fetch_statistics_increment_service_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Projects + describe FetchStatisticsIncrementService do + let(:project) { create(:project) } + + describe '#execute' do + subject { described_class.new(project).execute } + + it 'creates a new record for today with count == 1' do + expect { subject }.to change { ProjectDailyStatistic.count }.by(1) + created_stat = ProjectDailyStatistic.last + + expect(created_stat.fetch_count).to eq(1) + expect(created_stat.project).to eq(project) + expect(created_stat.date).to eq(Date.today) + end + + it "doesn't increment previous days statistics" do + yesterday_stat = create(:project_daily_statistic, fetch_count: 5, project: project, date: 1.day.ago) + + expect { subject }.not_to change { yesterday_stat.reload.fetch_count } + end + + context 'when the record already exists for today' do + let!(:project_daily_stat) { create(:project_daily_statistic, fetch_count: 5, project: project, date: Date.today) } + + it 'increments the today record count by 1' do + expect { subject }.to change { project_daily_stat.reload.fetch_count }.to(6) + end + end + end + end +end diff --git a/spec/workers/project_daily_statistics_worker_spec.rb b/spec/workers/project_daily_statistics_worker_spec.rb new file mode 100644 index 00000000000..8640add99e5 --- /dev/null +++ b/spec/workers/project_daily_statistics_worker_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe ProjectDailyStatisticsWorker, '#perform' do + let(:worker) { described_class.new } + let(:project) { create(:project) } + + describe '#perform' do + context 'with a non-existing project' do + it 'does nothing' do + expect(Projects::FetchStatisticsIncrementService).not_to receive(:new) + + worker.perform(-1) + end + end + + context 'with an existing project without a repository' do + it 'does nothing' do + expect(Projects::FetchStatisticsIncrementService).not_to receive(:new) + + worker.perform(project.id) + end + end + + it 'calls daily_statistics_service with the given project' do + project = create(:project, :repository) + + expect_next_instance_of(Projects::FetchStatisticsIncrementService, project) do |service| + expect(service).to receive(:execute) + end + + worker.perform(project.id) + end + end +end -- cgit v1.2.1 From 078f11a6b9c0197a3cfde6b3658ca268097e315c Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 27 Feb 2019 10:58:21 +0000 Subject: Prepare test suite for switch to Gitaly-embedded Git hooks --- spec/lib/gitlab/git/repository_spec.rb | 4 ++-- spec/lib/gitlab/shell_spec.rb | 1 - spec/support/helpers/test_env.rb | 12 ++++++++---- spec/tasks/gitlab/shell_rake_spec.rb | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index b3a728c139e..e3dd02f1478 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Git::Repository, :seed_helper do describe '.create_hooks' do let(:repo_path) { File.join(storage_path, 'hook-test.git') } let(:hooks_dir) { File.join(repo_path, 'hooks') } - let(:target_hooks_dir) { Gitlab.config.gitlab_shell.hooks_path } + let(:target_hooks_dir) { Gitlab::Shell.new.hooks_path } let(:existing_target) { File.join(repo_path, 'foobar') } before do @@ -1945,7 +1945,7 @@ describe Gitlab::Git::Repository, :seed_helper do imported_repo.create_from_bundle(valid_bundle_path) hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') } - expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path) + expect(File.readlink(hooks_path)).to eq(Gitlab::Shell.new.hooks_path) end it 'raises an error if the bundle is an attempted malicious payload' do diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 033e1bf81a1..d6aadf0f7de 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -393,7 +393,6 @@ describe Gitlab::Shell do before do allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path) - allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path) allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800) end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 06bcf4f8013..80a22134021 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -147,12 +147,15 @@ module TestEnv version: Gitlab::Shell.version_required, task: 'gitlab:shell:install') - create_fake_git_hooks + # gitlab-shell hooks don't work in our test environment because they try to make internal API calls + sabotage_gitlab_shell_hooks end - def create_fake_git_hooks - # gitlab-shell hooks don't work in our test environment because they try to make internal API calls - hooks_dir = File.join(Gitlab.config.gitlab_shell.path, 'hooks') + def sabotage_gitlab_shell_hooks + create_fake_git_hooks(Gitlab::Shell.new.hooks_path) + end + + def create_fake_git_hooks(hooks_dir) %w[pre-receive post-receive update].each do |hook| File.open(File.join(hooks_dir, hook), 'w', 0755) { |f| f.puts '#!/bin/sh' } end @@ -169,6 +172,7 @@ module TestEnv task: "gitlab:gitaly:install[#{install_gitaly_args}]") do Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true) + create_fake_git_hooks(File.join(gitaly_dir, 'ruby/git-hooks')) start_gitaly(gitaly_dir) end end diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb index 0ed5d3e27b9..a9d14070177 100644 --- a/spec/tasks/gitlab/shell_rake_spec.rb +++ b/spec/tasks/gitlab/shell_rake_spec.rb @@ -8,7 +8,7 @@ describe 'gitlab:shell rake tasks' do end after do - TestEnv.create_fake_git_hooks + TestEnv.sabotage_gitlab_shell_hooks end describe 'install task' do -- cgit v1.2.1 From 55826a7a77a23842bb5c73fbc8946695cc046af1 Mon Sep 17 00:00:00 2001 From: Jason Lenny Date: Wed, 27 Feb 2019 11:03:57 +0000 Subject: Add Project template for go-micro --- spec/lib/gitlab/project_template_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index 0cee100b64e..115097e0d26 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -7,7 +7,8 @@ describe Gitlab::ProjectTemplate do described_class.new('rails', 'Ruby on Rails', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/rails'), described_class.new('spring', 'Spring', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/spring'), described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express'), - described_class.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'), + described_class.new('dotnetcore', '.NET Core', 'A .NET Core console application template, customizable for any .NET Core project', 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'), + described_class.new('gomicro', 'Go Micro', 'Go Micro is a framework for micro service development.', 'https://gitlab.com/gitlab-org/project-templates/go-micro'), described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'), described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'), described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'), -- cgit v1.2.1 From 316889cb4789e8a4a43bf0c79a4269643a97c336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 27 Feb 2019 01:22:51 +0100 Subject: Revert "Merge branch 'revert-8baf9e5f' into 'master'" This reverts commit f5201a816f2eff9393e16f362403451e5d86ee6c, reversing changes made to 48e6db0dad6f256e8423e0bd6c9b254803f50ccf. --- spec/factories/ci/group_variables.rb | 1 + spec/factories/ci/variables.rb | 1 + spec/features/group_variables_spec.rb | 2 +- spec/features/project_variables_spec.rb | 2 +- .../gitlab/ci/variables/collection/item_spec.rb | 20 ++- spec/lib/gitlab/ci/variables/collection_spec.rb | 4 +- spec/models/ci/build_spec.rb | 176 ++++++++++----------- spec/models/ci/group_variable_spec.rb | 1 + spec/models/ci/variable_spec.rb | 1 + spec/models/concerns/has_variable_spec.rb | 2 +- spec/models/concerns/maskable_spec.rb | 76 +++++++++ spec/requests/api/group_variables_spec.rb | 4 +- spec/requests/api/runner_spec.rb | 18 +-- spec/requests/api/variables_spec.rb | 4 +- .../features/variable_list_shared_examples.rb | 8 +- 15 files changed, 207 insertions(+), 113 deletions(-) create mode 100644 spec/models/concerns/maskable_spec.rb (limited to 'spec') diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb index 64716842b12..9bf520a2c0a 100644 --- a/spec/factories/ci/group_variables.rb +++ b/spec/factories/ci/group_variables.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :ci_group_variable, class: Ci::GroupVariable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' + masked false trait(:protected) do protected true diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index 3d014b9b54f..97a7c9ba252 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :ci_variable, class: Ci::Variable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' + masked false trait(:protected) do protected true diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 57e3ddfb39c..1a53e7c9512 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test value', group: group) } + let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) } let(:page_path) { group_settings_ci_cd_path(group) } before do diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index a93df3696d2..6bdf5df1036 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Project variables', :js do let(:user) { create(:user) } let(:project) { create(:project) } - let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } + let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') } let(:page_path) { project_settings_ci_cd_path(project) } before do diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index 8bf44acb228..3ff2fe18c15 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Variables::Collection::Item do let(:expected_value) { variable_value } let(:variable) do - { key: variable_key, value: variable_value, public: true } + { key: variable_key, value: variable_value, public: true, masked: false } end describe '.new' do @@ -88,7 +88,7 @@ describe Gitlab::Ci::Variables::Collection::Item do resource = described_class.fabricate(variable) expect(resource).to be_a(described_class) - expect(resource).to eq(key: 'CI_VAR', value: '123', public: false) + expect(resource).to eq(key: 'CI_VAR', value: '123', public: false, masked: false) end it 'supports using another collection item' do @@ -134,7 +134,21 @@ describe Gitlab::Ci::Variables::Collection::Item do .to_runner_variable expect(runner_variable) - .to eq(key: 'VAR', value: 'value', public: true, file: true) + .to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false) + end + end + + context 'when variable masking is disabled' do + before do + stub_feature_flags(variable_masking: false) + end + + it 'does not expose the masked field to the runner' do + runner_variable = described_class + .new(key: 'VAR', value: 'value', masked: true) + .to_runner_variable + + expect(runner_variable).to eq(key: 'VAR', value: 'value', public: true) end end end diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index 5c91816a586..edb209c0cf4 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Variables::Collection do describe '.new' do it 'can be initialized with an array' do - variable = { key: 'VAR', value: 'value', public: true } + variable = { key: 'VAR', value: 'value', public: true, masked: false } collection = described_class.new([variable]) @@ -93,7 +93,7 @@ describe Gitlab::Ci::Variables::Collection do collection = described_class.new([{ key: 'TEST', value: '1' }]) expect(collection.to_runner_variables) - .to eq [{ key: 'TEST', value: '1', public: true }] + .to eq [{ key: 'TEST', value: '1', public: true, masked: false }] end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 17540443688..81ff727b458 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2114,55 +2114,55 @@ describe Ci::Build do context 'returns variables' do let(:predefined_variables) do [ - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, - { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true }, - { key: 'CI_JOB_ID', value: build.id.to_s, public: true }, - { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true }, - { key: 'CI_JOB_TOKEN', value: 'my-token', public: false }, - { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, - { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false }, - { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, - { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false }, - { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }, - { key: 'CI', value: 'true', public: true }, - { key: 'GITLAB_CI', value: 'true', public: true }, - { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true }, - { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, - { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, - { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true }, - { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true }, - { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true }, - { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true }, - { key: 'CI_JOB_NAME', value: 'test', public: true }, - { key: 'CI_JOB_STAGE', value: 'test', public: true }, - { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, - { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true }, - { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true }, - { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true }, - { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, - { key: 'CI_NODE_TOTAL', value: '1', public: true }, - { key: 'CI_BUILD_REF', value: build.sha, public: true }, - { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, - { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true }, - { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true }, - { key: 'CI_BUILD_NAME', value: 'test', public: true }, - { key: 'CI_BUILD_STAGE', value: 'test', public: true }, - { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true }, - { key: 'CI_PROJECT_NAME', value: project.path, public: true }, - { key: 'CI_PROJECT_PATH', value: project.full_path, public: true }, - { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true }, - { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, - { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, - { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, - { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true }, - { key: 'CI_PAGES_URL', value: project.pages_url, public: true }, - { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true }, - { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true }, - { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, - { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }, - { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true }, - { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true }, - { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true } + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false }, + { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true, masked: false }, + { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false }, + { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false }, + { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: false }, + { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false }, + { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: false }, + { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false }, + { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: false }, + { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false }, + { key: 'CI', value: 'true', public: true, masked: false }, + { key: 'GITLAB_CI', value: 'true', public: true, masked: false }, + { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false }, + { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false }, + { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false }, + { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false }, + { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false }, + { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false }, + { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false }, + { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false }, + { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false }, + { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false }, + { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false }, + { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, + { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false }, + { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false }, + { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false }, + { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false }, + { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, + { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false }, + { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false }, + { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false }, + { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false }, + { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false }, + { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false }, + { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false }, + { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false }, + { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false }, + { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false }, + { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false }, + { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false }, + { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false }, + { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false }, + { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false }, + { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true, masked: false }, + { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false }, + { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, + { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, + { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false } ] end @@ -2175,10 +2175,10 @@ describe Ci::Build do describe 'variables ordering' do context 'when variables hierarchy is stubbed' do - let(:build_pre_var) { { key: 'build', value: 'value', public: true } } - let(:project_pre_var) { { key: 'project', value: 'value', public: true } } - let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true } } - let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true } } + let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } } + let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } } + let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } } + let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } } before do allow(build).to receive(:predefined_variables) { [build_pre_var] } @@ -2200,7 +2200,7 @@ describe Ci::Build do project_pre_var, pipeline_pre_var, build_yaml_var, - { key: 'secret', value: 'value', public: false }]) + { key: 'secret', value: 'value', public: false, masked: false }]) end end @@ -2233,10 +2233,10 @@ describe Ci::Build do context 'when build has user' do let(:user_variables) do [ - { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, - { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }, - { key: 'GITLAB_USER_LOGIN', value: user.username, public: true }, - { key: 'GITLAB_USER_NAME', value: user.name, public: true } + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true, masked: false }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true, masked: false }, + { key: 'GITLAB_USER_LOGIN', value: user.username, public: true, masked: false }, + { key: 'GITLAB_USER_NAME', value: user.name, public: true, masked: false } ] end @@ -2250,8 +2250,8 @@ describe Ci::Build do context 'when build has an environment' do let(:environment_variables) do [ - { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true }, - { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true } + { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false }, + { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false } ] end @@ -2286,7 +2286,7 @@ describe Ci::Build do before do environment_variables << - { key: 'CI_ENVIRONMENT_URL', value: url, public: true } + { key: 'CI_ENVIRONMENT_URL', value: url, public: true, masked: false } end context 'when the URL was set from the job' do @@ -2323,7 +2323,7 @@ describe Ci::Build do end let(:manual_variable) do - { key: 'CI_JOB_MANUAL', value: 'true', public: true } + { key: 'CI_JOB_MANUAL', value: 'true', public: true, masked: false } end it { is_expected.to include(manual_variable) } @@ -2331,7 +2331,7 @@ describe Ci::Build do context 'when build is for tag' do let(:tag_variable) do - { key: 'CI_COMMIT_TAG', value: 'master', public: true } + { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false } end before do @@ -2343,7 +2343,7 @@ describe Ci::Build do context 'when CI variable is defined' do let(:ci_variable) do - { key: 'SECRET_KEY', value: 'secret_value', public: false } + { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } end before do @@ -2358,7 +2358,7 @@ describe Ci::Build do let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } let(:protected_variable) do - { key: 'PROTECTED_KEY', value: 'protected_value', public: false } + { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } end before do @@ -2390,7 +2390,7 @@ describe Ci::Build do context 'when group CI variable is defined' do let(:ci_variable) do - { key: 'SECRET_KEY', value: 'secret_value', public: false } + { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } end before do @@ -2405,7 +2405,7 @@ describe Ci::Build do let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } let(:protected_variable) do - { key: 'PROTECTED_KEY', value: 'protected_value', public: false } + { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } end before do @@ -2444,11 +2444,11 @@ describe Ci::Build do let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) } let(:user_trigger_variable) do - { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false } + { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false } end let(:predefined_trigger_variable) do - { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true } + { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false } end before do @@ -2480,7 +2480,7 @@ describe Ci::Build do context 'when pipeline has a variable' do let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) } - it { is_expected.to include(pipeline_variable.to_runner_variable) } + it { is_expected.to include(key: pipeline_variable.key, value: pipeline_variable.value, public: false, masked: false) } end context 'when a job was triggered by a pipeline schedule' do @@ -2497,16 +2497,16 @@ describe Ci::Build do pipeline_schedule.reload end - it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) } + it { is_expected.to include(key: pipeline_schedule_variable.key, value: pipeline_schedule_variable.value, public: false, masked: false) } end context 'when container registry is enabled' do let(:container_registry_enabled) { true } let(:ci_registry) do - { key: 'CI_REGISTRY', value: 'registry.example.com', public: true } + { key: 'CI_REGISTRY', value: 'registry.example.com', public: true, masked: false } end let(:ci_registry_image) do - { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true } + { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true, masked: false } end context 'and is disabled for project' do @@ -2535,13 +2535,13 @@ describe Ci::Build do build.update(runner: runner) end - it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) } - it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) } - it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) } + it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true, masked: false }) } + it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true, masked: false }) } end context 'when build is for a deployment' do - let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } } + let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false, masked: false } } before do build.environment = 'production' @@ -2555,7 +2555,7 @@ describe Ci::Build do end context 'when project has custom CI config path' do - let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } } + let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } } before do project.update(ci_config_path: 'custom') @@ -2572,7 +2572,7 @@ describe Ci::Build do it "includes AUTO_DEVOPS_DOMAIN" do is_expected.to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }) + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) end end @@ -2583,7 +2583,7 @@ describe Ci::Build do it "includes AUTO_DEVOPS_DOMAIN" do is_expected.not_to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }) + { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) end end end @@ -2598,9 +2598,9 @@ describe Ci::Build do variables = subject.reverse.uniq { |variable| variable[:key] }.reverse expect(variables) - .not_to include(key: 'MYVAR', value: 'myvar', public: true) + .not_to include(key: 'MYVAR', value: 'myvar', public: true, masked: false) expect(variables) - .to include(key: 'MYVAR', value: 'pipeline value', public: false) + .to include(key: 'MYVAR', value: 'pipeline value', public: false, masked: false) end end @@ -2616,13 +2616,13 @@ describe Ci::Build do it 'includes CI_NODE_INDEX' do is_expected.to include( - { key: 'CI_NODE_INDEX', value: index.to_s, public: true } + { key: 'CI_NODE_INDEX', value: index.to_s, public: true, masked: false } ) end it 'includes correct CI_NODE_TOTAL' do is_expected.to include( - { key: 'CI_NODE_TOTAL', value: total.to_s, public: true } + { key: 'CI_NODE_TOTAL', value: total.to_s, public: true, masked: false } ) end end @@ -2641,7 +2641,7 @@ describe Ci::Build do it 'returns static predefined variables' do expect(build.variables.size).to be >= 28 expect(build.variables) - .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true) + .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) expect(build).not_to be_persisted end end @@ -2651,8 +2651,8 @@ describe Ci::Build do let(:deploy_token_variables) do [ - { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true }, - { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false } + { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true, masked: false }, + { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false, masked: false } ] end @@ -2711,7 +2711,7 @@ describe Ci::Build do end expect(variables) - .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true) + .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) end it 'does not return prohibited variables' do diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index 1b10501701c..21d96bf3454 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -5,6 +5,7 @@ describe Ci::GroupVariable do it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Presentable) } + it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) } describe '.unprotected' do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 875e8b2b682..02c07a2bd83 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -6,6 +6,7 @@ describe Ci::Variable do describe 'validations' do it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Presentable) } + it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) } end diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb index 3fbe86c5b56..bff96e12ffa 100644 --- a/spec/models/concerns/has_variable_spec.rb +++ b/spec/models/concerns/has_variable_spec.rb @@ -57,7 +57,7 @@ describe HasVariable do describe '#to_runner_variable' do it 'returns a hash for the runner' do expect(subject.to_runner_variable) - .to eq(key: subject.key, value: subject.value, public: false) + .to include(key: subject.key, value: subject.value, public: false) end end end diff --git a/spec/models/concerns/maskable_spec.rb b/spec/models/concerns/maskable_spec.rb new file mode 100644 index 00000000000..aeba7ad862f --- /dev/null +++ b/spec/models/concerns/maskable_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Maskable do + let(:variable) { build(:ci_variable) } + + describe 'masked value validations' do + subject { variable } + + context 'when variable is masked' do + before do + subject.masked = true + end + + it { is_expected.not_to allow_value('hello').for(:value) } + it { is_expected.not_to allow_value('hello world').for(:value) } + it { is_expected.not_to allow_value('hello$VARIABLEworld').for(:value) } + it { is_expected.not_to allow_value('hello\rworld').for(:value) } + it { is_expected.to allow_value('helloworld').for(:value) } + end + + context 'when variable is not masked' do + before do + subject.masked = false + end + + it { is_expected.to allow_value('hello').for(:value) } + it { is_expected.to allow_value('hello world').for(:value) } + it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) } + it { is_expected.to allow_value('hello\rworld').for(:value) } + it { is_expected.to allow_value('helloworld').for(:value) } + end + end + + describe 'REGEX' do + subject { Maskable::REGEX } + + it 'does not match strings shorter than 8 letters' do + expect(subject.match?('hello')).to eq(false) + end + + it 'does not match strings with spaces' do + expect(subject.match?('hello world')).to eq(false) + end + + it 'does not match strings with shell variables' do + expect(subject.match?('hello$VARIABLEworld')).to eq(false) + end + + it 'does not match strings with escape characters' do + expect(subject.match?('hello\rworld')).to eq(false) + end + + it 'does not match strings that span more than one line' do + string = <<~EOS + hello + world + EOS + + expect(subject.match?(string)).to eq(false) + end + + it 'matches valid strings' do + expect(subject.match?('helloworld')).to eq(true) + end + end + + describe '#to_runner_variable' do + subject { variable.to_runner_variable } + + it 'exposes the masked attribute' do + expect(subject).to include(:masked) + end + end +end diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index e52f4c70407..66b9aae4b58 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -87,12 +87,12 @@ describe API::GroupVariables do it 'creates variable' do expect do - post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } + post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true } end.to change {group.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') - expect(json_response['value']).to eq('VALUE_2') + expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index e6c235ca26e..43c06f7c973 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -436,9 +436,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end let(:expected_variables) do - [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, - { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, - { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }] + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false }, + { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }] end let(:expected_artifacts) do @@ -740,12 +740,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when triggered job is available' do let(:expected_variables) do - [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true }, - { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true }, - { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true }, - { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }, - { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false }, - { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }] + [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, + { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false }, + { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true, 'masked' => false }, + { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }, + { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false, 'masked' => false }, + { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false, 'masked' => false }] end let(:trigger) { create(:ci_trigger, project: project) } diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index cdac5b2f400..5df6baf0ddf 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -73,12 +73,12 @@ describe API::Variables do context 'authorized user with proper permissions' do it 'creates variable' do expect do - post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true } + post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true } end.to change {project.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') - expect(json_response['value']).to eq('VALUE_2') + expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy end diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 0a464d77cb7..73156d18c1b 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -8,7 +8,7 @@ shared_examples 'variable list' do it 'adds new CI variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('key value') + find('.js-ci-variable-input-value').set('key_value') end click_button('Save variables') @@ -19,7 +19,7 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') end end @@ -44,7 +44,7 @@ shared_examples 'variable list' do it 'adds new protected variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('key value') + find('.js-ci-variable-input-value').set('key_value') find('.ci-variable-protected-item .js-project-feature-toggle').click expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -58,7 +58,7 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end end -- cgit v1.2.1 From 8e5911353bcb48155dc5dffab6a732bbba49cd37 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 27 Feb 2019 12:44:24 +0000 Subject: Use Web IDE path for merge request edit buttons --- spec/features/merge_request/maintainer_edits_fork_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb index 7839b97122c..aead98dae23 100644 --- a/spec/features/merge_request/maintainer_edits_fork_spec.rb +++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb @@ -18,6 +18,8 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js end before do + stub_feature_flags(web_ide_default: false) + target_project.add_maintainer(user) sign_in(user) -- cgit v1.2.1 From a565f3d88d9c2e38b615378298b8db1f306ebd53 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Mon, 25 Feb 2019 17:19:36 +0900 Subject: Fix misspellings in app/spec executable lines Signed-off-by: Takuya Noguchi --- .../merge_request/user_posts_diff_notes_spec.rb | 2 +- spec/fixtures/trace/sample_trace | 18 +++++++++--------- spec/javascripts/badges/store/actions_spec.js | 4 ++-- spec/javascripts/notes/stores/mutation_spec.js | 4 ++-- spec/lib/gitlab/current_settings_spec.rb | 2 +- .../v1/rename_base_spec.rb | 2 +- .../prometheus/queries/matched_metric_query_spec.rb | 4 ++-- spec/lib/object_storage/direct_upload_spec.rb | 2 +- spec/models/ci/build_trace_chunk_spec.rb | 4 ++-- spec/models/concerns/sortable_spec.rb | 2 +- spec/serializers/provider_repo_entity_spec.rb | 2 +- spec/services/notification_service_spec.rb | 2 +- 12 files changed, 24 insertions(+), 24 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb index 51b78d3e7d1..19edce1b562 100644 --- a/spec/features/merge_request/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb @@ -178,7 +178,7 @@ describe 'Merge request > User posts diff notes', :js do end end - describe 'with muliple note forms' do + describe 'with multiple note forms' do before do visit diffs_project_merge_request_path(project, merge_request, view: 'inline') click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace index 3d8beb0dec2..8f9747f8143 100644 --- a/spec/fixtures/trace/sample_trace +++ b/spec/fixtures/trace/sample_trace @@ -1795,7 +1795,7 @@ GroupsController when requesting a redirected path returns not found PUT transfer - when transfering to a subgroup goes right + when transferring to a subgroup goes right should return a notice (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) should redirect to the new path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) when converting to a root group goes right @@ -2299,7 +2299,7 @@ Groups::TransferService should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) - when transfering a group with nested groups and projects + when transferring a group with nested groups and projects should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example) @@ -2426,9 +2426,9 @@ Groups::MilestonesController lists legacy group milestones and group milestones #show when there is a title parameter - searchs for a legacy group milestone + searches for a legacy group milestone when there is not a title parameter - searchs for a group milestone + searches for a group milestone behaves like milestone tabs #merge_requests as html @@ -3109,11 +3109,11 @@ Pending: (Failures listed here are expected and do not affect your suite's statu # around hook at ./spec/spec_helper.rb:186 did not execute the example # ./spec/controllers/groups_controller_spec.rb:129 - 15) GroupsController PUT transfer when transfering to a subgroup goes right should return a notice + 15) GroupsController PUT transfer when transferring to a subgroup goes right should return a notice # around hook at ./spec/spec_helper.rb:190 did not execute the example # ./spec/controllers/groups_controller_spec.rb:516 - 16) GroupsController PUT transfer when transfering to a subgroup goes right should redirect to the new path + 16) GroupsController PUT transfer when transferring to a subgroup goes right should redirect to the new path # around hook at ./spec/spec_helper.rb:190 did not execute the example # ./spec/controllers/groups_controller_spec.rb:520 @@ -3301,15 +3301,15 @@ Pending: (Failures listed here are expected and do not affect your suite's statu # around hook at ./spec/spec_helper.rb:190 did not execute the example # ./spec/services/groups/transfer_service_spec.rb:341 - 63) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update subgroups path + 63) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should update subgroups path # around hook at ./spec/spec_helper.rb:190 did not execute the example # ./spec/services/groups/transfer_service_spec.rb:363 - 64) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update projects path + 64) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should update projects path # around hook at ./spec/spec_helper.rb:190 did not execute the example # ./spec/services/groups/transfer_service_spec.rb:375 - 65) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should create redirect for the subgroups and projects + 65) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should create redirect for the subgroups and projects # around hook at ./spec/spec_helper.rb:190 did not execute the example # ./spec/services/groups/transfer_service_spec.rb:383 diff --git a/spec/javascripts/badges/store/actions_spec.js b/spec/javascripts/badges/store/actions_spec.js index 2623465ebd6..e8d5f8c3aac 100644 --- a/spec/javascripts/badges/store/actions_spec.js +++ b/spec/javascripts/badges/store/actions_spec.js @@ -411,7 +411,7 @@ describe('Badges store actions', () => { it('escapes user input', done => { spyOn(axios, 'get').and.callFake(() => Promise.resolve({ data: createDummyBadgeResponse() })); - badgeInForm.imageUrl = '&make-sandwhich=true'; + badgeInForm.imageUrl = '&make-sandwich=true'; badgeInForm.linkUrl = ''; actions @@ -422,7 +422,7 @@ describe('Badges store actions', () => { expect(url).toMatch(`^${dummyEndpointUrl}/render?`); expect(url).toMatch('\\?link_url=%3Cscript%3EI%20am%20dangerous!%3C%2Fscript%3E&'); - expect(url).toMatch('&image_url=%26make-sandwhich%3Dtrue$'); + expect(url).toMatch('&image_url=%26make-sandwich%3Dtrue$'); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index fcad1f245b6..aa08bf59a7f 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -530,7 +530,7 @@ describe('Notes Store mutations', () => { state = { convertedDisscussionIds: [] }; }); - it('adds a disucssion to convertedDisscussionIds', () => { + it('adds a discussion to convertedDisscussionIds', () => { mutations.CONVERT_TO_DISCUSSION(state, discussion.id); expect(state.convertedDisscussionIds).toContain(discussion.id); @@ -549,7 +549,7 @@ describe('Notes Store mutations', () => { state = { convertedDisscussionIds: [41, 42] }; }); - it('removes a disucssion from convertedDisscussionIds', () => { + it('removes a discussion from convertedDisscussionIds', () => { mutations.REMOVE_CONVERTED_DISCUSSION(state, discussion.id); expect(state.convertedDisscussionIds).not.toContain(discussion.id); diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index caf9fc5442c..17d5eae24f5 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -143,7 +143,7 @@ describe Gitlab::CurrentSettings do it_behaves_like 'a non-persisted ApplicationSetting object' - it 'uses the value from the DB attribute if present and not overriden by an accessor' do + it 'uses the value from the DB attribute if present and not overridden by an accessor' do expect(current_settings.home_page_url).to eq(db_settings.home_page_url) end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 248cca25a2c..81419e51635 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete Project.find(project.id) end - describe "#remove_last_ocurrence" do + describe "#remove_last_occurrence" do it "removes only the last occurrence of a string" do input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace" diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb index 420218a695a..936447b8474 100644 --- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::Prometheus::Queries::MatchedMetricQuery do [{ '__name__' => 'metric_a' }, { '__name__' => 'metric_b' }] end - let(:partialy_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] } + let(:partially_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] } let(:empty_series_info) { [] } let(:client) { double('prometheus_client') } @@ -60,7 +60,7 @@ describe Gitlab::Prometheus::Queries::MatchedMetricQuery do context 'one of the series info was not found' do before do - allow(client).to receive(:series).and_return(partialy_empty_series_info) + allow(client).to receive(:series).and_return(partially_empty_series_info) end it 'responds with one active and one missing metric' do expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 1 }]) diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb index 1024e1a25ea..8ccbd90ddb8 100644 --- a/spec/lib/object_storage/direct_upload_spec.rb +++ b/spec/lib/object_storage/direct_upload_spec.rb @@ -121,7 +121,7 @@ describe ObjectStorage::DirectUpload do expect(subject[:MultipartUpload][:PartURLs].length).to eq(2) end - it 'part size is mimimum, 5MB' do + it 'part size is minimum, 5MB' do expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte) end end diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index d214fdf369a..59db347582b 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -171,7 +171,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end shared_examples_for 'Scheduling sidekiq worker to flush data to persist store' do - context 'when new data fullfilled chunk size' do + context 'when new data fulfilled chunk size' do let(:new_data) { 'a' * described_class::CHUNK_SIZE } it 'schedules trace chunk flush worker' do @@ -193,7 +193,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end shared_examples_for 'Scheduling no sidekiq worker' do - context 'when new data fullfilled chunk size' do + context 'when new data fulfilled chunk size' do let(:new_data) { 'a' * described_class::CHUNK_SIZE } it 'does not schedule trace chunk flush worker' do diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb index 39c16ae60af..0a9d2021a19 100644 --- a/spec/models/concerns/sortable_spec.rb +++ b/spec/models/concerns/sortable_spec.rb @@ -99,7 +99,7 @@ describe Sortable do expect(ordered_group_names('id_desc')).to eq(%w(bbb BB AAA aa)) end - it 'sorts groups by name via case-insentitive comparision' do + it 'sorts groups by name via case-insensitive comparision' do expect(ordered_group_names('name_asc')).to eq(%w(aa AAA BB bbb)) expect(ordered_group_names('name_desc')).to eq(%w(bbb BB AAA aa)) end diff --git a/spec/serializers/provider_repo_entity_spec.rb b/spec/serializers/provider_repo_entity_spec.rb index b67115bab10..9a1160d16d5 100644 --- a/spec/serializers/provider_repo_entity_spec.rb +++ b/spec/serializers/provider_repo_entity_spec.rb @@ -13,7 +13,7 @@ describe ProviderRepoEntity do describe '#as_json' do subject { entity.as_json } - it 'includes requried fields' do + it 'includes required fields' do expect(subject[:id]).to eq(provider_repo[:id]) expect(subject[:full_name]).to eq(provider_repo[:full_name]) expect(subject[:owner_name]).to eq(provider_repo[:owner][:login]) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 6a5a6989607..9ba4a11104a 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -177,7 +177,7 @@ describe NotificationService, :mailer do end end - context 'when recieving a non-existent method' do + context 'when receiving a non-existent method' do it 'raises NoMethodError' do expect { async.foo(key) }.to raise_error(NoMethodError) end -- cgit v1.2.1 From d3accd36a0fda4014979c916ef1c0a3875a30199 Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Mon, 11 Jun 2018 18:04:46 +0200 Subject: add tag_list param to runners api --- spec/requests/api/runners_spec.rb | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 5ca442bc448..5548e3fd01a 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -90,6 +90,17 @@ describe API::Runners do expect(response).to have_gitlab_http_status(400) end + + it 'filters runners by tag_list' do + create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2]) + create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2']) + + get api('/runners?tag_list=tag1,tag2', user) + + expect(json_response).to match_array [ + a_hash_including('description' => 'Runner tagged with tag1 and tag2') + ] + end end context 'unauthorized user' do @@ -181,6 +192,17 @@ describe API::Runners do expect(response).to have_gitlab_http_status(400) end + + it 'filters runners by tag_list' do + create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2]) + create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2']) + + get api('/runners/all?tag_list=tag1,tag2', admin) + + expect(json_response).to match_array [ + a_hash_including('description' => 'Runner tagged with tag1 and tag2') + ] + end end context 'without admin privileges' do @@ -716,6 +738,17 @@ describe API::Runners do expect(response).to have_gitlab_http_status(400) end + + it 'filters runners by tag_list' do + create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2]) + create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2']) + + get api("/projects/#{project.id}/runners?tag_list=tag1,tag2", user) + + expect(json_response).to match_array [ + a_hash_including('description' => 'Runner tagged with tag1 and tag2') + ] + end end context 'authorized user without maintainer privileges' do -- cgit v1.2.1 From 0fad7aa79c7925391b3586187a6da464bffbd49d Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Wed, 13 Jun 2018 11:18:47 +0200 Subject: add tag filter to admin runners page --- spec/features/admin/admin_runners_spec.rb | 50 +++++++++++++++++++++++++ spec/finders/admin/runners_finder_spec.rb | 8 ++++ spec/services/autocomplete_tags_service_spec.rb | 17 +++++++++ 3 files changed, 75 insertions(+) create mode 100644 spec/services/autocomplete_tags_service_spec.rb (limited to 'spec') diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index ed9c0ea9ac0..97b432a6751 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -141,6 +141,56 @@ describe "Admin Runners" do end end + describe 'filter by tag', :js do + it 'shows correct runner when tag matches' do + create :ci_runner, description: 'runner-blue', tag_list: ['blue'] + create :ci_runner, description: 'runner-red', tag_list: ['red'] + + visit admin_runners_path + + expect(page).to have_content 'runner-blue' + expect(page).to have_content 'runner-red' + + input_filtered_search_keys('tag:blue') + + expect(page).to have_content 'runner-blue' + expect(page).not_to have_content 'runner-red' + end + + it 'shows no runner when tag does not match' do + create :ci_runner, description: 'runner-blue', tag_list: ['blue'] + create :ci_runner, description: 'runner-red', tag_list: ['blue'] + + visit admin_runners_path + + input_filtered_search_keys('tag:red') + + expect(page).not_to have_content 'runner-blue' + expect(page).not_to have_content 'runner-blue' + expect(page).to have_text 'No runners found' + end + + it 'shows correct runner when tag is selected and search term is entered' do + create :ci_runner, description: 'runner-a-1', tag_list: ['blue'] + create :ci_runner, description: 'runner-a-2', tag_list: ['red'] + create :ci_runner, description: 'runner-b-1', tag_list: ['blue'] + + visit admin_runners_path + + input_filtered_search_keys('tag:blue') + + expect(page).to have_content 'runner-a-1' + expect(page).to have_content 'runner-b-1' + expect(page).not_to have_content 'runner-a-2' + + input_filtered_search_keys('tag:blue runner-a') + + expect(page).to have_content 'runner-a-1' + expect(page).not_to have_content 'runner-b-1' + expect(page).not_to have_content 'runner-a-2' + end + end + it 'sorts by last contact date', :js do create(:ci_runner, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37') create(:ci_runner, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37') diff --git a/spec/finders/admin/runners_finder_spec.rb b/spec/finders/admin/runners_finder_spec.rb index 0b2325cc7ca..94ccb398801 100644 --- a/spec/finders/admin/runners_finder_spec.rb +++ b/spec/finders/admin/runners_finder_spec.rb @@ -37,6 +37,14 @@ describe Admin::RunnersFinder do end end + context 'filter by tag_name' do + it 'calls the corresponding scope on Ci::Runner' do + expect(Ci::Runner).to receive(:tagged_with).with(%w[tag1 tag2]).and_call_original + + described_class.new(params: { tag_name: %w[tag1 tag2] }).execute + end + end + context 'sort' do context 'without sort param' do it 'sorts by created_at' do diff --git a/spec/services/autocomplete_tags_service_spec.rb b/spec/services/autocomplete_tags_service_spec.rb new file mode 100644 index 00000000000..ff7128baa24 --- /dev/null +++ b/spec/services/autocomplete_tags_service_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe AutocompleteTagsService do + describe '#run' do + it 'returns a hash of all tags' do + create(:ci_runner, tag_list: %w[tag1 tag2]) + create(:ci_runner, tag_list: %w[tag1 tag3]) + create(:project, tag_list: %w[tag4]) + + expect(described_class.new(Ci::Runner).run).to match_array [ + { id: kind_of(Integer), title: 'tag1' }, + { id: kind_of(Integer), title: 'tag2' }, + { id: kind_of(Integer), title: 'tag3' } + ] + end + end +end -- cgit v1.2.1 From 2e05292562e71deeff9b76bd3c696eca2a65a491 Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Thu, 15 Nov 2018 16:10:10 +0100 Subject: use lazy ajax filter dropdown for runner tags the potential number of available runner tags is too large to load it statically to a dropdown. we use the same lazy loaded dropdown as is used for the users dropdown already. --- .../acts_as_taggable_on/tags_finder_spec.rb | 66 ++++++++++++++++++++++ spec/services/autocomplete_tags_service_spec.rb | 17 ------ 2 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb delete mode 100644 spec/services/autocomplete_tags_service_spec.rb (limited to 'spec') diff --git a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb new file mode 100644 index 00000000000..9d1fac20362 --- /dev/null +++ b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Autocomplete::ActsAsTaggableOn::TagsFinder do + describe '#execute' do + context 'with empty params' do + it 'returns all tags' do + create :ci_runner, tag_list: ['tag1'] + create :ci_runner, tag_list: ['tag2'] + + tags = described_class.new(taggable_type: Ci::Runner, params: {}).execute.map(&:name) + + expect(tags).to match_array %w(tag1 tag2) + end + end + + context 'filter by search' do + context 'with an empty search term' do + it 'returns an empty collection' do + create :ci_runner, tag_list: ['tag1'] + create :ci_runner, tag_list: ['tag2'] + + tags = described_class.new(taggable_type: Ci::Runner, params: { search: '' }).execute.map(&:name) + + expect(tags).to be_empty + end + end + + context 'with a search containing 2 characters' do + it 'returns the tag that strictly matches the search term' do + create :ci_runner, tag_list: ['t1'] + create :ci_runner, tag_list: ['t11'] + + tags = described_class.new(taggable_type: Ci::Runner, params: { search: 't1' }).execute.map(&:name) + + expect(tags).to match_array ['t1'] + end + end + + context 'with a search containing 3 characters' do + it 'returns the tag that partially matches the search term' do + create :ci_runner, tag_list: ['tag1'] + create :ci_runner, tag_list: ['tag11'] + + tags = described_class.new(taggable_type: Ci::Runner, params: { search: 'ag1' }).execute.map(&:name) + + expect(tags).to match_array %w(tag1 tag11) + end + end + end + + context 'limit' do + it 'limits the result set by the limit constant' do + stub_const("#{described_class}::LIMIT", 1) + + create :ci_runner, tag_list: ['tag1'] + create :ci_runner, tag_list: ['tag2'] + + tags = described_class.new(taggable_type: Ci::Runner, params: { search: 'tag' }).execute + + expect(tags.count).to eq 1 + end + end + end +end diff --git a/spec/services/autocomplete_tags_service_spec.rb b/spec/services/autocomplete_tags_service_spec.rb deleted file mode 100644 index ff7128baa24..00000000000 --- a/spec/services/autocomplete_tags_service_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'rails_helper' - -RSpec.describe AutocompleteTagsService do - describe '#run' do - it 'returns a hash of all tags' do - create(:ci_runner, tag_list: %w[tag1 tag2]) - create(:ci_runner, tag_list: %w[tag1 tag3]) - create(:project, tag_list: %w[tag4]) - - expect(described_class.new(Ci::Runner).run).to match_array [ - { id: kind_of(Integer), title: 'tag1' }, - { id: kind_of(Integer), title: 'tag2' }, - { id: kind_of(Integer), title: 'tag3' } - ] - end - end -end -- cgit v1.2.1 From 022ee0c0c91d30dea3c1c0472525e86ec8379827 Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Tue, 11 Dec 2018 09:43:34 +0100 Subject: don't filter tags by taggable type Due to performance reasons we cannot use the type filter on the tags. The table for ActsAsTaggableOn is too big and too unoptimized, such that the queries time out on production. See the discussion https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19740#note_120087938 for more info. --- .../acts_as_taggable_on/tags_finder_spec.rb | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'spec') diff --git a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb index 9d1fac20362..857f6bba7e6 100644 --- a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb +++ b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb @@ -6,10 +6,10 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do describe '#execute' do context 'with empty params' do it 'returns all tags' do - create :ci_runner, tag_list: ['tag1'] - create :ci_runner, tag_list: ['tag2'] + ActsAsTaggableOn::Tag.create!(name: 'tag1') + ActsAsTaggableOn::Tag.create!(name: 'tag2') - tags = described_class.new(taggable_type: Ci::Runner, params: {}).execute.map(&:name) + tags = described_class.new(params: {}).execute.map(&:name) expect(tags).to match_array %w(tag1 tag2) end @@ -18,10 +18,10 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do context 'filter by search' do context 'with an empty search term' do it 'returns an empty collection' do - create :ci_runner, tag_list: ['tag1'] - create :ci_runner, tag_list: ['tag2'] + ActsAsTaggableOn::Tag.create!(name: 'tag1') + ActsAsTaggableOn::Tag.create!(name: 'tag2') - tags = described_class.new(taggable_type: Ci::Runner, params: { search: '' }).execute.map(&:name) + tags = described_class.new(params: { search: '' }).execute.map(&:name) expect(tags).to be_empty end @@ -29,10 +29,10 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do context 'with a search containing 2 characters' do it 'returns the tag that strictly matches the search term' do - create :ci_runner, tag_list: ['t1'] - create :ci_runner, tag_list: ['t11'] + ActsAsTaggableOn::Tag.create!(name: 't1') + ActsAsTaggableOn::Tag.create!(name: 't11') - tags = described_class.new(taggable_type: Ci::Runner, params: { search: 't1' }).execute.map(&:name) + tags = described_class.new(params: { search: 't1' }).execute.map(&:name) expect(tags).to match_array ['t1'] end @@ -40,10 +40,10 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do context 'with a search containing 3 characters' do it 'returns the tag that partially matches the search term' do - create :ci_runner, tag_list: ['tag1'] - create :ci_runner, tag_list: ['tag11'] + ActsAsTaggableOn::Tag.create!(name: 'tag1') + ActsAsTaggableOn::Tag.create!(name: 'tag11') - tags = described_class.new(taggable_type: Ci::Runner, params: { search: 'ag1' }).execute.map(&:name) + tags = described_class.new(params: { search: 'ag1' }).execute.map(&:name) expect(tags).to match_array %w(tag1 tag11) end @@ -54,10 +54,10 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do it 'limits the result set by the limit constant' do stub_const("#{described_class}::LIMIT", 1) - create :ci_runner, tag_list: ['tag1'] - create :ci_runner, tag_list: ['tag2'] + ActsAsTaggableOn::Tag.create!(name: 'tag1') + ActsAsTaggableOn::Tag.create!(name: 'tag2') - tags = described_class.new(taggable_type: Ci::Runner, params: { search: 'tag' }).execute + tags = described_class.new(params: { search: 'tag' }).execute expect(tags.count).to eq 1 end -- cgit v1.2.1 From 0853c234fc39dcfd70175d86990089712c462d58 Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Thu, 21 Feb 2019 20:26:54 +0100 Subject: make assertions on objects instead of tag names --- .../acts_as_taggable_on/tags_finder_spec.rb | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb index 857f6bba7e6..79d2f9cdb45 100644 --- a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb +++ b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb @@ -6,12 +6,12 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do describe '#execute' do context 'with empty params' do it 'returns all tags' do - ActsAsTaggableOn::Tag.create!(name: 'tag1') - ActsAsTaggableOn::Tag.create!(name: 'tag2') + tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1') + tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag2') - tags = described_class.new(params: {}).execute.map(&:name) + tags = described_class.new(params: {}).execute - expect(tags).to match_array %w(tag1 tag2) + expect(tags).to match_array [tag1, tag2] end end @@ -21,7 +21,7 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do ActsAsTaggableOn::Tag.create!(name: 'tag1') ActsAsTaggableOn::Tag.create!(name: 'tag2') - tags = described_class.new(params: { search: '' }).execute.map(&:name) + tags = described_class.new(params: { search: '' }).execute expect(tags).to be_empty end @@ -29,23 +29,23 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do context 'with a search containing 2 characters' do it 'returns the tag that strictly matches the search term' do - ActsAsTaggableOn::Tag.create!(name: 't1') + tag1 = ActsAsTaggableOn::Tag.create!(name: 't1') ActsAsTaggableOn::Tag.create!(name: 't11') - tags = described_class.new(params: { search: 't1' }).execute.map(&:name) + tags = described_class.new(params: { search: 't1' }).execute - expect(tags).to match_array ['t1'] + expect(tags).to match_array [tag1] end end context 'with a search containing 3 characters' do it 'returns the tag that partially matches the search term' do - ActsAsTaggableOn::Tag.create!(name: 'tag1') - ActsAsTaggableOn::Tag.create!(name: 'tag11') + tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1') + tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag11') - tags = described_class.new(params: { search: 'ag1' }).execute.map(&:name) + tags = described_class.new(params: { search: 'ag1' }).execute - expect(tags).to match_array %w(tag1 tag11) + expect(tags).to match_array [tag1, tag2] end end end -- cgit v1.2.1 From 9c498b10ee07388d36df468d9286631ff98c00b1 Mon Sep 17 00:00:00 2001 From: Diana Stanley Date: Wed, 27 Feb 2019 11:39:27 -0800 Subject: Move check for nil due_on into build section of spec --- .../github_import/importer/milestones_importer_spec.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb index 35c37f9499d..120a07ff2b3 100644 --- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb @@ -87,6 +87,7 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis describe '#build' do let(:milestone_hash) { importer.build(milestone) } + let(:milestone_hash2) { importer.build(milestone2) } it 'returns the attributes of the milestone as a Hash' do expect(milestone_hash).to be_an_instance_of(Hash) @@ -117,6 +118,10 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis expect(milestone_hash[:due_date]).to eq(due_on.to_date) end + it 'responds correctly to no due date value' do + expect(milestone_hash2[:due_date]).to be nil + end + it 'includes the created timestamp' do expect(milestone_hash[:created_at]).to eq(created_at) end @@ -127,14 +132,6 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis end end - describe '#nil_due_on' do - let(:milestone_hash) { importer.build(milestone2) } - - it 'should handle missing due_on correctly' do - expect(milestone_hash[:due_date]).to be nil - end - end - describe '#each_milestone' do it 'returns the milestones' do expect(client) -- cgit v1.2.1 From 77985826d94454514c40b8da926e13b3b3791841 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 20 Feb 2019 17:18:15 +0200 Subject: Show header and footer system messages in email * Add email_header_and_footer_enabled flag to appearances table * Set email_header_and_footer_enabled default value to false * Add checkbox to appearance to toggle show header and footer in emails * Add email_header_and_footer_enabled to allowed params in controller * Add header and footer messages to the html and text email layouts * Remove the color styling for emails header and footer * Add empty_mailer layout for emails without layout, to have the header and footer applied --- .../admin/appearances_controller_spec.rb | 48 +++++++-- spec/helpers/emails_helper_spec.rb | 54 +++++++++++ spec/mailers/abuse_report_mailer_spec.rb | 25 +++-- spec/mailers/email_rejection_mailer_spec.rb | 16 +++ spec/mailers/emails/auto_devops_spec.rb | 3 + spec/mailers/emails/issues_spec.rb | 9 ++ spec/mailers/notify_spec.rb | 108 +++++++++++++++++++++ spec/mailers/repository_check_mailer_spec.rb | 7 ++ spec/models/appearance_spec.rb | 18 ++++ .../shared_examples/notify_shared_examples.rb | 28 ++++++ 10 files changed, 297 insertions(+), 19 deletions(-) create mode 100644 spec/mailers/email_rejection_mailer_spec.rb (limited to 'spec') diff --git a/spec/controllers/admin/appearances_controller_spec.rb b/spec/controllers/admin/appearances_controller_spec.rb index 4ddd0953267..621aa148301 100644 --- a/spec/controllers/admin/appearances_controller_spec.rb +++ b/spec/controllers/admin/appearances_controller_spec.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require 'spec_helper' describe Admin::AppearancesController do let(:admin) { create(:admin) } - let(:header_message) { "Header message" } - let(:footer_message) { "Footer" } + let(:header_message) { 'Header message' } + let(:footer_message) { 'Footer' } describe 'POST #create' do let(:create_params) do { - title: "Foo", - description: "Bar", + title: 'Foo', + description: 'Bar', header_message: header_message, footer_message: footer_message } @@ -24,9 +26,26 @@ describe Admin::AppearancesController do expect(Appearance.current).to have_attributes( header_message: header_message, - footer_message: footer_message + footer_message: footer_message, + email_header_and_footer_enabled: false, + message_background_color: '#E75E40', + message_font_color: '#FFFFFF' ) end + + context 'when enabling header and footer in email' do + it 'creates appearance with enabled flag' do + create_params[:email_header_and_footer_enabled] = true + + post :create, params: { appearance: create_params } + + expect(Appearance.current).to have_attributes( + header_message: header_message, + footer_message: footer_message, + email_header_and_footer_enabled: true + ) + end + end end describe 'PUT #update' do @@ -48,8 +67,25 @@ describe Admin::AppearancesController do expect(Appearance.current).to have_attributes( header_message: header_message, - footer_message: footer_message + footer_message: footer_message, + email_header_and_footer_enabled: false, + message_background_color: '#E75E40', + message_font_color: '#FFFFFF' ) end + + context 'when enabling header and footer in email' do + it 'updates appearance with enabled flag' do + update_params[:email_header_and_footer_enabled] = true + + post :update, params: { appearance: update_params } + + expect(Appearance.current).to have_attributes( + header_message: header_message, + footer_message: footer_message, + email_header_and_footer_enabled: true + ) + end + end end end diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index 23d7e41803e..03b4c19ec22 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -142,4 +142,58 @@ describe EmailsHelper do end end end + + describe 'header and footer messages' do + context 'when email_header_and_footer_enabled is enabled' do + it 'returns header and footer messages' do + create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: true + + aggregate_failures do + expect(html_header_message).to eq(%{

    Foo

    }) + expect(html_footer_message).to eq(%{}) + expect(text_header_message).to eq('Foo') + expect(text_footer_message).to eq('Bar') + end + end + + context 'when header and footer messages are empty' do + it 'returns nil' do + create :appearance, header_message: '', footer_message: '', email_header_and_footer_enabled: true + + aggregate_failures do + expect(html_header_message).to eq(nil) + expect(html_footer_message).to eq(nil) + expect(text_header_message).to eq(nil) + expect(text_footer_message).to eq(nil) + end + end + end + + context 'when header and footer messages are nil' do + it 'returns nil' do + create :appearance, header_message: nil, footer_message: nil, email_header_and_footer_enabled: true + + aggregate_failures do + expect(html_header_message).to eq(nil) + expect(html_footer_message).to eq(nil) + expect(text_header_message).to eq(nil) + expect(text_footer_message).to eq(nil) + end + end + end + end + + context 'when email_header_and_footer_enabled is disabled' do + it 'returns header and footer messages' do + create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: false + + aggregate_failures do + expect(html_header_message).to eq(nil) + expect(html_footer_message).to eq(nil) + expect(text_header_message).to eq(nil) + expect(text_footer_message).to eq(nil) + end + end + end + end end diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb index bda892083b3..f96870cc112 100644 --- a/spec/mailers/abuse_report_mailer_spec.rb +++ b/spec/mailers/abuse_report_mailer_spec.rb @@ -4,25 +4,24 @@ describe AbuseReportMailer do include EmailSpec::Matchers describe '.notify' do - context 'with admin_notification_email set' do - before do - stub_application_setting(admin_notification_email: 'admin@example.com') - end + before do + stub_application_setting(admin_notification_email: 'admin@example.com') + end - it 'sends to the admin_notification_email' do - report = create(:abuse_report) + let(:report) { create(:abuse_report) } + + subject { described_class.notify(report.id) } - mail = described_class.notify(report.id) + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' - expect(mail).to deliver_to 'admin@example.com' + context 'with admin_notification_email set' do + it 'sends to the admin_notification_email' do + is_expected.to deliver_to 'admin@example.com' end it 'includes the user in the subject' do - report = create(:abuse_report) - - mail = described_class.notify(report.id) - - expect(mail).to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse" + is_expected.to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse" end end diff --git a/spec/mailers/email_rejection_mailer_spec.rb b/spec/mailers/email_rejection_mailer_spec.rb new file mode 100644 index 00000000000..bbe0a50ae8e --- /dev/null +++ b/spec/mailers/email_rejection_mailer_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe EmailRejectionMailer do + include EmailSpec::Matchers + + describe '#rejection' do + let(:raw_email) { 'From: someone@example.com\nraw email here' } + + subject { described_class.rejection('some rejection reason', raw_email) } + + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + end +end diff --git a/spec/mailers/emails/auto_devops_spec.rb b/spec/mailers/emails/auto_devops_spec.rb index 839caf3f50e..dd7c12c3143 100644 --- a/spec/mailers/emails/auto_devops_spec.rb +++ b/spec/mailers/emails/auto_devops_spec.rb @@ -13,6 +13,9 @@ describe Emails::AutoDevops do subject { Notify.autodevops_disabled_email(pipeline, owner.email) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it 'sents email with correct subject' do is_expected.to have_subject("#{project.name} | Auto DevOps pipeline was disabled for #{project.name}") end diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb index 09253cf8003..5b5bd6f4308 100644 --- a/spec/mailers/emails/issues_spec.rb +++ b/spec/mailers/emails/issues_spec.rb @@ -29,5 +29,14 @@ describe Emails::Issues do expect(subject).to have_body_text "23, 34, 58" end + + context 'with header and footer' do + let(:results) { { success: 165, error_lines: [], parse_error: false } } + + subject { Notify.import_issues_csv_email(user.id, project.id, results) } + + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + end end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 4f578c48d5b..15b04c9d066 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -53,6 +53,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject and body' do aggregate_failures do @@ -72,6 +74,9 @@ describe Notify do context 'when sent with a reason' do subject { described_class.new_issue_email(issue.assignees.first.id, issue.id, NotificationReason::ASSIGNED) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it 'includes the reason in a header' do is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end @@ -99,6 +104,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -118,6 +125,9 @@ describe Notify do context 'when sent with a reason' do subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it 'includes the reason in a header' do is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end @@ -134,6 +144,8 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with a labels subscriptions link in its footer' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -173,6 +185,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -199,6 +213,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains description about action taken' do is_expected.to have_body_text 'Issue was moved to another project' @@ -226,6 +242,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject and body' do aggregate_failures do @@ -243,6 +261,9 @@ describe Notify do context 'when sent with a reason' do subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id, NotificationReason::ASSIGNED) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it 'includes the reason in a header' do is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end @@ -270,6 +291,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like "an unsubscribeable thread" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -289,6 +312,9 @@ describe Notify do context 'when sent with a reason' do subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::ASSIGNED) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it 'includes the reason in a header' do is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end @@ -313,6 +339,8 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like "an unsubscribeable thread" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains the description' do is_expected.to have_body_text(merge_request.description) @@ -329,6 +357,8 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with a labels subscriptions link in its footer' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -352,6 +382,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -379,6 +411,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the merge author' do sender = subject.header[:from].addrs[0] @@ -413,6 +447,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the merge request author' do sender = subject.header[:from].addrs[0] @@ -442,6 +478,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the push user' do sender = subject.header[:from].addrs[0] @@ -482,6 +520,9 @@ describe Notify do subject { described_class.note_issue_email(recipient.id, third_note.id) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it 'has In-Reply-To header pointing to previous note in discussion' do expect(subject.header['In-Reply-To'].message_ids).to eq(["note_#{second_note.id}@#{host}"]) end @@ -502,6 +543,9 @@ describe Notify do subject { described_class.note_issue_email(recipient.id, note.id) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it 'has In-Reply-To header pointing to the issue' do expect(subject.header['In-Reply-To'].message_ids).to eq(["issue_#{note.noteable.id}@#{host}"]) end @@ -518,6 +562,9 @@ describe Notify do subject { described_class.note_project_snippet_email(project_snippet_note.author_id, project_snippet_note.id) } + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do let(:model) { project_snippet } end @@ -535,6 +582,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject and body' do is_expected.to have_subject("#{project.name} | Project was moved") @@ -559,6 +608,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do to_emails = subject.header[:to].addrs.map(&:address) @@ -582,6 +633,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject "Access to the #{project.full_name} project was denied" @@ -599,6 +652,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject "Access to the #{project.full_name} project was granted" @@ -629,6 +684,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject "Invitation to join the #{project.full_name} project" @@ -653,6 +710,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject 'Invitation accepted' @@ -676,6 +735,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject 'Invitation declined' @@ -708,6 +769,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject and body' do aggregate_failures do @@ -732,6 +795,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject and body' do aggregate_failures do @@ -756,6 +821,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject and body' do aggregate_failures do @@ -819,6 +886,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject' do is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})" @@ -845,6 +914,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject' do is_expected.to have_referable_subject(merge_request, reply: true) @@ -871,6 +942,8 @@ describe Notify do end it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject' do is_expected.to have_referable_subject(issue, reply: true) @@ -948,6 +1021,8 @@ describe Notify do it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_commit it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' end describe 'on a merge request' do @@ -958,6 +1033,8 @@ describe Notify do it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_merge_request it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' end end end @@ -976,6 +1053,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do to_emails = subject.header[:to].addrs.map(&:address) @@ -998,6 +1077,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject "Access to the #{group.name} group was denied" @@ -1014,6 +1095,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject "Access to the #{group.name} group was granted" @@ -1044,6 +1127,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject "Invitation to join the #{group.name} group" @@ -1068,6 +1153,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject 'Invitation accepted' @@ -1091,6 +1178,8 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'contains all the useful information' do is_expected.to have_subject 'Invitation declined' @@ -1140,6 +1229,8 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with X-GitLab headers containing project details' it_behaves_like 'an email that contains a header with author username' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -1165,6 +1256,8 @@ describe Notify do it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like 'an email with X-GitLab headers containing project details' it_behaves_like 'an email that contains a header with author username' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -1189,6 +1282,8 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with X-GitLab headers containing project details' it_behaves_like 'an email that contains a header with author username' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -1210,6 +1305,8 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with X-GitLab headers containing project details' it_behaves_like 'an email that contains a header with author username' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -1237,6 +1334,8 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with X-GitLab headers containing project details' it_behaves_like 'an email that contains a header with author username' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -1328,6 +1427,8 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'an email with X-GitLab headers containing project details' it_behaves_like 'an email that contains a header with author username' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -1348,6 +1449,11 @@ describe Notify do describe 'HTML emails setting' do let(:multipart_mail) { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } + subject { multipart_mail } + + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + context 'when disabled' do it 'only sends the text template' do stub_application_setting(html_emails_enabled: false) @@ -1386,6 +1492,8 @@ describe Notify do subject { described_class.note_personal_snippet_email(personal_snippet_note.author_id, personal_snippet_note.id) } it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject and body' do is_expected.to have_referable_subject(personal_snippet, reply: true) diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb index 00613c7b671..384660f7221 100644 --- a/spec/mailers/repository_check_mailer_spec.rb +++ b/spec/mailers/repository_check_mailer_spec.rb @@ -17,5 +17,12 @@ describe RepositoryCheckMailer do expect(mail).to have_subject 'GitLab Admin | 3 projects failed their last repository check' end + + context 'with footer and header' do + subject { described_class.notify(1) } + + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + end end end diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 28d482adebf..3e95aa2b5dd 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -78,4 +78,22 @@ describe Appearance do it { is_expected.to allow_value(hex).for(:message_font_color) } it { is_expected.not_to allow_value('000').for(:message_font_color) } end + + describe 'email_header_and_footer_enabled' do + context 'default email_header_and_footer_enabled flag value' do + it 'returns email_header_and_footer_enabled as true' do + appearance = build(:appearance) + + expect(appearance.email_header_and_footer_enabled?).to eq(false) + end + end + + context 'when setting email_header_and_footer_enabled flag value' do + it 'returns email_header_and_footer_enabled as true' do + appearance = build(:appearance, email_header_and_footer_enabled: true) + + expect(appearance.email_header_and_footer_enabled?).to eq(true) + end + end + end end diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index a38354060cf..4fff1c4e228 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -252,3 +252,31 @@ shared_examples 'a note email' do end end end + +shared_examples 'appearance header and footer enabled' do + it "contains header and footer" do + create :appearance, header_message: "Foo", footer_message: "Bar", email_header_and_footer_enabled: true + + aggregate_failures do + expect(subject.html_part).to have_body_text("

    Foo

    ") + expect(subject.html_part).to have_body_text("

    Bar

    ") + + expect(subject.text_part).to have_body_text(/^Foo/) + expect(subject.text_part).to have_body_text(/Bar$/) + end + end +end + +shared_examples 'appearance header and footer not enabled' do + it "does not contain header and footer" do + create :appearance, header_message: "Foo", footer_message: "Bar", email_header_and_footer_enabled: false + + aggregate_failures do + expect(subject.html_part).not_to have_body_text("

    Foo

    ") + expect(subject.html_part).not_to have_body_text("

    Bar

    ") + + expect(subject.text_part).not_to have_body_text(/^Foo/) + expect(subject.text_part).not_to have_body_text(/Bar$/) + end + end +end -- cgit v1.2.1 From 910c7f5f6340573550edbe509117c9f55d12afa8 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Feb 2019 19:43:32 +0100 Subject: Refactor note_form_spec.js to use Vue test utils --- .../javascripts/notes/components/note_form_spec.js | 157 +++++++++++++-------- 1 file changed, 98 insertions(+), 59 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js index 5db20fd285f..7cc324cfe44 100644 --- a/spec/javascripts/notes/components/note_form_spec.js +++ b/spec/javascripts/notes/components/note_form_spec.js @@ -1,17 +1,15 @@ -import Vue from 'vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import createStore from '~/notes/stores'; -import issueNoteForm from '~/notes/components/note_form.vue'; +import NoteForm from '~/notes/components/note_form.vue'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import { noteableDataMock, notesDataMock } from '../mock_data'; -import { keyboardDownEvent } from '../../issue_show/helpers'; describe('issue_note_form component', () => { let store; - let vm; + let wrapper; let props; beforeEach(() => { - const Component = Vue.extend(issueNoteForm); - store = createStore(); store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); @@ -22,26 +20,35 @@ describe('issue_note_form component', () => { noteId: '545', }; - vm = new Component({ + const localVue = createLocalVue(); + wrapper = shallowMount(NoteForm, { store, propsData: props, - }).$mount(); + // see https://gitlab.com/gitlab-org/gitlab-ce/issues/56317 for the following + localVue, + sync: false, + }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); describe('noteHash', () => { it('returns note hash string based on `noteId`', () => { - expect(vm.noteHash).toBe(`#note_${props.noteId}`); + expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`); }); it('return note hash as `#` when `noteId` is empty', done => { - vm.noteId = ''; - Vue.nextTick() + wrapper.setProps({ + ...props, + noteId: '', + }); + + wrapper.vm + .$nextTick() .then(() => { - expect(vm.noteHash).toBe('#'); + expect(wrapper.vm.noteHash).toBe('#'); }) .then(done) .catch(done.fail); @@ -50,95 +57,127 @@ describe('issue_note_form component', () => { describe('conflicts editing', () => { it('should show conflict message if note changes outside the component', done => { - vm.isEditing = true; - vm.noteBody = 'Foo'; + wrapper.setProps({ + ...props, + isEditing: true, + noteBody: 'Foo', + }); + const message = 'This comment has changed since you started editing, please review the updated comment to ensure information is not lost.'; - Vue.nextTick(() => { - expect( - vm.$el - .querySelector('.js-conflict-edit-warning') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toEqual(message); - done(); - }); + wrapper.vm + .$nextTick() + .then(() => { + const conflictWarning = wrapper.find('.js-conflict-edit-warning'); + + expect(conflictWarning.exists()).toBe(true); + expect( + conflictWarning + .text() + .replace(/\s+/g, ' ') + .trim(), + ).toBe(message); + }) + .then(done) + .catch(done.fail); }); }); describe('form', () => { it('should render text area with placeholder', () => { - expect(vm.$el.querySelector('textarea').getAttribute('placeholder')).toEqual( + const textarea = wrapper.find('textarea'); + + expect(textarea.attributes('placeholder')).toEqual( 'Write a comment or drag your files here…', ); }); it('should link to markdown docs', () => { const { markdownDocsPath } = notesDataMock; + const markdownField = wrapper.find(MarkdownField); + const markdownFieldProps = markdownField.props(); - expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual( - 'Markdown', - ); + expect(markdownFieldProps.markdownDocsPath).toBe(markdownDocsPath); }); describe('keyboard events', () => { + let textarea; + + beforeEach(() => { + textarea = wrapper.find('textarea'); + textarea.setValue('Foo'); + }); + describe('up', () => { it('should ender edit mode', () => { - spyOn(vm, 'editMyLastNote').and.callThrough(); - vm.$el.querySelector('textarea').value = 'Foo'; - vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(38, true)); + // TODO: do not spy on vm + spyOn(wrapper.vm, 'editMyLastNote').and.callThrough(); + + textarea.trigger('keydown.up'); - expect(vm.editMyLastNote).toHaveBeenCalled(); + expect(wrapper.vm.editMyLastNote).toHaveBeenCalled(); }); }); describe('enter', () => { it('should save note when cmd+enter is pressed', () => { - spyOn(vm, 'handleUpdate').and.callThrough(); - vm.$el.querySelector('textarea').value = 'Foo'; - vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true)); + textarea.trigger('keydown.enter', { metaKey: true }); + + const { handleFormUpdate } = wrapper.emitted(); - expect(vm.handleUpdate).toHaveBeenCalled(); + expect(handleFormUpdate.length).toBe(1); }); it('should save note when ctrl+enter is pressed', () => { - spyOn(vm, 'handleUpdate').and.callThrough(); - vm.$el.querySelector('textarea').value = 'Foo'; - vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true)); + textarea.trigger('keydown.enter', { ctrlKey: true }); - expect(vm.handleUpdate).toHaveBeenCalled(); + const { handleFormUpdate } = wrapper.emitted(); + + expect(handleFormUpdate.length).toBe(1); }); }); }); describe('actions', () => { it('should be possible to cancel', done => { - spyOn(vm, 'cancelHandler').and.callThrough(); - vm.isEditing = true; + // TODO: do not spy on vm + spyOn(wrapper.vm, 'cancelHandler').and.callThrough(); + wrapper.setProps({ + ...props, + isEditing: true, + }); - Vue.nextTick(() => { - vm.$el.querySelector('.note-edit-cancel').click(); + wrapper.vm + .$nextTick() + .then(() => { + const cancelButton = wrapper.find('.note-edit-cancel'); + cancelButton.trigger('click'); - Vue.nextTick(() => { - expect(vm.cancelHandler).toHaveBeenCalled(); - done(); - }); - }); + expect(wrapper.vm.cancelHandler).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); it('should be possible to update the note', done => { - vm.isEditing = true; - - Vue.nextTick(() => { - vm.$el.querySelector('textarea').value = 'Foo'; - vm.$el.querySelector('.js-vue-issue-save').click(); - - Vue.nextTick(() => { - expect(vm.isSubmitting).toEqual(true); - done(); - }); + wrapper.setProps({ + ...props, + isEditing: true, }); + + wrapper.vm + .$nextTick() + .then(() => { + const textarea = wrapper.find('textarea'); + textarea.setValue('Foo'); + const saveButton = wrapper.find('.js-vue-issue-save'); + saveButton.trigger('click'); + + expect(wrapper.vm.isSubmitting).toEqual(true); + }) + .then(done) + .catch(done.fail); }); }); }); -- cgit v1.2.1 From edfdf568d3ef121ef696f769f6d57a334c961871 Mon Sep 17 00:00:00 2001 From: Natalia Tepluhina Date: Thu, 28 Feb 2019 08:23:17 +0000 Subject: Resolve "Add missing karma tests to the the MR Diff components" --- spec/javascripts/diffs/components/app_spec.js | 100 +++++++++++++- .../components/changed_files_dropdown_spec.js | 1 - .../components/compare_versions_dropdown_spec.js | 153 +++++++++++++++++++-- .../diffs/components/diff_file_header_spec.js | 84 +++++++++++ .../diffs/components/edit_button_spec.js | 62 ++++++++- .../diffs/components/hidden_files_warning_spec.js | 49 ++++++- 6 files changed, 428 insertions(+), 21 deletions(-) delete mode 100644 spec/javascripts/diffs/components/changed_files_dropdown_spec.js (limited to 'spec') diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index bce6113f75a..d81c433cca6 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -1,11 +1,19 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; import { TEST_HOST } from 'spec/test_constants'; import App from '~/diffs/components/app.vue'; import NoChanges from '~/diffs/components/no_changes.vue'; import DiffFile from '~/diffs/components/diff_file.vue'; import Mousetrap from 'mousetrap'; +import CompareVersions from '~/diffs/components/compare_versions.vue'; +import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue'; +import CommitWidget from '~/diffs/components/commit_widget.vue'; +import TreeList from '~/diffs/components/tree_list.vue'; import createDiffsStore from '../create_diffs_store'; +import diffsMockData from '../mock_data/merge_request_diffs'; + +const mergeRequestDiff = { version_index: 1 }; describe('diffs/components/app', () => { const oldMrTabs = window.mrTabs; @@ -49,6 +57,21 @@ describe('diffs/components/app', () => { wrapper.destroy(); }); + it('displays loading icon on loading', () => { + createComponent({}, ({ state }) => { + state.diffs.isLoading = true; + }); + + expect(wrapper.contains(GlLoadingIcon)).toBe(true); + }); + + it('displays diffs container when not loading', () => { + createComponent(); + + expect(wrapper.contains(GlLoadingIcon)).toBe(false); + expect(wrapper.contains('#diffs')).toBe(true); + }); + it('does not show commit info', () => { createComponent(); @@ -134,8 +157,8 @@ describe('diffs/components/app', () => { }); it('does not render empty state when diff files exist', () => { - createComponent({}, () => { - store.state.diffs.diffFiles.push({ + createComponent({}, ({ state }) => { + state.diffs.diffFiles.push({ id: 1, }); }); @@ -145,9 +168,9 @@ describe('diffs/components/app', () => { }); it('does not render empty state when versions match', () => { - createComponent({}, () => { - store.state.diffs.startVersion = { version_index: 1 }; - store.state.diffs.mergeRequestDiff = { version_index: 1 }; + createComponent({}, ({ state }) => { + state.diffs.startVersion = mergeRequestDiff; + state.diffs.mergeRequestDiff = mergeRequestDiff; }); expect(wrapper.contains(NoChanges)).toBe(false); @@ -307,4 +330,71 @@ describe('diffs/components/app', () => { .catch(done.fail); }); }); + + describe('diffs', () => { + it('should render compare versions component', () => { + createComponent({}, ({ state }) => { + state.diffs.mergeRequestDiffs = diffsMockData; + state.diffs.targetBranchName = 'target-branch'; + state.diffs.mergeRequestDiff = mergeRequestDiff; + }); + + expect(wrapper.contains(CompareVersions)).toBe(true); + expect(wrapper.find(CompareVersions).props()).toEqual( + jasmine.objectContaining({ + targetBranch: { + branchName: 'target-branch', + versionIndex: -1, + path: '', + }, + mergeRequestDiffs: diffsMockData, + mergeRequestDiff, + }), + ); + }); + + it('should render hidden files warning if render overflow warning is present', () => { + createComponent({}, ({ state }) => { + state.diffs.renderOverflowWarning = true; + state.diffs.realSize = '5'; + state.diffs.plainDiffPath = 'plain diff path'; + state.diffs.emailPatchPath = 'email patch path'; + state.diffs.size = 1; + }); + + expect(wrapper.contains(HiddenFilesWarning)).toBe(true); + expect(wrapper.find(HiddenFilesWarning).props()).toEqual( + jasmine.objectContaining({ + total: '5', + plainDiffPath: 'plain diff path', + emailPatchPath: 'email patch path', + visible: 1, + }), + ); + }); + + it('should display commit widget if store has a commit', () => { + createComponent({}, () => { + store.state.diffs.commit = { + author: 'John Doe', + }; + }); + + expect(wrapper.contains(CommitWidget)).toBe(true); + }); + + it('should display diff file if there are diff files', () => { + createComponent({}, ({ state }) => { + state.diffs.diffFiles.push({ sha: '123' }); + }); + + expect(wrapper.contains(DiffFile)).toBe(true); + }); + + it('should render tree list', () => { + createComponent(); + + expect(wrapper.find(TreeList).exists()).toBe(true); + }); + }); }); diff --git a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js b/spec/javascripts/diffs/components/changed_files_dropdown_spec.js deleted file mode 100644 index 7237274eb43..00000000000 --- a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js +++ /dev/null @@ -1 +0,0 @@ -// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js index 53b9ac22fc0..8a3834d542f 100644 --- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js +++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js @@ -1,34 +1,161 @@ -// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 import { shallowMount, createLocalVue } from '@vue/test-utils'; import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue'; import diffsMockData from '../mock_data/merge_request_diffs'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; + +const localVue = createLocalVue(); +const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 }; +const startVersion = { version_index: 4 }; +const mergeRequestVersion = { + version_path: '123', +}; +const baseVersionPath = '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37'; describe('CompareVersionsDropdown', () => { let wrapper; - const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 }; - const factory = (options = {}) => { - const localVue = createLocalVue(); + const findSelectedVersion = () => wrapper.find('.dropdown-menu-toggle'); + const findVersionsListElements = () => wrapper.findAll('li'); + const findLinkElement = index => + findVersionsListElements() + .at(index) + .find('a'); + const findLastLink = () => findLinkElement(findVersionsListElements().length - 1); - wrapper = shallowMount(CompareVersionsDropdown, { localVue, ...options }); + const createComponent = (props = {}) => { + wrapper = shallowMount(localVue.extend(CompareVersionsDropdown), { + localVue, + sync: false, + propsData: { ...props }, + }); }; afterEach(() => { wrapper.destroy(); }); - it('should render a correct base version link', () => { - factory({ - propsData: { - baseVersionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37', + describe('selected version name', () => { + it('shows latest version when latest is selected', () => { + createComponent({ + mergeRequestVersion, + startVersion, + otherVersions: diffsMockData, + }); + + expect(findSelectedVersion().text()).toBe('latest version'); + }); + + it('shows target branch name for base branch', () => { + createComponent({ + targetBranch, + }); + + expect(findSelectedVersion().text()).toBe('tmp-wine-dev'); + }); + + it('shows correct version for non-base and non-latest branches', () => { + createComponent({ + startVersion, + targetBranch, + }); + + expect(findSelectedVersion().text()).toBe(`version ${startVersion.version_index}`); + }); + }); + + describe('target versions list', () => { + it('should have the same length as otherVersions if merge request version is present', () => { + createComponent({ + mergeRequestVersion, + otherVersions: diffsMockData, + }); + + expect(findVersionsListElements().length).toEqual(diffsMockData.length); + }); + + it('should have an otherVersions length plus 1 if no merge request version is present', () => { + createComponent({ + targetBranch, + otherVersions: diffsMockData, + }); + + expect(findVersionsListElements().length).toEqual(diffsMockData.length + 1); + }); + + it('should have base branch link as active on base branch', () => { + createComponent({ + targetBranch, + otherVersions: diffsMockData, + }); + + expect(findLastLink().classes()).toContain('is-active'); + }); + + it('should have correct branch link as active if start version present', () => { + createComponent({ + targetBranch, + startVersion, + otherVersions: diffsMockData, + }); + + expect(findLinkElement(0).classes()).toContain('is-active'); + }); + + it('should render a correct base version link', () => { + createComponent({ + baseVersionPath, otherVersions: diffsMockData.slice(1), targetBranch, - }, + }); + + expect(findLastLink().attributes('href')).toEqual(baseVersionPath); + expect(findLastLink().text()).toContain('(base)'); + }); + + it('should not render commits count if no showCommitsCount is passed', () => { + createComponent({ + otherVersions: diffsMockData, + targetBranch, + }); + + const commitsCount = diffsMockData[0].commits_count; + + expect(findLinkElement(0).text()).not.toContain(`${commitsCount} commit`); + }); + + it('should render correct commits count if showCommitsCount is passed', () => { + createComponent({ + otherVersions: diffsMockData, + targetBranch, + showCommitCount: true, + }); + + const commitsCount = diffsMockData[0].commits_count; + + expect(findLinkElement(0).text()).toContain(`${commitsCount} commit`); + }); + + it('should render correct commit sha', () => { + createComponent({ + otherVersions: diffsMockData, + targetBranch, + }); + + const commitShaElement = findLinkElement(0).find('.commit-sha'); + + expect(commitShaElement.text()).toBe(diffsMockData[0].short_commit_sha); }); - const links = wrapper.findAll('a'); - const lastLink = links.wrappers[links.length - 1]; + it('should render correct time-ago ', () => { + createComponent({ + otherVersions: diffsMockData, + targetBranch, + }); + + const timeAgoElement = findLinkElement(0).find(TimeAgo); - expect(lastLink.attributes('href')).toEqual(wrapper.props('baseVersionPath')); + expect(timeAgoElement.exists()).toBe(true); + expect(timeAgoElement.props('time')).toBe(diffsMockData[0].created_at); + }); }); }); diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 005a4751ea1..1201f066d2f 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -491,5 +491,89 @@ describe('diff_file_header', () => { }); }); }); + + describe('file actions', () => { + it('should not render if diff file has a submodule', () => { + props.diffFile.submodule = 'submodule'; + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.file-actions')).toEqual(null); + }); + + it('should not render if add merge request buttons is false', () => { + props.addMergeRequestButtons = false; + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.file-actions')).toEqual(null); + }); + + describe('with add merge request buttons enabled', () => { + beforeEach(() => { + props.addMergeRequestButtons = true; + props.diffFile.edit_path = 'edit-path'; + }); + + const viewReplacedFileButton = () => vm.$el.querySelector('.js-view-replaced-file'); + const viewFileButton = () => vm.$el.querySelector('.js-view-file-button'); + const externalUrl = () => vm.$el.querySelector('.js-external-url'); + + it('should render if add merge request buttons is true and diff file does not have a submodule', () => { + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.file-actions')).not.toEqual(null); + }); + + it('should not render view replaced file button if no replaced view path is present', () => { + vm = mountComponentWithStore(Component, { props, store }); + + expect(viewReplacedFileButton()).toEqual(null); + }); + + it('should render view replaced file button if replaced view path is present', () => { + props.diffFile.replaced_view_path = 'replaced-view-path'; + vm = mountComponentWithStore(Component, { props, store }); + + expect(viewReplacedFileButton()).not.toEqual(null); + expect(viewReplacedFileButton().getAttribute('href')).toBe('replaced-view-path'); + }); + + it('should render correct file view button path', () => { + props.diffFile.view_path = 'view-path'; + vm = mountComponentWithStore(Component, { props, store }); + + expect(viewFileButton().getAttribute('href')).toBe('view-path'); + }); + + it('should not render external url view link if diff file has no external url', () => { + vm = mountComponentWithStore(Component, { props, store }); + + expect(externalUrl()).toEqual(null); + }); + + it('should render external url view link if diff file has external url', () => { + props.diffFile.external_url = 'external_url'; + vm = mountComponentWithStore(Component, { props, store }); + + expect(externalUrl()).not.toEqual(null); + expect(externalUrl().getAttribute('href')).toBe('external_url'); + }); + }); + + describe('without file blob', () => { + beforeEach(() => { + props.diffFile.blob = null; + props.addMergeRequestButtons = true; + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('should not render toggle discussions button', () => { + expect(vm.$el.querySelector('.js-btn-vue-toggle-comments')).toEqual(null); + }); + + it('should not render edit button', () => { + expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null); + }); + }); + }); }); }); diff --git a/spec/javascripts/diffs/components/edit_button_spec.js b/spec/javascripts/diffs/components/edit_button_spec.js index 7237274eb43..ccdae4cb312 100644 --- a/spec/javascripts/diffs/components/edit_button_spec.js +++ b/spec/javascripts/diffs/components/edit_button_spec.js @@ -1 +1,61 @@ -// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import EditButton from '~/diffs/components/edit_button.vue'; + +const localVue = createLocalVue(); +const editPath = 'test-path'; + +describe('EditButton', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(EditButton, { + localVue, + sync: false, + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('has correct href attribute', () => { + createComponent({ + editPath, + canCurrentUserFork: false, + }); + + expect(wrapper.attributes('href')).toBe(editPath); + }); + + it('emits a show fork message event if current user can fork', () => { + createComponent({ + editPath, + canCurrentUserFork: true, + }); + wrapper.trigger('click'); + + expect(wrapper.emitted('showForkMessage')).toBeTruthy(); + }); + + it('doesnt emit a show fork message event if current user cannot fork', () => { + createComponent({ + editPath, + canCurrentUserFork: false, + }); + wrapper.trigger('click'); + + expect(wrapper.emitted('showForkMessage')).toBeFalsy(); + }); + + it('doesnt emit a show fork message event if current user can modify blob', () => { + createComponent({ + editPath, + canCurrentUserFork: true, + canModifyBlob: true, + }); + wrapper.trigger('click'); + + expect(wrapper.emitted('showForkMessage')).toBeFalsy(); + }); +}); diff --git a/spec/javascripts/diffs/components/hidden_files_warning_spec.js b/spec/javascripts/diffs/components/hidden_files_warning_spec.js index 7237274eb43..5bf5ddd27bd 100644 --- a/spec/javascripts/diffs/components/hidden_files_warning_spec.js +++ b/spec/javascripts/diffs/components/hidden_files_warning_spec.js @@ -1 +1,48 @@ -// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue'; + +const localVue = createLocalVue(); +const propsData = { + total: '10', + visible: 5, + plainDiffPath: 'plain-diff-path', + emailPatchPath: 'email-patch-path', +}; + +describe('HiddenFilesWarning', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMount(HiddenFilesWarning, { + localVue, + sync: false, + propsData, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('has a correct plain diff URL', () => { + const plainDiffLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Plain diff')[0]; + + expect(plainDiffLink.attributes('href')).toBe(propsData.plainDiffPath); + }); + + it('has a correct email patch URL', () => { + const emailPatchLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Email patch')[0]; + + expect(emailPatchLink.attributes('href')).toBe(propsData.emailPatchPath); + }); + + it('has a correct visible/total files text', () => { + const filesText = wrapper.find('strong'); + + expect(filesText.text()).toBe('5 of 10'); + }); +}); -- cgit v1.2.1 From 94024cd6990102ad5d5386d84afcc09fe3007957 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 28 Feb 2019 09:46:09 +0000 Subject: Jira: Make issue links title compact Port of a community contribution from @glensc to upstream. --- spec/models/project_services/jira_service_spec.rb | 3 ++- spec/services/system_note_service_spec.rb | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 788b3179b01..5428fcb1271 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -177,9 +177,10 @@ describe JiraService do expect(WebMock).to have_requested(:post, @remote_link_url).with( body: hash_including( GlobalID: 'GitLab', + relationship: 'mentioned on', object: { url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{commit_id}", - title: "GitLab: Solved by commit #{commit_id}.", + title: "Solved by commit #{commit_id}.", icon: { title: 'GitLab', url16x16: favicon_path }, status: { resolved: true } } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 82544ab0413..b917de14b2e 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -807,9 +807,10 @@ describe SystemNoteService do expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with( body: hash_including( GlobalID: "GitLab", + relationship: 'mentioned on', object: { url: project_commit_url(project, commit), - title: "GitLab: Mentioned on commit - #{commit.title}", + title: "Commit - #{commit.title}", icon: { title: "GitLab", url16x16: favicon_path }, status: { resolved: false } } @@ -833,9 +834,10 @@ describe SystemNoteService do expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with( body: hash_including( GlobalID: "GitLab", + relationship: 'mentioned on', object: { url: project_issue_url(project, issue), - title: "GitLab: Mentioned on issue - #{issue.title}", + title: "Issue - #{issue.title}", icon: { title: "GitLab", url16x16: favicon_path }, status: { resolved: false } } @@ -859,9 +861,10 @@ describe SystemNoteService do expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with( body: hash_including( GlobalID: "GitLab", + relationship: 'mentioned on', object: { url: project_snippet_url(project, snippet), - title: "GitLab: Mentioned on snippet - #{snippet.title}", + title: "Snippet - #{snippet.title}", icon: { title: "GitLab", url16x16: favicon_path }, status: { resolved: false } } -- cgit v1.2.1 From d9dd52092ff7e489c162ddf436fb496cc8144e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 27 Feb 2019 14:32:08 +0100 Subject: Make JS pagination handle missing 'X-Total-Pages' header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../vue_shared/components/table_pagination_spec.js | 182 +++++++++++++++++++-- 1 file changed, 166 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js index 407d1d59f83..42cd41381dc 100644 --- a/spec/javascripts/vue_shared/components/table_pagination_spec.js +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js @@ -22,10 +22,10 @@ describe('Pagination component', () => { it('should not render anything', () => { component = mountComponent({ pageInfo: { - nextPage: 1, + nextPage: NaN, page: 1, perPage: 20, - previousPage: null, + previousPage: NaN, total: 15, totalPages: 1, }, @@ -58,6 +58,28 @@ describe('Pagination component', () => { expect(spy).not.toHaveBeenCalled(); }); + it('should be disabled and non clickable when total and totalPages are NaN', () => { + component = mountComponent({ + pageInfo: { + nextPage: 2, + page: 1, + perPage: 20, + previousPage: NaN, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + expect( + component.$el.querySelector('.js-previous-button').classList.contains('disabled'), + ).toEqual(true); + + component.$el.querySelector('.js-previous-button .page-link').click(); + + expect(spy).not.toHaveBeenCalled(); + }); + it('should be enabled and clickable', () => { component = mountComponent({ pageInfo: { @@ -75,6 +97,24 @@ describe('Pagination component', () => { expect(spy).toHaveBeenCalledWith(1); }); + + it('should be enabled and clickable when total and totalPages are NaN', () => { + component = mountComponent({ + pageInfo: { + nextPage: 3, + page: 2, + perPage: 20, + previousPage: 1, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + component.$el.querySelector('.js-previous-button .page-link').click(); + + expect(spy).toHaveBeenCalledWith(1); + }); }); describe('first button', () => { @@ -99,6 +139,28 @@ describe('Pagination component', () => { expect(spy).toHaveBeenCalledWith(1); }); + + it('should call the change callback with the first page when total and totalPages are NaN', () => { + component = mountComponent({ + pageInfo: { + nextPage: 3, + page: 2, + perPage: 20, + previousPage: 1, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + const button = component.$el.querySelector('.js-first-button .page-link'); + + expect(button.textContent.trim()).toEqual('« First'); + + button.click(); + + expect(spy).toHaveBeenCalledWith(1); + }); }); describe('last button', () => { @@ -123,16 +185,32 @@ describe('Pagination component', () => { expect(spy).toHaveBeenCalledWith(5); }); + + it('should not render', () => { + component = mountComponent({ + pageInfo: { + nextPage: 3, + page: 2, + perPage: 20, + previousPage: 1, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + expect(component.$el.querySelector('.js-last-button .page-link')).toBeNull(); + }); }); describe('next button', () => { it('should be disabled and non clickable', () => { component = mountComponent({ pageInfo: { - nextPage: 5, + nextPage: NaN, page: 5, perPage: 20, - previousPage: 1, + previousPage: 4, total: 84, totalPages: 5, }, @@ -146,6 +224,26 @@ describe('Pagination component', () => { expect(spy).not.toHaveBeenCalled(); }); + it('should be disabled and non clickable when total and totalPages are NaN', () => { + component = mountComponent({ + pageInfo: { + nextPage: NaN, + page: 5, + perPage: 20, + previousPage: 4, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next'); + + component.$el.querySelector('.js-next-button .page-link').click(); + + expect(spy).not.toHaveBeenCalled(); + }); + it('should be enabled and clickable', () => { component = mountComponent({ pageInfo: { @@ -163,6 +261,24 @@ describe('Pagination component', () => { expect(spy).toHaveBeenCalledWith(4); }); + + it('should be enabled and clickable when total and totalPages are NaN', () => { + component = mountComponent({ + pageInfo: { + nextPage: 4, + page: 3, + perPage: 20, + previousPage: 2, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + component.$el.querySelector('.js-next-button .page-link').click(); + + expect(spy).toHaveBeenCalledWith(4); + }); }); describe('numbered buttons', () => { @@ -181,22 +297,56 @@ describe('Pagination component', () => { expect(component.$el.querySelectorAll('.page').length).toEqual(5); }); + + it('should not render any page', () => { + component = mountComponent({ + pageInfo: { + nextPage: 4, + page: 3, + perPage: 20, + previousPage: 2, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + expect(component.$el.querySelectorAll('.page').length).toEqual(0); + }); }); - it('should render the spread operator', () => { - component = mountComponent({ - pageInfo: { - nextPage: 4, - page: 3, - perPage: 20, - previousPage: 2, - total: 84, - totalPages: 10, - }, - change: spy, + describe('spread operator', () => { + it('should render', () => { + component = mountComponent({ + pageInfo: { + nextPage: 4, + page: 3, + perPage: 20, + previousPage: 2, + total: 84, + totalPages: 10, + }, + change: spy, + }); + + expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...'); }); - expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...'); + it('should not render', () => { + component = mountComponent({ + pageInfo: { + nextPage: 4, + page: 3, + perPage: 20, + previousPage: 2, + total: NaN, + totalPages: NaN, + }, + change: spy, + }); + + expect(component.$el.querySelector('.separator')).toBeNull(); + }); }); }); }); -- cgit v1.2.1 From 3a321c80031630c3687cfdc08699bb0824a3dbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Mon, 11 Feb 2019 12:53:58 +0100 Subject: Secure vulerability and add specs --- .../projects/group_links_controller_spec.rb | 2 ++ .../features/security/group/private_access_spec.rb | 32 ++++++++++++++--- spec/policies/group_policy_spec.rb | 40 ++++++++++++++++++---- 3 files changed, 64 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 675eeff8d12..b985a9f2846 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -67,6 +67,8 @@ describe Projects::GroupLinksController do context 'when project group id equal link group id' do before do + group2.add_developer(user) + post(:create, params: { namespace_id: project.namespace, project_id: project, diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 4705cd12d23..3238e07fe15 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -27,7 +27,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -42,7 +42,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -58,7 +58,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -73,7 +73,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -93,4 +93,28 @@ describe 'Private Group access' do it { is_expected.to be_denied_for(:visitor) } it { is_expected.to be_denied_for(:external) } end + + describe 'GET /groups/:path for shared projects' do + let(:project) { create(:project, :public) } + before do + Projects::GroupLinks::CreateService.new( + project, + create(:user), + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + subject { group_path(group) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:maintainer).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } + end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index be1804c5ce0..4c31ff30fc6 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -74,6 +74,38 @@ describe GroupPolicy do end end + context 'with no user and public project' do + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + let(:current_user) { nil } + + before do + Projects::GroupLinks::CreateService.new( + project, + user, + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + it { expect_disallowed(:read_group) } + end + + context 'with foreign user and public project' do + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + let(:current_user) { create(:user) } + + before do + Projects::GroupLinks::CreateService.new( + project, + user, + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + it { expect_disallowed(:read_group) } + end + context 'has projects' do let(:current_user) { create(:user) } let(:project) { create(:project, namespace: group) } @@ -82,17 +114,13 @@ describe GroupPolicy do project.add_developer(current_user) end - it do - expect_allowed(:read_group, :read_label) - end + it { expect_allowed(:read_label) } context 'in subgroups', :nested_groups do let(:subgroup) { create(:group, :private, parent: group) } let(:project) { create(:project, namespace: subgroup) } - it do - expect_allowed(:read_group, :read_label) - end + it { expect_allowed(:read_label) } end end -- cgit v1.2.1 From ff431fe0762b4d590a1d307b1a6b3ab7268ff7df Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 28 Feb 2019 14:45:20 +0000 Subject: Revert "Restart Unicorn and Sidekiq when GRPC throws 14:Endpoint read failed" This reverts commit 006753110a462e62f549cdf3c410e73eed068dbf. --- .../sidekiq_middleware/memory_killer_spec.rb | 63 ++++++++++++++++ .../lib/gitlab/sidekiq_middleware/shutdown_spec.rb | 88 ---------------------- 2 files changed, 63 insertions(+), 88 deletions(-) create mode 100644 spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb delete mode 100644 spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb new file mode 100644 index 00000000000..8fdbbacd04d --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::MemoryKiller do + subject { described_class.new } + let(:pid) { 999 } + + let(:worker) { double(:worker, class: 'TestWorker') } + let(:job) { { 'jid' => 123 } } + let(:queue) { 'test_queue' } + + def run + thread = subject.call(worker, job, queue) { nil } + thread&.join + end + + before do + allow(subject).to receive(:get_rss).and_return(10.kilobytes) + allow(subject).to receive(:pid).and_return(pid) + end + + context 'when MAX_RSS is set to 0' do + before do + stub_const("#{described_class}::MAX_RSS", 0) + end + + it 'does nothing' do + expect(subject).not_to receive(:sleep) + + run + end + end + + context 'when MAX_RSS is exceeded' do + before do + stub_const("#{described_class}::MAX_RSS", 5.kilobytes) + end + + it 'sends the STP, TERM and KILL signals at expected times' do + expect(subject).to receive(:sleep).with(15 * 60).ordered + expect(Process).to receive(:kill).with('SIGSTP', pid).ordered + + expect(subject).to receive(:sleep).with(30).ordered + expect(Process).to receive(:kill).with('SIGTERM', pid).ordered + + expect(subject).to receive(:sleep).with(10).ordered + expect(Process).to receive(:kill).with('SIGKILL', pid).ordered + + run + end + end + + context 'when MAX_RSS is not exceeded' do + before do + stub_const("#{described_class}::MAX_RSS", 15.kilobytes) + end + + it 'does nothing' do + expect(subject).not_to receive(:sleep) + + run + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb b/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb deleted file mode 100644 index 0001795c3f0..00000000000 --- a/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'spec_helper' - -describe Gitlab::SidekiqMiddleware::Shutdown do - subject { described_class.new } - - let(:pid) { Process.pid } - let(:worker) { double(:worker, class: 'TestWorker') } - let(:job) { { 'jid' => 123 } } - let(:queue) { 'test_queue' } - let(:block) { proc { nil } } - - def run - subject.call(worker, job, queue) { block.call } - described_class.shutdown_thread&.join - end - - def pop_trace - subject.trace.pop(true) - end - - before do - allow(subject).to receive(:get_rss).and_return(10.kilobytes) - described_class.clear_shutdown_thread - end - - context 'when MAX_RSS is set to 0' do - before do - stub_const("#{described_class}::MAX_RSS", 0) - end - - it 'does nothing' do - expect(subject).not_to receive(:sleep) - - run - end - end - - def expect_shutdown_sequence - expect(pop_trace).to eq([:sleep, 15 * 60]) - expect(pop_trace).to eq([:kill, 'SIGTSTP', pid]) - - expect(pop_trace).to eq([:sleep, 30]) - expect(pop_trace).to eq([:kill, 'SIGTERM', pid]) - - expect(pop_trace).to eq([:sleep, 10]) - expect(pop_trace).to eq([:kill, 'SIGKILL', pid]) - end - - context 'when MAX_RSS is exceeded' do - before do - stub_const("#{described_class}::MAX_RSS", 5.kilobytes) - end - - it 'sends the TSTP, TERM and KILL signals at expected times' do - run - - expect_shutdown_sequence - end - end - - context 'when MAX_RSS is not exceeded' do - before do - stub_const("#{described_class}::MAX_RSS", 15.kilobytes) - end - - it 'does nothing' do - expect(subject).not_to receive(:sleep) - - run - end - end - - context 'when WantShutdown is raised' do - let(:block) { proc { raise described_class::WantShutdown } } - - it 'starts the shutdown sequence and re-raises the exception' do - expect { run }.to raise_exception(described_class::WantShutdown) - - # We can't expect 'run' to have joined on the shutdown thread, because - # it hit an exception. - shutdown_thread = described_class.shutdown_thread - expect(shutdown_thread).not_to be_nil - shutdown_thread.join - - expect_shutdown_sequence - end - end -end -- cgit v1.2.1 From e96f2f248a63cc7115fb52a02b1743f15205e642 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 26 Feb 2019 23:03:39 -0300 Subject: Mark cached value as persisted when is an instance of ActiveRecord::Base --- spec/lib/gitlab/json_cache_spec.rb | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb index b52078e8556..2cae8ec031a 100644 --- a/spec/lib/gitlab/json_cache_spec.rb +++ b/spec/lib/gitlab/json_cache_spec.rb @@ -297,13 +297,39 @@ describe Gitlab::JsonCache do expect(result).to eq(broadcast_message) end + context 'when the cached value is an instance of ActiveRecord::Base' do + it 'returns a persisted record when id is set' do + backend.write(expanded_key, broadcast_message.to_json) + + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result).to be_persisted + end + + it 'returns a new record when id is nil' do + backend.write(expanded_key, build(:broadcast_message).to_json) + + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result).to be_new_record + end + + it 'returns a new record when id is missing' do + backend.write(expanded_key, build(:broadcast_message).attributes.except('id').to_json) + + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result).to be_new_record + end + end + it "returns the result of the block when 'as' option is nil" do result = cache.fetch(key, as: nil) { 'block result' } expect(result).to eq('block result') end - it "returns the result of the block when 'as' option is not informed" do + it "returns the result of the block when 'as' option is missing" do result = cache.fetch(key) { 'block result' } expect(result).to eq('block result') -- cgit v1.2.1 From 39afba065970bc5482589039e9e93c04f0c9285f Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Tue, 19 Feb 2019 14:51:11 -0600 Subject: Always use CTE for IssuableFinder counts Since the CTE is faster than a subquery and the only reason we're using a subquery is that the CTE can't handle sorting by certain attributes, let's use the CTE always (when the feature flag is enabled) when counting, since we can ignore ordering if we just want a count of results. --- spec/controllers/groups_controller_spec.rb | 4 +-- spec/finders/issues_finder_spec.rb | 55 ++++++++++++++---------------- 2 files changed, 26 insertions(+), 33 deletions(-) (limited to 'spec') diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 7d87b33e503..21e5122c06b 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -227,9 +227,7 @@ describe GroupsController do context 'searching' do before do - # Remove in https://gitlab.com/gitlab-org/gitlab-ce/issues/54643 - stub_feature_flags(use_cte_for_group_issues_search: false) - stub_feature_flags(use_subquery_for_group_issues_search: true) + stub_feature_flags(attempt_group_search_optimizations: true) end it 'works with popularity sort' do diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 34cb09942be..868caf748f4 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -659,7 +659,7 @@ describe IssuesFinder do before do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) - stub_feature_flags(use_subquery_for_group_issues_search: true) + stub_feature_flags(attempt_group_search_optimizations: true) end context 'when there is no search param' do @@ -690,11 +690,11 @@ describe IssuesFinder do end end - context 'when the use_subquery_for_group_issues_search flag is disabled' do + context 'when the attempt_group_search_optimizations flag is disabled' do let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } before do - stub_feature_flags(use_subquery_for_group_issues_search: false) + stub_feature_flags(attempt_group_search_optimizations: false) end it 'returns false' do @@ -702,6 +702,14 @@ describe IssuesFinder do end end + context 'when force_cte? is true' do + let(:params) { { search: 'foo', attempt_group_search_optimizations: true, force_cte: true } } + + it 'returns false' do + expect(finder.use_subquery_for_search?).to be_falsey + end + end + context 'when all conditions are met' do let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } @@ -711,72 +719,59 @@ describe IssuesFinder do end end - describe '#use_cte_for_search?' do + describe '#use_cte_for_count?' do let(:finder) { described_class.new(nil, params) } before do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) - stub_feature_flags(use_cte_for_group_issues_search: true) - stub_feature_flags(use_subquery_for_group_issues_search: false) + stub_feature_flags(attempt_group_search_optimizations: true) end context 'when there is no search param' do - let(:params) { { attempt_group_search_optimizations: true } } + let(:params) { { attempt_group_search_optimizations: true, force_cte: true } } it 'returns false' do - expect(finder.use_cte_for_search?).to be_falsey + expect(finder.use_cte_for_count?).to be_falsey end end context 'when the database is not Postgres' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } + let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } before do allow(Gitlab::Database).to receive(:postgresql?).and_return(false) end it 'returns false' do - expect(finder.use_cte_for_search?).to be_falsey + expect(finder.use_cte_for_count?).to be_falsey end end - context 'when the attempt_group_search_optimizations param is falsey' do + context 'when the force_cte param is falsey' do let(:params) { { search: 'foo' } } it 'returns false' do - expect(finder.use_cte_for_search?).to be_falsey - end - end - - context 'when the use_cte_for_group_issues_search flag is disabled' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } - - before do - stub_feature_flags(use_cte_for_group_issues_search: false) - end - - it 'returns false' do - expect(finder.use_cte_for_search?).to be_falsey + expect(finder.use_cte_for_count?).to be_falsey end end - context 'when use_subquery_for_search? is true' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } + context 'when the attempt_group_search_optimizations flag is disabled' do + let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } before do - stub_feature_flags(use_subquery_for_group_issues_search: true) + stub_feature_flags(attempt_group_search_optimizations: false) end it 'returns false' do - expect(finder.use_cte_for_search?).to be_falsey + expect(finder.use_cte_for_count?).to be_falsey end end context 'when all conditions are met' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } + let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } it 'returns true' do - expect(finder.use_cte_for_search?).to be_truthy + expect(finder.use_cte_for_count?).to be_truthy end end end -- cgit v1.2.1 From a05aba61c71d82117d4dfbf60430c0e4313b468b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 28 Feb 2019 17:21:01 -0800 Subject: Freeze date in merge request status view spec Previously, when this spec ran on the 28th of February, "one month ago" isn't actually a month ago, it's "28 days ago". Now we freeze the date to one that works as intended, since the point of the test is only that it shows a relative date in the tooltip, not necessarily that it's an exact, specific date. --- spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb b/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb index 02c225292ce..9424795749d 100644 --- a/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb +++ b/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb @@ -2,6 +2,12 @@ require 'spec_helper' describe 'projects/issues/_merge_requests_status.html.haml' do + around do |ex| + Timecop.freeze(Date.new(2018, 7, 22)) do + ex.run + end + end + it 'shows date of status change in tooltip' do merge_request = create(:merge_request, created_at: 1.month.ago) -- cgit v1.2.1 From adf003a9312fddfb5438775c3cf24b052c616b4f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 28 Feb 2019 18:15:32 +0900 Subject: Fix inconsistent `branch?` behavior between Ci::Pipeline and Ci::Build Add spec Add more tests ok --- spec/factories/merge_requests.rb | 9 +++ spec/models/ci/build_spec.rb | 116 +++++++++++++++++++++++++++++++++++ spec/models/concerns/has_ref_spec.rb | 20 ++++++ 3 files changed, 145 insertions(+) (limited to 'spec') diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 2392bfc4a53..0c0c5fc8a4c 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -101,6 +101,15 @@ FactoryBot.define do end end + trait :with_merge_request_pipeline do + after(:build) do |merge_request| + merge_request.merge_request_pipelines << build(:ci_pipeline, + source: :merge_request, + merge_request: merge_request, + project: merge_request.source_project) + end + end + trait :deployed_review_app do target_branch 'pages-deploy-target' diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 81ff727b458..e5281f3a09e 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2734,6 +2734,122 @@ describe Ci::Build do end end + describe '#secret_group_variables' do + subject { build.secret_group_variables } + + let!(:variable) { create(:ci_group_variable, protected: true, group: group) } + + context 'when ref is branch' do + let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) } + + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: 'master', project: project) + end + + it { is_expected.to include(variable) } + end + + context 'when ref is not protected' do + it { is_expected.not_to include(variable) } + end + end + + context 'when ref is tag' do + let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) } + + context 'when ref is protected' do + before do + create(:protected_tag, project: project, name: 'v*') + end + + it { is_expected.to include(variable) } + end + + context 'when ref is not protected' do + it { is_expected.not_to include(variable) } + end + end + + context 'when ref is merge request' do + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) } + + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project) + end + + it 'does not return protected variables as it is not supported for merge request pipelines' do + is_expected.not_to include(variable) + end + end + + context 'when ref is not protected' do + it { is_expected.not_to include(variable) } + end + end + end + + describe '#secret_project_variables' do + subject { build.secret_project_variables } + + let!(:variable) { create(:ci_variable, protected: true, project: project) } + + context 'when ref is branch' do + let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) } + + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: 'master', project: project) + end + + it { is_expected.to include(variable) } + end + + context 'when ref is not protected' do + it { is_expected.not_to include(variable) } + end + end + + context 'when ref is tag' do + let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) } + + context 'when ref is protected' do + before do + create(:protected_tag, project: project, name: 'v*') + end + + it { is_expected.to include(variable) } + end + + context 'when ref is not protected' do + it { is_expected.not_to include(variable) } + end + end + + context 'when ref is merge request' do + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) } + + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project) + end + + it 'does not return protected variables as it is not supported for merge request pipelines' do + is_expected.not_to include(variable) + end + end + + context 'when ref is not protected' do + it { is_expected.not_to include(variable) } + end + end + end + describe '#scoped_variables_hash' do context 'when overriding CI variables' do before do diff --git a/spec/models/concerns/has_ref_spec.rb b/spec/models/concerns/has_ref_spec.rb index 8aed72d77a4..8aa2fecb18c 100644 --- a/spec/models/concerns/has_ref_spec.rb +++ b/spec/models/concerns/has_ref_spec.rb @@ -16,6 +16,16 @@ describe HasRef do it 'return true when tag is set to false' do is_expected.to be_truthy end + + context 'when it was triggered by merge request' do + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:build) { create(:ci_build, pipeline: pipeline) } + + it 'returns false' do + is_expected.to be_falsy + end + end end context 'is not a tag' do @@ -55,5 +65,15 @@ describe HasRef do is_expected.to start_with(Gitlab::Git::BRANCH_REF_PREFIX) end end + + context 'when it is triggered by a merge request' do + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:build) { create(:ci_build, tag: false, pipeline: pipeline) } + + it 'returns nil' do + is_expected.to be_nil + end + end end end -- cgit v1.2.1 From 347c884cb25e3ed428cf3db62e73658dc20a22b0 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Thu, 28 Feb 2019 19:15:30 +0800 Subject: Clean up `noteable_id` for notes on commits This was incorrectly set by a bug in: https://gitlab.com/gitlab-org/gitlab-ce/issues/54924 --- ...ean_up_noteable_id_for_notes_on_commits_spec.rb | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb (limited to 'spec') diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb new file mode 100644 index 00000000000..bb3038ada6e --- /dev/null +++ b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190228092516_clean_up_noteable_id_for_notes_on_commits.rb') + +describe CleanUpNoteableIdForNotesOnCommits, :migration do + let(:notes) { table(:notes) } + + before do + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + + notes.create!(noteable_type: 'Issue', noteable_id: 1, note: 'Test') + notes.create!(noteable_type: 'MergeRequest', noteable_id: 1, note: 'Test') + notes.create!(noteable_type: 'Snippet', noteable_id: 1, note: 'Test') + end + + it 'clears noteable_id for notes on commits' do + expect { migrate! }.to change { dirty_notes_on_commits.count }.from(3).to(0) + end + + it 'does not clear noteable_id for other notes' do + expect { migrate! }.not_to change { other_notes.count } + end + + def dirty_notes_on_commits + notes.where(noteable_type: 'Commit').where('noteable_id IS NOT NULL') + end + + def other_notes + notes.where("noteable_type != 'Commit' AND noteable_id IS NOT NULL") + end +end -- cgit v1.2.1 From 4193e3d47543abfd533f5bc73753a2d31ab9eb22 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 23 Jan 2019 22:17:31 -0800 Subject: Bump fog-aws to 3.3.0 and associated dependencies Fixes issue with AWS V4 signatures not working with Ceph S3: https://github.com/fog/fog-aws/issues/462 --- spec/controllers/concerns/send_file_upload_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index cf3b24f50a3..aa71a247956 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -112,7 +112,7 @@ describe SendFileUpload do it 'sends a file with a custom type' do headers = double - expected_headers = %r(response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application/ecmascript) + expected_headers = /response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application%2Fecmascript/ expect(Gitlab::Workhorse).to receive(:send_url).with(expected_headers).and_call_original expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/) -- cgit v1.2.1 From 37541114770f9229acb29e798494a063e1b40d04 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Mar 2019 09:34:16 +0000 Subject: Move EE differences in CE diff files into CE Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/9974, https://gitlab.com/gitlab-org/gitlab-ee/issues/9973, https://gitlab.com/gitlab-org/gitlab-ee/issues/9969 --- spec/javascripts/notes/stores/getters_spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js index c066975a43b..8f3c493dd4c 100644 --- a/spec/javascripts/notes/stores/getters_spec.js +++ b/spec/javascripts/notes/stores/getters_spec.js @@ -261,4 +261,12 @@ describe('Getters Notes Store', () => { expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeFalsy(); }); }); + + describe('getDiscussion', () => { + it('returns discussion by ID', () => { + state.discussions.push({ id: '1' }); + + expect(getters.getDiscussion(state)('1')).toEqual({ id: '1' }); + }); + }); }); -- cgit v1.2.1 From adddbf437dc9702e06cc6ec2fd8a8b1c38cccdeb Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 1 Mar 2019 10:35:28 +0000 Subject: SIGSTP should be SIGTSTP --- spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb index 8fdbbacd04d..5df56178df2 100644 --- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb @@ -37,7 +37,7 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do it 'sends the STP, TERM and KILL signals at expected times' do expect(subject).to receive(:sleep).with(15 * 60).ordered - expect(Process).to receive(:kill).with('SIGSTP', pid).ordered + expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered expect(subject).to receive(:sleep).with(30).ordered expect(Process).to receive(:kill).with('SIGTERM', pid).ordered -- cgit v1.2.1 From d64ec5df34d6bbc1db1562e2cc5eb9c77c09fe06 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 28 Feb 2019 15:35:06 +0100 Subject: Use factory method for import_projects store Make sure we always create a new instance of the import_projects store. --- .../import_projects/components/import_projects_table_spec.js | 8 +++++--- .../import_projects/components/imported_project_table_row_spec.js | 3 ++- .../import_projects/components/provider_repo_table_row_spec.js | 7 ++++++- 3 files changed, 13 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/import_projects/components/import_projects_table_spec.js b/spec/javascripts/import_projects/components/import_projects_table_spec.js index a1ff84ce259..ab8642bf0dd 100644 --- a/spec/javascripts/import_projects/components/import_projects_table_spec.js +++ b/spec/javascripts/import_projects/components/import_projects_table_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import store from '~/import_projects/store'; +import createStore from '~/import_projects/store'; import importProjectsTable from '~/import_projects/components/import_projects_table.vue'; import STATUS_MAP from '~/import_projects/constants'; import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; @@ -9,6 +9,7 @@ import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; describe('ImportProjectsTable', () => { let vm; let mock; + let store; const reposPath = '/repos-path'; const jobsPath = '/jobs-path'; const providerTitle = 'THE PROVIDER'; @@ -31,12 +32,13 @@ describe('ImportProjectsTable', () => { }, }).$mount(); - component.$store.dispatch('stopJobsPolling'); + store.dispatch('stopJobsPolling'); return component; } beforeEach(() => { + store = createStore(); store.dispatch('setInitialData', { reposPath }); mock = new MockAdapter(axios); }); @@ -167,7 +169,7 @@ describe('ImportProjectsTable', () => { expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); mock.onGet(jobsPath).replyOnce(200, updatedProjects); - return vm.$store.dispatch('restartJobsPolling'); + return store.dispatch('restartJobsPolling'); }) .then(() => setTimeoutPromise()) .then(() => { diff --git a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js index 8af3b5954a9..7dac7e9ccc1 100644 --- a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js +++ b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import store from '~/import_projects/store'; +import createStore from '~/import_projects/store'; import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue'; import STATUS_MAP from '~/import_projects/constants'; @@ -16,6 +16,7 @@ describe('ImportedProjectTableRow', () => { function createComponent() { const ImportedProjectTableRow = Vue.extend(importedProjectTableRow); + const store = createStore(); return new ImportedProjectTableRow({ store, propsData: { diff --git a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js index 69377f8d685..4d2bacd2ad0 100644 --- a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js +++ b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js @@ -1,12 +1,13 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import store from '~/import_projects/store'; +import createStore from '~/import_projects/store'; import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue'; import STATUS_MAP, { STATUSES } from '~/import_projects/constants'; import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; describe('ProviderRepoTableRow', () => { + let store; let vm; const repo = { id: 10, @@ -28,6 +29,10 @@ describe('ProviderRepoTableRow', () => { }).$mount(); } + beforeEach(() => { + store = createStore(); + }); + afterEach(() => { vm.$destroy(); }); -- cgit v1.2.1 From 700ae637d20a4c4f5754046e0f3a7bdb8931358b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 1 Mar 2019 10:22:46 +0000 Subject: Moves utility function into CE --- spec/javascripts/lib/utils/number_utility_spec.js | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js index 94c6214c86a..818404bad81 100644 --- a/spec/javascripts/lib/utils/number_utility_spec.js +++ b/spec/javascripts/lib/utils/number_utility_spec.js @@ -4,6 +4,7 @@ import { bytesToMiB, bytesToGiB, numberToHumanSize, + sum, } from '~/lib/utils/number_utils'; describe('Number Utils', () => { @@ -87,4 +88,14 @@ describe('Number Utils', () => { expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB'); }); }); + + describe('sum', () => { + it('should add up two values', () => { + expect(sum(1, 2)).toEqual(3); + }); + + it('should add up all the values in an array when passed to a reducer', () => { + expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15); + }); + }); }); -- cgit v1.2.1 From c5b3b71e89996ba4e6fb977e3cbb3eebfbbfed4e Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Wed, 27 Feb 2019 21:08:48 +0800 Subject: CE-port for 7714-view-recent-boards Adds additional count param to Boards::Visits::LatestService --- spec/models/board_group_recent_visit_spec.rb | 22 ++++++++++++++++------ spec/models/board_project_recent_visit_spec.rb | 22 ++++++++++++++++------ spec/services/boards/visits/latest_service_spec.rb | 12 ++++++++++++ 3 files changed, 44 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb index 59ad4e5417e..558be61824f 100644 --- a/spec/models/board_group_recent_visit_spec.rb +++ b/spec/models/board_group_recent_visit_spec.rb @@ -50,15 +50,25 @@ describe BoardGroupRecentVisit do end describe '#latest' do - it 'returns the most recent visited' do - board2 = create(:board, group: group) - board3 = create(:board, group: group) + def create_visit(time) + create :board_group_recent_visit, group: group, user: user, updated_at: time + end - create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago - create :board_group_recent_visit, group: board2.group, board: board2, user: user, updated_at: 5.days.ago - recent = create :board_group_recent_visit, group: board3.group, board: board3, user: user, updated_at: 1.day.ago + it 'returns the most recent visited' do + create_visit(7.days.ago) + create_visit(5.days.ago) + recent = create_visit(1.day.ago) expect(described_class.latest(user, group)).to eq recent end + + it 'returns last 3 visited boards' do + create_visit(7.days.ago) + visit1 = create_visit(3.days.ago) + visit2 = create_visit(2.days.ago) + visit3 = create_visit(5.days.ago) + + expect(described_class.latest(user, group, count: 3)).to eq([visit2, visit1, visit3]) + end end end diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb index 275581945fa..e404fb3bbdb 100644 --- a/spec/models/board_project_recent_visit_spec.rb +++ b/spec/models/board_project_recent_visit_spec.rb @@ -50,15 +50,25 @@ describe BoardProjectRecentVisit do end describe '#latest' do - it 'returns the most recent visited' do - board2 = create(:board, project: project) - board3 = create(:board, project: project) + def create_visit(time) + create :board_project_recent_visit, project: project, user: user, updated_at: time + end - create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago - create :board_project_recent_visit, project: board2.project, board: board2, user: user, updated_at: 5.days.ago - recent = create :board_project_recent_visit, project: board3.project, board: board3, user: user, updated_at: 1.day.ago + it 'returns the most recent visited' do + create_visit(7.days.ago) + create_visit(5.days.ago) + recent = create_visit(1.day.ago) expect(described_class.latest(user, project)).to eq recent end + + it 'returns last 3 visited boards' do + create_visit(7.days.ago) + visit1 = create_visit(3.days.ago) + visit2 = create_visit(2.days.ago) + visit3 = create_visit(5.days.ago) + + expect(described_class.latest(user, project, count: 3)).to eq([visit2, visit1, visit3]) + end end end diff --git a/spec/services/boards/visits/latest_service_spec.rb b/spec/services/boards/visits/latest_service_spec.rb index e55d599e2cc..c8a0a5e4243 100644 --- a/spec/services/boards/visits/latest_service_spec.rb +++ b/spec/services/boards/visits/latest_service_spec.rb @@ -23,6 +23,12 @@ describe Boards::Visits::LatestService do service.execute end + + it 'queries for last N visits' do + expect(BoardProjectRecentVisit).to receive(:latest).with(user, project, count: 5).once + + described_class.new(project_board.parent, user, count: 5).execute + end end context 'when a group board' do @@ -42,6 +48,12 @@ describe Boards::Visits::LatestService do service.execute end + + it 'queries for last N visits' do + expect(BoardGroupRecentVisit).to receive(:latest).with(user, group, count: 5).once + + described_class.new(group_board.parent, user, count: 5).execute + end end end end -- cgit v1.2.1 From 529c570c0271a94f7356815f8cb63dca9ed3f716 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 27 Feb 2019 20:12:40 +0100 Subject: Move related issues shared components from EE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will rewrite Related MRs widget in CE with Vue. It’s pretty much the same with Related Issues in EE. I made EE only components reusable and this is the CE backward compatability commit. Links: Issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/57662 MR: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9730 --- .../components/issue/related_issuable_item_spec.js | 194 +++++++++++++++++++++ .../components/issue/related_issuable_mock_data.js | 111 ++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js create mode 100644 spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js (limited to 'spec') diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js b/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js new file mode 100644 index 00000000000..42198e92eea --- /dev/null +++ b/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js @@ -0,0 +1,194 @@ +import Vue from 'vue'; +import { mount, createLocalVue } from '@vue/test-utils'; +import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; +import { defaultMilestone, defaultAssignees } from './related_issuable_mock_data'; + +describe('RelatedIssuableItem', () => { + let wrapper; + const props = { + idKey: 1, + displayReference: 'gitlab-org/gitlab-test#1', + pathIdSeparator: '#', + path: `${gl.TEST_HOST}/path`, + title: 'title', + confidential: true, + dueDate: '1990-12-31', + weight: 10, + createdAt: '2018-12-01T00:00:00.00Z', + milestone: defaultMilestone, + assignees: defaultAssignees, + eventNamespace: 'relatedIssue', + }; + const slots = { + dueDate: '
    ', + weight: '
    ', + }; + + beforeEach(() => { + const localVue = createLocalVue(); + + wrapper = mount(localVue.extend(RelatedIssuableItem), { + localVue, + slots, + sync: false, + propsData: props, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('contains issuable-info-container class when canReorder is false', () => { + expect(wrapper.props('canReorder')).toBe(false); + expect(wrapper.find('.issuable-info-container').exists()).toBe(true); + }); + + it('does not render token state', () => { + expect(wrapper.find('.text-secondary svg').exists()).toBe(false); + }); + + it('does not render remove button', () => { + expect(wrapper.find({ ref: 'removeButton' }).exists()).toBe(false); + }); + + describe('token title', () => { + it('links to computedPath', () => { + expect(wrapper.find('.item-title a').attributes('href')).toEqual(wrapper.props('path')); + }); + + it('renders confidential icon', () => { + expect(wrapper.find('.confidential-icon').exists()).toBe(true); + }); + + it('renders title', () => { + expect(wrapper.find('.item-title a').text()).toEqual(props.title); + }); + }); + + describe('token state', () => { + let tokenState; + + beforeEach(done => { + wrapper.setProps({ state: 'opened' }); + + Vue.nextTick(() => { + tokenState = wrapper.find('.issue-token-state-icon-open'); + + done(); + }); + }); + + it('renders if hasState', () => { + expect(tokenState.exists()).toBe(true); + }); + + it('renders state title', () => { + const stateTitle = tokenState.attributes('data-original-title'); + + expect(stateTitle).toContain('Opened'); + expect(stateTitle).toContain( + 'Dec 1, 2018 12:00am GMT+0000', + ); + }); + + it('renders aria label', () => { + expect(tokenState.attributes('aria-label')).toEqual('opened'); + }); + + it('renders open icon when open state', () => { + expect(tokenState.classes('issue-token-state-icon-open')).toBe(true); + }); + + it('renders close icon when close state', done => { + wrapper.setProps({ + state: 'closed', + closedAt: '2018-12-01T00:00:00.00Z', + }); + + Vue.nextTick(() => { + expect(tokenState.classes('issue-token-state-icon-closed')).toBe(true); + + done(); + }); + }); + }); + + describe('token metadata', () => { + let tokenMetadata; + + beforeEach(done => { + Vue.nextTick(() => { + tokenMetadata = wrapper.find('.item-meta'); + + done(); + }); + }); + + it('renders item path and ID', () => { + const pathAndID = tokenMetadata.find('.item-path-id').text(); + + expect(pathAndID).toContain('gitlab-org/gitlab-test'); + expect(pathAndID).toContain('#1'); + }); + + it('renders milestone icon and name', () => { + const milestoneIcon = tokenMetadata.find('.item-milestone svg use'); + const milestoneTitle = tokenMetadata.find('.item-milestone .milestone-title'); + + expect(milestoneIcon.attributes('href')).toContain('clock'); + expect(milestoneTitle.text()).toContain('Milestone title'); + }); + + it('renders due date component', () => { + expect(tokenMetadata.find('.js-due-date-slot').exists()).toBe(true); + }); + + it('renders weight component', () => { + expect(tokenMetadata.find('.js-weight-slot').exists()).toBe(true); + }); + }); + + describe('token assignees', () => { + it('renders assignees avatars', () => { + expect(wrapper.findAll('.item-assignees .user-avatar-link').length).toBe(2); + expect(wrapper.find('.item-assignees .avatar-counter').text()).toContain('+2'); + }); + }); + + describe('remove button', () => { + let removeBtn; + + beforeEach(done => { + wrapper.setProps({ canRemove: true }); + Vue.nextTick(() => { + removeBtn = wrapper.find({ ref: 'removeButton' }); + + done(); + }); + }); + + it('renders if canRemove', () => { + expect(removeBtn.exists()).toBe(true); + }); + + it('renders disabled button when removeDisabled', done => { + wrapper.vm.removeDisabled = true; + + Vue.nextTick(() => { + expect(removeBtn.attributes('disabled')).toEqual('disabled'); + + done(); + }); + }); + + it('triggers onRemoveRequest when clicked', () => { + removeBtn.trigger('click'); + + const { relatedIssueRemoveRequest } = wrapper.emitted(); + + expect(relatedIssueRemoveRequest.length).toBe(1); + expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js new file mode 100644 index 00000000000..26bfdd7551e --- /dev/null +++ b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js @@ -0,0 +1,111 @@ +export const defaultProps = { + endpoint: '/foo/bar/issues/1/related_issues', + currentNamespacePath: 'foo', + currentProjectPath: 'bar', +}; + +export const issuable1 = { + id: 200, + epic_issue_id: 1, + confidential: false, + reference: 'foo/bar#123', + displayReference: '#123', + title: 'some title', + path: '/foo/bar/issues/123', + state: 'opened', +}; + +export const issuable2 = { + id: 201, + epic_issue_id: 2, + confidential: false, + reference: 'foo/bar#124', + displayReference: '#124', + title: 'some other thing', + path: '/foo/bar/issues/124', + state: 'opened', +}; + +export const issuable3 = { + id: 202, + epic_issue_id: 3, + confidential: false, + reference: 'foo/bar#125', + displayReference: '#125', + title: 'some other other thing', + path: '/foo/bar/issues/125', + state: 'opened', +}; + +export const issuable4 = { + id: 203, + epic_issue_id: 4, + confidential: false, + reference: 'foo/bar#126', + displayReference: '#126', + title: 'some other other other thing', + path: '/foo/bar/issues/126', + state: 'opened', +}; + +export const issuable5 = { + id: 204, + epic_issue_id: 5, + confidential: false, + reference: 'foo/bar#127', + displayReference: '#127', + title: 'some other other other thing', + path: '/foo/bar/issues/127', + state: 'opened', +}; + +export const defaultMilestone = { + id: 1, + state: 'active', + title: 'Milestone title', + start_date: '2018-01-01', + due_date: '2019-12-31', +}; + +export const defaultAssignees = [ + { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/root`, + status_tooltip_html: null, + path: '/root', + }, + { + id: 13, + name: 'Brooks Beatty', + username: 'brynn_champlin', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/brynn_champlin`, + status_tooltip_html: null, + path: '/brynn_champlin', + }, + { + id: 6, + name: 'Bryce Turcotte', + username: 'melynda', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/melynda`, + status_tooltip_html: null, + path: '/melynda', + }, + { + id: 20, + name: 'Conchita Eichmann', + username: 'juliana_gulgowski', + state: 'active', + avatar_url: `${gl.TEST_HOST}`, + web_url: `${gl.TEST_HOST}/juliana_gulgowski`, + status_tooltip_html: null, + path: '/juliana_gulgowski', + }, +]; -- cgit v1.2.1 From f62dc67d262792829bdcaef779b3915e9cc93a4f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 25 Feb 2019 20:05:26 +0900 Subject: Add suffix for merge request event Fix ok Add spec Fix ok Fix Add changelog Fix Add memoization a fix --- spec/factories/merge_requests.rb | 2 +- .../user_sees_merge_request_pipelines_spec.rb | 8 ++--- spec/features/projects/pipelines/pipeline_spec.rb | 2 +- spec/lib/gitlab/ci/build/policy/changes_spec.rb | 2 +- spec/lib/gitlab/ci/build/policy/refs_spec.rb | 14 ++++++++ spec/lib/gitlab/ci/pipeline/chain/build_spec.rb | 4 +-- .../ci/pipeline/chain/validate/config_spec.rb | 2 +- spec/models/ci/build_spec.rb | 2 +- spec/models/ci/pipeline_spec.rb | 40 +++++++++++----------- spec/models/merge_request_spec.rb | 6 ++-- spec/requests/api/runner_spec.rb | 2 +- spec/services/ci/create_pipeline_service_spec.rb | 4 +-- .../services/merge_requests/create_service_spec.rb | 4 +-- .../merge_requests/refresh_service_spec.rb | 4 +-- ..._head_pipeline_for_merge_request_worker_spec.rb | 2 +- 15 files changed, 56 insertions(+), 42 deletions(-) (limited to 'spec') diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 0c0c5fc8a4c..18f724770b5 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -104,7 +104,7 @@ FactoryBot.define do trait :with_merge_request_pipeline do after(:build) do |merge_request| merge_request.merge_request_pipelines << build(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, merge_request: merge_request, project: merge_request.source_project) end diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb index 7b473faa884..97b2aa82fce 100644 --- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb @@ -49,7 +49,7 @@ describe 'Merge request > User sees merge request pipelines', :js do let!(:merge_request_pipeline) do Ci::CreatePipelineService.new(project, user, ref: 'feature') - .execute(:merge_request, merge_request: merge_request) + .execute(:merge_request_event, merge_request: merge_request) end before do @@ -81,7 +81,7 @@ describe 'Merge request > User sees merge request pipelines', :js do let!(:merge_request_pipeline_2) do Ci::CreatePipelineService.new(project, user, ref: 'feature') - .execute(:merge_request, merge_request: merge_request) + .execute(:merge_request_event, merge_request: merge_request) end before do @@ -220,7 +220,7 @@ describe 'Merge request > User sees merge request pipelines', :js do let!(:merge_request_pipeline) do Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature') - .execute(:merge_request, merge_request: merge_request) + .execute(:merge_request_event, merge_request: merge_request) end let(:forked_project) { fork_project(project, user2, repository: true) } @@ -263,7 +263,7 @@ describe 'Merge request > User sees merge request pipelines', :js do let!(:merge_request_pipeline_2) do Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature') - .execute(:merge_request, merge_request: merge_request) + .execute(:merge_request_event, merge_request: merge_request) end before do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 72ef460d315..36b8c15b8b6 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -666,7 +666,7 @@ describe 'Pipeline', :js do let(:pipeline) do create(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, project: merge_request.source_project, ref: 'feature', sha: merge_request.diff_head_sha, diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb index dc3329061d1..92cf0376c02 100644 --- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb @@ -133,7 +133,7 @@ describe Gitlab::Ci::Build::Policy::Changes do let(:seed) { double('build seed', to_resource: ci_build) } context 'when source is merge request' do - let(:source) { :merge_request } + let(:source) { :merge_request_event } let(:merge_request) do create(:merge_request, diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb index 553fc0fb9bf..b4ddbf89b70 100644 --- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb @@ -68,6 +68,20 @@ describe Gitlab::Ci::Build::Policy::Refs do expect(described_class.new(%w[triggers])) .not_to be_satisfied_by(pipeline) end + + context 'when source is merge_request_event' do + let(:pipeline) { build_stubbed(:ci_pipeline, source: :merge_request_event) } + + it 'is satisfied with only: merge_request' do + expect(described_class.new(%w[merge_requests])) + .to be_satisfied_by(pipeline) + end + + it 'is not satisfied with only: merge_request_event' do + expect(described_class.new(%w[merge_request_events])) + .not_to be_satisfied_by(pipeline) + end + end end context 'when matching a ref by a regular expression' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index c9d1d09a938..3debd42ac65 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -96,7 +96,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do context 'when pipeline is running for a merge request' do let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( - source: :merge_request, + source: :merge_request_event, origin_ref: 'feature', checkout_sha: project.commit.id, after_sha: nil, @@ -117,7 +117,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do end it 'correctly indicated that this is a merge request pipeline' do - expect(pipeline).to be_merge_request + expect(pipeline).to be_merge_request_event expect(pipeline.merge_request).to eq(merge_request) end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb index 053bc421649..e6c6a82b463 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb @@ -115,7 +115,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do let(:pipeline) { build_stubbed(:ci_pipeline, project: project) } let(:merge_request_pipeline) do - build(:ci_pipeline, source: :merge_request, project: project) + build(:ci_pipeline, source: :merge_request_event, project: project) end let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index e5281f3a09e..fc75d3e23fb 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -23,7 +23,7 @@ describe Ci::Build do it { is_expected.to validate_presence_of(:ref) } it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:trace) } - it { is_expected.to delegate_method(:merge_request?).to(:pipeline) } + it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) } it { is_expected.to be_a(ArtifactMigratable) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ee400bec04b..462793b259b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -80,11 +80,11 @@ describe Ci::Pipeline, :mailer do context 'when merge request pipelines exist' do let!(:merge_request_pipeline_1) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let!(:merge_request_pipeline_2) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let(:merge_request) do @@ -106,11 +106,11 @@ describe Ci::Pipeline, :mailer do let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) } let!(:merge_request_pipeline_1) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let!(:merge_request_pipeline_2) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let(:merge_request) do @@ -134,7 +134,7 @@ describe Ci::Pipeline, :mailer do subject { described_class.detached_merge_request_pipelines(merge_request) } let!(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) end let(:merge_request) { create(:merge_request) } @@ -157,7 +157,7 @@ describe Ci::Pipeline, :mailer do subject { pipeline.detached_merge_request_pipeline? } let!(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) end let(:merge_request) { create(:merge_request) } @@ -176,7 +176,7 @@ describe Ci::Pipeline, :mailer do subject { described_class.merge_request_pipelines(merge_request) } let!(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) end let(:merge_request) { create(:merge_request) } @@ -199,7 +199,7 @@ describe Ci::Pipeline, :mailer do subject { pipeline.merge_request_pipeline? } let!(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) end let(:merge_request) { create(:merge_request) } @@ -218,7 +218,7 @@ describe Ci::Pipeline, :mailer do subject { described_class.mergeable_merge_request_pipelines(merge_request) } let!(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) end let(:merge_request) { create(:merge_request) } @@ -241,7 +241,7 @@ describe Ci::Pipeline, :mailer do subject { pipeline.mergeable_merge_request_pipeline? } let!(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) end let(:merge_request) { create(:merge_request) } @@ -256,11 +256,11 @@ describe Ci::Pipeline, :mailer do end end - describe '.merge_request' do - subject { described_class.merge_request } + describe '.merge_request_event' do + subject { described_class.merge_request_event } context 'when there is a merge request pipeline' do - let!(:pipeline) { create(:ci_pipeline, source: :merge_request, merge_request: merge_request) } + let!(:pipeline) { create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) } let(:merge_request) { create(:merge_request) } it 'returns merge request pipeline first' do @@ -281,7 +281,7 @@ describe Ci::Pipeline, :mailer do let(:pipeline) { build(:ci_pipeline, source: source, merge_request: merge_request) } context 'when source is merge request' do - let(:source) { :merge_request } + let(:source) { :merge_request_event } context 'when merge request is specified' do let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_project: project, target_branch: 'master') } @@ -505,7 +505,7 @@ describe Ci::Pipeline, :mailer do context 'when source is merge request' do let(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let(:merge_request) do @@ -1097,7 +1097,7 @@ describe Ci::Pipeline, :mailer do context 'when source is merge request' do let(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let(:merge_request) do @@ -1147,7 +1147,7 @@ describe Ci::Pipeline, :mailer do context 'when ref is merge request' do let(:pipeline) do create(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, merge_request: merge_request) end @@ -1310,7 +1310,7 @@ describe Ci::Pipeline, :mailer do context 'when source is merge request' do let(:pipeline) do - create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let(:merge_request) do @@ -2266,7 +2266,7 @@ describe Ci::Pipeline, :mailer do let!(:pipeline) do create(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, project: pipeline_project, ref: source_branch, merge_request: merge_request) @@ -2289,7 +2289,7 @@ describe Ci::Pipeline, :mailer do let!(:pipeline_2) do create(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, project: pipeline_project, ref: source_branch, merge_request: merge_request_2) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 82a853a23b9..07cb4c9c1e3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1333,7 +1333,7 @@ describe MergeRequest do let!(:merge_request_pipeline) do create(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, project: project, ref: source_ref, sha: shas.second, @@ -1372,7 +1372,7 @@ describe MergeRequest do let!(:merge_request_pipeline_2) do create(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, project: project, ref: source_ref, sha: shas.first, @@ -1399,7 +1399,7 @@ describe MergeRequest do let!(:merge_request_pipeline_2) do create(:ci_pipeline, - source: :merge_request, + source: :merge_request_event, project: project, ref: source_ref, sha: shas.first, diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 43c06f7c973..9087cccb759 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -549,7 +549,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end context 'when job is made for merge request' do - let(:pipeline) { create(:ci_pipeline_without_jobs, source: :merge_request, project: project, ref: 'feature', merge_request: merge_request) } + let(:pipeline) { create(:ci_pipeline_without_jobs, source: :merge_request_event, project: project, ref: 'feature', merge_request: merge_request) } let!(:job) { create(:ci_build, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) } let(:merge_request) { create(:merge_request) } diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 93349ba7b5b..24707cd2d41 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -701,7 +701,7 @@ describe Ci::CreatePipelineService do let(:target_sha) { nil } context 'when source is merge request' do - let(:source) { :merge_request } + let(:source) { :merge_request_event } context "when config has merge_requests keywords" do let(:config) do @@ -734,7 +734,7 @@ describe Ci::CreatePipelineService do it 'creates a merge request pipeline' do expect(pipeline).to be_persisted - expect(pipeline).to be_merge_request + expect(pipeline).to be_merge_request_event expect(pipeline.merge_request).to eq(merge_request) expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test]) end diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index b46aa65818d..a04a4d5fc36 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -194,7 +194,7 @@ describe MergeRequests::CreateService do merge_request.reload expect(merge_request.merge_request_pipelines.count).to eq(1) - expect(merge_request.actual_head_pipeline).to be_merge_request + expect(merge_request.actual_head_pipeline).to be_merge_request_event end context 'when there are no commits between source branch and target branch' do @@ -226,7 +226,7 @@ describe MergeRequests::CreateService do end it 'sets the latest merge request pipeline as the head pipeline' do - expect(merge_request.actual_head_pipeline).to be_merge_request + expect(merge_request.actual_head_pipeline).to be_merge_request_event end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 9e9dc5a576c..43ceb1dcbee 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -173,12 +173,12 @@ describe MergeRequests::RefreshService do it 'sets the latest merge request pipeline as a head pipeline' do @merge_request.reload - expect(@merge_request.actual_head_pipeline).to be_merge_request + expect(@merge_request.actual_head_pipeline).to be_merge_request_event end it 'returns pipelines in correct order' do @merge_request.reload - expect(@merge_request.all_pipelines.first).to be_merge_request + expect(@merge_request.all_pipelines.first).to be_merge_request_event expect(@merge_request.all_pipelines.second).to be_push end end diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb index 963237ceadf..a13a3046f55 100644 --- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb +++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb @@ -39,7 +39,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do let!(:merge_request_pipeline) do create(:ci_pipeline, project: project, - source: :merge_request, + source: :merge_request_event, sha: latest_sha, merge_request: merge_request) end -- cgit v1.2.1 From 1592b5830f7b2847dff02ef2c66b745cdc60565a Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 17 Jan 2019 02:57:35 +0100 Subject: Adds Rollback functionality to HashedStorage migration We are adding sidekiq workers and service classes to allow to rollback a hashed storage migration. There are some refactoring involved as well as part of the code can be reused by both the migration and the rollback logic. --- spec/lib/gitlab/hashed_storage/migrator_spec.rb | 46 ++++++++++ spec/models/project_spec.rb | 38 ++++++++ .../rollback_repository_service_spec.rb | 101 +++++++++++++++++++++ .../hashed_storage/rollback_service_spec.rb | 57 ++++++++++++ .../project_rollback_hashed_storage_worker_spec.rb | 50 ++++++++++ 5 files changed, 292 insertions(+) create mode 100644 spec/services/projects/hashed_storage/rollback_repository_service_spec.rb create mode 100644 spec/services/projects/hashed_storage/rollback_service_spec.rb create mode 100644 spec/workers/project_rollback_hashed_storage_worker_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb index 3942f168ceb..7c2582bb27a 100644 --- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb +++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb @@ -88,4 +88,50 @@ describe Gitlab::HashedStorage::Migrator do end end end + + describe '#rollback' do + let(:project) { create(:project, :empty_repo) } + + it 'enqueues project rollback job' do + Sidekiq::Testing.fake! do + expect { subject.rollback(project) }.to change(ProjectRollbackHashedStorageWorker.jobs, :size).by(1) + end + end + + it 'rescues and log exceptions' do + allow(project).to receive(:rollback_to_hashed_storage!).and_raise(StandardError) + + expect { subject.rollback(project) }.not_to raise_error + end + + it 'rolls-back project storage' do + perform_enqueued_jobs do + subject.rollback(project) + end + + expect(project.reload.legacy_storage?).to be_truthy + end + + it 'has rolled-back project set as writable' do + perform_enqueued_jobs do + subject.rollback(project) + end + + expect(project.reload.repository_read_only?).to be_falsey + end + + context 'when project is already on legacy storage' do + let(:project) { create(:project, :legacy_storage, :empty_repo) } + + it 'doesnt enqueue any rollback job' do + Sidekiq::Testing.fake! do + expect { subject.rollback(project) }.not_to change(ProjectRollbackHashedStorageWorker.jobs, :size) + end + end + + it 'returns false' do + expect(subject.rollback(project)).to be_falsey + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9fb0d04ca9e..6dc89b352f3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3452,6 +3452,20 @@ describe Project do project.migrate_to_hashed_storage! end end + + describe '#rollback_to_legacy_storage!' do + let(:project) { create(:project, :empty_repo, :legacy_storage) } + + it 'returns nil' do + expect(project.rollback_to_legacy_storage!).to be_nil + end + + it 'does not run validations' do + expect(project).not_to receive(:valid?) + + project.rollback_to_legacy_storage! + end + end end context 'hashed storage' do @@ -3532,6 +3546,30 @@ describe Project do end end end + + describe '#rollback_to_legacy_storage!' do + let(:project) { create(:project, :repository, skip_disk_validation: true) } + + it 'returns true' do + expect(project.rollback_to_legacy_storage!).to be_truthy + end + + it 'does not run validations' do + expect(project).not_to receive(:valid?) + + project.rollback_to_legacy_storage! + end + + it 'does not flag as read-only' do + expect { project.rollback_to_legacy_storage! }.not_to change { project.repository_read_only } + end + + it 'enqueues a job' do + Sidekiq::Testing.fake! do + expect { project.rollback_to_legacy_storage! }.to change(ProjectRollbackHashedStorageWorker.jobs, :size).by(1) + end + end + end end describe '#gl_repository' do diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb new file mode 100644 index 00000000000..c9d0a085d21 --- /dev/null +++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do + include GitHelpers + + let(:gitlab_shell) { Gitlab::Shell.new } + let(:project) { create(:project, :repository, :wiki_repo, storage_version: ::Project::HASHED_STORAGE_FEATURES[:repository]) } + let(:legacy_storage) { Storage::LegacyProject.new(project) } + let(:hashed_storage) { Storage::HashedProject.new(project) } + + subject(:service) { described_class.new(project, project.disk_path) } + + describe '#execute' do + let(:old_disk_path) { hashed_storage.disk_path } + let(:new_disk_path) { legacy_storage.disk_path } + + before do + allow(service).to receive(:gitlab_shell) { gitlab_shell } + end + + context 'repository lock' do + it 'tries to lock the repository' do + expect(service).to receive(:try_to_set_repository_read_only!) + + service.execute + end + + it 'fails when a git operation is in progress' do + allow(project).to receive(:repo_reference_count) { 1 } + + expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryRollbackError) + end + end + + context 'when succeeds' do + it 'renames project and wiki repositories' do + service.execute + + expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.wiki.git")).to be_truthy + end + + it 'updates project to be legacy and not read-only' do + service.execute + + expect(project.legacy_storage?).to be_truthy + expect(project.repository_read_only).to be_falsey + end + + it 'move operation is called for both repositories' do + expect_move_repository(old_disk_path, new_disk_path) + expect_move_repository("#{old_disk_path}.wiki", "#{new_disk_path}.wiki") + + service.execute + end + + it 'writes project full path to .git/config' do + service.execute + + rugged_config = rugged_repo(project.repository).config['gitlab.fullpath'] + + expect(rugged_config).to eq project.full_path + end + end + + context 'when one move fails' do + it 'rollsback repositories to original name' do + allow(service).to receive(:move_repository).and_call_original + allow(service).to receive(:move_repository).with(old_disk_path, new_disk_path).once { false } # will disable first move only + + expect(service).to receive(:rollback_folder_move).and_call_original + + service.execute + + expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.wiki.git")).to be_falsey + expect(project.repository_read_only?).to be_falsey + end + + context 'when rollback fails' do + before do + legacy_storage.ensure_storage_path_exists + gitlab_shell.mv_repository(project.repository_storage, old_disk_path, new_disk_path) + end + + it 'does not try to move nil repository over existing' do + expect(gitlab_shell).not_to receive(:mv_repository).with(project.repository_storage, old_disk_path, new_disk_path) + expect_move_repository("#{old_disk_path}.wiki", "#{new_disk_path}.wiki") + + service.execute + end + end + end + + def expect_move_repository(from_name, to_name) + expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage, from_name, to_name).and_call_original + end + end +end diff --git a/spec/services/projects/hashed_storage/rollback_service_spec.rb b/spec/services/projects/hashed_storage/rollback_service_spec.rb new file mode 100644 index 00000000000..427d1535559 --- /dev/null +++ b/spec/services/projects/hashed_storage/rollback_service_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::HashedStorage::RollbackService do + let(:project) { create(:project, :empty_repo, :wiki_repo) } + let(:logger) { double } + + subject(:service) { described_class.new(project, project.full_path, logger: logger) } + + describe '#execute' do + context 'attachments rollback' do + let(:attachments_service_class) { Projects::HashedStorage::RollbackAttachmentsService } + let(:attachments_service) { attachments_service_class.new(project, logger: logger) } + + it 'delegates rollback to Projects::HashedStorage::RollbackAttachmentsService' do + expect(attachments_service_class).to receive(:new) + .with(project, logger: logger) + .and_return(attachments_service) + expect(attachments_service).to receive(:execute) + + service.execute + end + + it 'does not delegate rollback if repository is in legacy storage already' do + project.storage_version = nil + expect(attachments_service_class).not_to receive(:new) + + service.execute + end + end + + context 'repository rollback' do + let(:repository_service_class) { Projects::HashedStorage::RollbackRepositoryService } + let(:repository_service) { repository_service_class.new(project, project.full_path, logger: logger) } + + it 'delegates rollback to RollbackRepositoryService' do + project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository] + + expect(repository_service_class).to receive(:new) + .with(project, project.full_path, logger: logger) + .and_return(repository_service) + expect(repository_service).to receive(:execute) + + service.execute + end + + it 'does not delegate rollback if repository is in legacy storage already' do + project.storage_version = nil + + expect(repository_service_class).not_to receive(:new) + + service.execute + end + end + end +end diff --git a/spec/workers/project_rollback_hashed_storage_worker_spec.rb b/spec/workers/project_rollback_hashed_storage_worker_spec.rb new file mode 100644 index 00000000000..aed7493763d --- /dev/null +++ b/spec/workers/project_rollback_hashed_storage_worker_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectRollbackHashedStorageWorker, :clean_gitlab_redis_shared_state do + include ExclusiveLeaseHelpers + + describe '#perform' do + let(:project) { create(:project, :empty_repo) } + let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" } + let(:lease_timeout) { described_class::LEASE_TIMEOUT } + let(:rollback_service) { ::Projects::HashedStorage::RollbackService } + + it 'skips when project no longer exists' do + expect(rollback_service).not_to receive(:new) + + subject.perform(-1) + end + + it 'skips when project is pending delete' do + pending_delete_project = create(:project, :empty_repo, pending_delete: true) + + expect(rollback_service).not_to receive(:new) + + subject.perform(pending_delete_project.id) + end + + it 'delegates rollback to service class when have exclusive lease' do + stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout) + + service_spy = spy + + allow(rollback_service) + .to receive(:new).with(project, project.disk_path, logger: subject.logger) + .and_return(service_spy) + + subject.perform(project.id) + + expect(service_spy).to have_received(:execute) + end + + it 'skips when it cant acquire the exclusive lease' do + stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) + + expect(rollback_service).not_to receive(:new) + + subject.perform(project.id) + end + end +end -- cgit v1.2.1 From d63380fa93dff921c69f7aaa31ff004864e4db13 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 23 Jan 2019 03:31:57 +0100 Subject: Refactor ProjectMigrate and ProjectRollback workers Moved to HashedStorage namespace, and added them to the `:hashed_storage` queue namespace --- spec/lib/gitlab/hashed_storage/migrator_spec.rb | 12 +++--- spec/models/project_spec.rb | 16 +++---- .../hashed_storage/project_migrate_worker_spec.rb | 48 +++++++++++++++++++++ .../hashed_storage/project_rollback_worker_spec.rb | 50 ++++++++++++++++++++++ .../project_migrate_hashed_storage_worker_spec.rb | 48 --------------------- .../project_rollback_hashed_storage_worker_spec.rb | 50 ---------------------- 6 files changed, 112 insertions(+), 112 deletions(-) create mode 100644 spec/workers/hashed_storage/project_migrate_worker_spec.rb create mode 100644 spec/workers/hashed_storage/project_rollback_worker_spec.rb delete mode 100644 spec/workers/project_migrate_hashed_storage_worker_spec.rb delete mode 100644 spec/workers/project_rollback_hashed_storage_worker_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb index 7c2582bb27a..000913f32bb 100644 --- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb +++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb @@ -13,9 +13,9 @@ describe Gitlab::HashedStorage::Migrator do let(:projects) { create_list(:project, 2, :legacy_storage) } let(:ids) { projects.map(&:id) } - it 'enqueue jobs to ProjectMigrateHashedStorageWorker' do + it 'enqueue jobs to HashedStorage::ProjectMigrateWorker' do Sidekiq::Testing.fake! do - expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(2) + expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(2) end end @@ -48,7 +48,7 @@ describe Gitlab::HashedStorage::Migrator do it 'enqueues project migration job' do Sidekiq::Testing.fake! do - expect { subject.migrate(project) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(1) + expect { subject.migrate(project) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1) end end @@ -79,7 +79,7 @@ describe Gitlab::HashedStorage::Migrator do it 'doesnt enqueue any migration job' do Sidekiq::Testing.fake! do - expect { subject.migrate(project) }.not_to change(ProjectMigrateHashedStorageWorker.jobs, :size) + expect { subject.migrate(project) }.not_to change(HashedStorage::ProjectMigrateWorker.jobs, :size) end end @@ -94,7 +94,7 @@ describe Gitlab::HashedStorage::Migrator do it 'enqueues project rollback job' do Sidekiq::Testing.fake! do - expect { subject.rollback(project) }.to change(ProjectRollbackHashedStorageWorker.jobs, :size).by(1) + expect { subject.rollback(project) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1) end end @@ -125,7 +125,7 @@ describe Gitlab::HashedStorage::Migrator do it 'doesnt enqueue any rollback job' do Sidekiq::Testing.fake! do - expect { subject.rollback(project) }.not_to change(ProjectRollbackHashedStorageWorker.jobs, :size) + expect { subject.rollback(project) }.not_to change(HashedStorage::ProjectRollbackWorker.jobs, :size) end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6dc89b352f3..b2392f9521f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3430,24 +3430,24 @@ describe Project do project.migrate_to_hashed_storage! end - it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the project repo is in use' do + it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the project repo is in use' do Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase - expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in) + expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in) project.migrate_to_hashed_storage! end - it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the wiki repo is in use' do + it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase - expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in) + expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in) project.migrate_to_hashed_storage! end - it 'schedules ProjectMigrateHashedStorageWorker' do - expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).with(project.id) + it 'schedules HashedStorage::ProjectMigrateWorker' do + expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_async).with(project.id) project.migrate_to_hashed_storage! end @@ -3541,7 +3541,7 @@ describe Project do project = create(:project, storage_version: 1, skip_disk_validation: true) Sidekiq::Testing.fake! do - expect { project.migrate_to_hashed_storage! }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(1) + expect { project.migrate_to_hashed_storage! }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1) end end end @@ -3566,7 +3566,7 @@ describe Project do it 'enqueues a job' do Sidekiq::Testing.fake! do - expect { project.rollback_to_legacy_storage! }.to change(ProjectRollbackHashedStorageWorker.jobs, :size).by(1) + expect { project.rollback_to_legacy_storage! }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1) end end end diff --git a/spec/workers/hashed_storage/project_migrate_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb new file mode 100644 index 00000000000..340e722aa7e --- /dev/null +++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do + include ExclusiveLeaseHelpers + + describe '#perform' do + let(:project) { create(:project, :empty_repo, :legacy_storage) } + let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" } + let(:lease_timeout) { described_class::LEASE_TIMEOUT } + let(:migration_service) { ::Projects::HashedStorage::MigrationService } + + it 'skips when project no longer exists' do + expect(migration_service).not_to receive(:new) + + subject.perform(-1) + end + + it 'skips when project is pending delete' do + pending_delete_project = create(:project, :empty_repo, pending_delete: true) + + expect(migration_service).not_to receive(:new) + + subject.perform(pending_delete_project.id) + end + + it 'delegates migration to service class when we have exclusive lease' do + stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout) + + service_spy = spy + + allow(migration_service) + .to receive(:new).with(project, project.full_path, logger: subject.logger) + .and_return(service_spy) + + subject.perform(project.id) + + expect(service_spy).to have_received(:execute) + end + + it 'skips when it cant acquire the exclusive lease' do + stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) + + expect(migration_service).not_to receive(:new) + + subject.perform(project.id) + end + end +end diff --git a/spec/workers/hashed_storage/project_rollback_worker_spec.rb b/spec/workers/hashed_storage/project_rollback_worker_spec.rb new file mode 100644 index 00000000000..d833553c0ec --- /dev/null +++ b/spec/workers/hashed_storage/project_rollback_worker_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe HashedStorage::ProjectRollbackWorker, :clean_gitlab_redis_shared_state do + include ExclusiveLeaseHelpers + + describe '#perform' do + let(:project) { create(:project, :empty_repo) } + let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" } + let(:lease_timeout) { described_class::LEASE_TIMEOUT } + let(:rollback_service) { ::Projects::HashedStorage::RollbackService } + + it 'skips when project no longer exists' do + expect(rollback_service).not_to receive(:new) + + subject.perform(-1) + end + + it 'skips when project is pending delete' do + pending_delete_project = create(:project, :empty_repo, pending_delete: true) + + expect(rollback_service).not_to receive(:new) + + subject.perform(pending_delete_project.id) + end + + it 'delegates rollback to service class when have exclusive lease' do + stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout) + + service_spy = spy + + allow(rollback_service) + .to receive(:new).with(project, project.disk_path, logger: subject.logger) + .and_return(service_spy) + + subject.perform(project.id) + + expect(service_spy).to have_received(:execute) + end + + it 'skips when it cant acquire the exclusive lease' do + stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) + + expect(rollback_service).not_to receive(:new) + + subject.perform(project.id) + end + end +end diff --git a/spec/workers/project_migrate_hashed_storage_worker_spec.rb b/spec/workers/project_migrate_hashed_storage_worker_spec.rb deleted file mode 100644 index 333eb6a0569..00000000000 --- a/spec/workers/project_migrate_hashed_storage_worker_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe ProjectMigrateHashedStorageWorker, :clean_gitlab_redis_shared_state do - include ExclusiveLeaseHelpers - - describe '#perform' do - let(:project) { create(:project, :empty_repo, :legacy_storage) } - let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" } - let(:lease_timeout) { described_class::LEASE_TIMEOUT } - let(:migration_service) { ::Projects::HashedStorage::MigrationService } - - it 'skips when project no longer exists' do - expect(migration_service).not_to receive(:new) - - subject.perform(-1) - end - - it 'skips when project is pending delete' do - pending_delete_project = create(:project, :empty_repo, pending_delete: true) - - expect(migration_service).not_to receive(:new) - - subject.perform(pending_delete_project.id) - end - - it 'delegates migration to service class when we have exclusive lease' do - stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout) - - service_spy = spy - - allow(migration_service) - .to receive(:new).with(project, project.full_path, logger: subject.logger) - .and_return(service_spy) - - subject.perform(project.id) - - expect(service_spy).to have_received(:execute) - end - - it 'skips when it cant acquire the exclusive lease' do - stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) - - expect(migration_service).not_to receive(:new) - - subject.perform(project.id) - end - end -end diff --git a/spec/workers/project_rollback_hashed_storage_worker_spec.rb b/spec/workers/project_rollback_hashed_storage_worker_spec.rb deleted file mode 100644 index aed7493763d..00000000000 --- a/spec/workers/project_rollback_hashed_storage_worker_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe ProjectRollbackHashedStorageWorker, :clean_gitlab_redis_shared_state do - include ExclusiveLeaseHelpers - - describe '#perform' do - let(:project) { create(:project, :empty_repo) } - let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" } - let(:lease_timeout) { described_class::LEASE_TIMEOUT } - let(:rollback_service) { ::Projects::HashedStorage::RollbackService } - - it 'skips when project no longer exists' do - expect(rollback_service).not_to receive(:new) - - subject.perform(-1) - end - - it 'skips when project is pending delete' do - pending_delete_project = create(:project, :empty_repo, pending_delete: true) - - expect(rollback_service).not_to receive(:new) - - subject.perform(pending_delete_project.id) - end - - it 'delegates rollback to service class when have exclusive lease' do - stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout) - - service_spy = spy - - allow(rollback_service) - .to receive(:new).with(project, project.disk_path, logger: subject.logger) - .and_return(service_spy) - - subject.perform(project.id) - - expect(service_spy).to have_received(:execute) - end - - it 'skips when it cant acquire the exclusive lease' do - stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) - - expect(rollback_service).not_to receive(:new) - - subject.perform(project.id) - end - end -end -- cgit v1.2.1 From fc0ff92807620c36d01f23eb0d7d88b02cb141c1 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 23 Jan 2019 03:40:05 +0100 Subject: Added Rollbacker workers and support on the rake task Rollback is done similar to Migration for the Hashed Storage. It also shares the same ExclusiveLease key to prevent both happening at the same time. All Hashed Storage related workers now share the same queue namespace which allows for assigning dedicated workers easily. --- spec/lib/gitlab/hashed_storage/migrator_spec.rb | 60 +++++++++++-- .../migrate_attachments_service_spec.rb | 2 + .../rollback_attachments_service_spec.rb | 100 +++++++++++++++++++++ .../rollback_repository_service_spec.rb | 2 +- .../hashed_storage/rollbacker_worker_spec.rb | 27 ++++++ 5 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb create mode 100644 spec/workers/hashed_storage/rollbacker_worker_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb index 000913f32bb..6154b3e2f76 100644 --- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb +++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb @@ -1,16 +1,24 @@ require 'spec_helper' describe Gitlab::HashedStorage::Migrator do - describe '#bulk_schedule' do - it 'schedules job to StorageMigratorWorker' do + describe '#bulk_schedule_migration' do + it 'schedules job to HashedStorage::MigratorWorker' do Sidekiq::Testing.fake! do - expect { subject.bulk_schedule(start: 1, finish: 5) }.to change(HashedStorage::MigratorWorker.jobs, :size).by(1) + expect { subject.bulk_schedule_migration(start: 1, finish: 5) }.to change(HashedStorage::MigratorWorker.jobs, :size).by(1) + end + end + end + + describe '#bulk_schedule_rollback' do + it 'schedules job to HashedStorage::RollbackerWorker' do + Sidekiq::Testing.fake! do + expect { subject.bulk_schedule_rollback(start: 1, finish: 5) }.to change(HashedStorage::RollbackerWorker.jobs, :size).by(1) end end end describe '#bulk_migrate' do - let(:projects) { create_list(:project, 2, :legacy_storage) } + let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) } let(:ids) { projects.map(&:id) } it 'enqueue jobs to HashedStorage::ProjectMigrateWorker' do @@ -32,13 +40,53 @@ describe Gitlab::HashedStorage::Migrator do subject.bulk_migrate(start: ids.min, finish: ids.max) end - it 'has migrated projects set as writable' do + it 'has all projects migrated and set as writable' do perform_enqueued_jobs do subject.bulk_migrate(start: ids.min, finish: ids.max) end projects.each do |project| - expect(project.reload.repository_read_only?).to be_falsey + project.reload + + expect(project.hashed_storage?(:repository)).to be_truthy + expect(project.repository_read_only?).to be_falsey + end + end + end + + describe '#bulk_rollback' do + let(:projects) { create_list(:project, 2, :empty_repo) } + let(:ids) { projects.map(&:id) } + + it 'enqueue jobs to HashedStorage::ProjectRollbackWorker' do + Sidekiq::Testing.fake! do + expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(2) + end + end + + it 'rescues and log exceptions' do + allow_any_instance_of(Project).to receive(:rollback_to_legacy_storage!).and_raise(StandardError) + expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.not_to raise_error + end + + it 'delegates each project in specified range to #rollback' do + projects.each do |project| + expect(subject).to receive(:rollback).with(project) + end + + subject.bulk_rollback(start: ids.min, finish: ids.max) + end + + it 'has all projects rolledback and set as writable' do + perform_enqueued_jobs do + subject.bulk_rollback(start: ids.min, finish: ids.max) + end + + projects.each do |project| + project.reload + + expect(project.legacy_storage?).to be_truthy + expect(project.repository_read_only?).to be_falsey end end end diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index 61dbb57ec08..d51988c6f42 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -86,6 +86,8 @@ describe Projects::HashedStorage::MigrateAttachmentsService do context '#new_disk_path' do it 'returns new disk_path for project' do + service.execute + expect(service.new_disk_path).to eq(project.disk_path) end end diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb new file mode 100644 index 00000000000..9e18428f412 --- /dev/null +++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::HashedStorage::RollbackAttachmentsService do + subject(:service) { described_class.new(project, logger: nil) } + + let(:project) { create(:project, :repository, skip_disk_validation: true) } + let(:legacy_storage) { Storage::LegacyProject.new(project) } + let(:hashed_storage) { Storage::HashedProject.new(project) } + + let!(:upload) { Upload.find_by(path: file_uploader.upload_path) } + let(:file_uploader) { build(:file_uploader, project: project) } + let(:old_disk_path) { File.join(base_path(hashed_storage), upload.path) } + let(:new_disk_path) { File.join(base_path(legacy_storage), upload.path) } + + context '#execute' do + context 'when succeeds' do + it 'moves attachments to legacy storage layout' do + expect(File.file?(old_disk_path)).to be_truthy + expect(File.file?(new_disk_path)).to be_falsey + expect(File.exist?(base_path(hashed_storage))).to be_truthy + expect(File.exist?(base_path(legacy_storage))).to be_falsey + expect(FileUtils).to receive(:mv).with(base_path(hashed_storage), base_path(legacy_storage)).and_call_original + + service.execute + + expect(File.exist?(base_path(legacy_storage))).to be_truthy + expect(File.exist?(base_path(hashed_storage))).to be_falsey + expect(File.file?(old_disk_path)).to be_falsey + expect(File.file?(new_disk_path)).to be_truthy + end + + it 'returns true' do + expect(service.execute).to be_truthy + end + + it 'sets skipped to false' do + service.execute + + expect(service.skipped?).to be_falsey + end + end + + context 'when original folder does not exist anymore' do + before do + FileUtils.rm_rf(base_path(hashed_storage)) + end + + it 'skips moving folders and go to next' do + expect(FileUtils).not_to receive(:mv).with(base_path(hashed_storage), base_path(legacy_storage)) + + service.execute + + expect(File.exist?(base_path(legacy_storage))).to be_falsey + expect(File.file?(new_disk_path)).to be_falsey + end + + it 'returns true' do + expect(service.execute).to be_truthy + end + + it 'sets skipped to true' do + service.execute + + expect(service.skipped?).to be_truthy + end + end + + context 'when target folder already exists' do + before do + FileUtils.mkdir_p(base_path(legacy_storage)) + end + + it 'raises AttachmentRollbackError' do + expect(FileUtils).not_to receive(:mv).with(base_path(legacy_storage), base_path(hashed_storage)) + + expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentRollbackError) + end + end + end + + context '#old_disk_path' do + it 'returns old disk_path for project' do + expect(service.old_disk_path).to eq(project.disk_path) + end + end + + context '#new_disk_path' do + it 'returns new disk_path for project' do + service.execute + + expect(service.new_disk_path).to eq(project.full_path) + end + end + + def base_path(storage) + File.join(FileUploader.root, storage.disk_path) + end +end diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb index c9d0a085d21..7c3a243b3ca 100644 --- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb @@ -66,7 +66,7 @@ describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis end context 'when one move fails' do - it 'rollsback repositories to original name' do + it 'rolls repositories back to original name' do allow(service).to receive(:move_repository).and_call_original allow(service).to receive(:move_repository).with(old_disk_path, new_disk_path).once { false } # will disable first move only diff --git a/spec/workers/hashed_storage/rollbacker_worker_spec.rb b/spec/workers/hashed_storage/rollbacker_worker_spec.rb new file mode 100644 index 00000000000..4055f380978 --- /dev/null +++ b/spec/workers/hashed_storage/rollbacker_worker_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe HashedStorage::RollbackerWorker do + subject(:worker) { described_class.new } + let(:projects) { create_list(:project, 2, :empty_repo) } + let(:ids) { projects.map(&:id) } + + describe '#perform' do + it 'delegates to MigratorService' do + expect_any_instance_of(Gitlab::HashedStorage::Migrator).to receive(:bulk_rollback).with(start: 5, finish: 10) + + worker.perform(5, 10) + end + + it 'rollsback projects in the specified range' do + perform_enqueued_jobs do + worker.perform(ids.min, ids.max) + end + + projects.each do |project| + expect(project.reload.legacy_storage?).to be_truthy + end + end + end +end -- cgit v1.2.1 From 4bae61005dd835c7c8bf3ce911328a8e8af86a93 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 22 Feb 2019 09:12:06 +0100 Subject: Edge case: upgrade/downgrade when repository doesn't exist This change takes the case where repository doesn't exist on disk and make it ignore but succeed the step, increasing or decreasing the version depending on the operation. --- .../projects/hashed_storage/migrate_repository_service_spec.rb | 10 ++++++++++ .../hashed_storage/rollback_repository_service_spec.rb | 10 ++++++++++ 2 files changed, 20 insertions(+) (limited to 'spec') diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index 0772dc4b85b..cd56337420b 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -32,6 +32,16 @@ describe Projects::HashedStorage::MigrateRepositoryService do end end + context 'when repository doesnt exist on disk' do + let(:project) { create(:project, :legacy_storage) } + + it 'skips the disk change but increase the version' do + service.execute + + expect(project.hashed_storage?(:repository)).to be_truthy + end + end + context 'when succeeds' do it 'renames project and wiki repositories' do service.execute diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb index 7c3a243b3ca..daac220f710 100644 --- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb @@ -34,6 +34,16 @@ describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis end end + context 'when repository doesnt exist on disk' do + let(:project) { create(:project) } + + it 'skips the disk change but decrease the version' do + service.execute + + expect(project.legacy_storage?).to be_truthy + end + end + context 'when succeeds' do it 'renames project and wiki repositories' do service.execute -- cgit v1.2.1 From 264394f6d39c57e6863742ae0bf7c300156185ad Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 1 Mar 2019 07:57:11 +0100 Subject: Extract and simplify more code into BaseRepositoryService` `try_to_set_repository_read_only!` is now on the Base class. Simplified Exception classes from 2 to 1 with a more descriptive name. --- .../services/projects/hashed_storage/migrate_repository_service_spec.rb | 2 +- .../projects/hashed_storage/rollback_repository_service_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index cd56337420b..e77e2198439 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -28,7 +28,7 @@ describe Projects::HashedStorage::MigrateRepositoryService do it 'fails when a git operation is in progress' do allow(project).to receive(:repo_reference_count) { 1 } - expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryMigrationError) + expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError) end end diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb index daac220f710..41927934501 100644 --- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb @@ -30,7 +30,7 @@ describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis it 'fails when a git operation is in progress' do allow(project).to receive(:repo_reference_count) { 1 } - expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryRollbackError) + expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError) end end -- cgit v1.2.1 From b91a005162515cfe398d90772ed66ecd92e17531 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 1 Mar 2019 07:58:05 +0100 Subject: Extract duplicated code into BaseAttachmentService Exceptions were also simplified from 2 to 1. --- .../projects/hashed_storage/migrate_attachments_service_spec.rb | 4 ++-- .../projects/hashed_storage/rollback_attachments_service_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index d51988c6f42..639dd930618 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -70,10 +70,10 @@ describe Projects::HashedStorage::MigrateAttachmentsService do FileUtils.mkdir_p(base_path(hashed_storage)) end - it 'raises AttachmentMigrationError' do + it 'raises AttachmentCannotMoveError' do expect(FileUtils).not_to receive(:mv).with(base_path(legacy_storage), base_path(hashed_storage)) - expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentMigrationError) + expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError) end end end diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb index 9e18428f412..6f4154d6011 100644 --- a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb @@ -72,10 +72,10 @@ describe Projects::HashedStorage::RollbackAttachmentsService do FileUtils.mkdir_p(base_path(legacy_storage)) end - it 'raises AttachmentRollbackError' do + it 'raises AttachmentCannotMoveError' do expect(FileUtils).not_to receive(:mv).with(base_path(legacy_storage), base_path(hashed_storage)) - expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentRollbackError) + expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError) end end end -- cgit v1.2.1 From 43e713eb41117138c13ee4b9279321ca4331a302 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Fri, 1 Mar 2019 14:51:54 +0000 Subject: Refactor model and spec - Move some specs into contexts - Let get_slugs method take a parameter and return a specific slug. - Add rescues when using Addressable::URI. --- .../settings/operations_controller_spec.rb | 63 ++++--- .../projects/settings/operations_settings_spec.rb | 77 +++++++++ spec/helpers/projects_helper_spec.rb | 50 ++++++ .../error_tracking_settings/components/app_spec.js | 63 +++++++ .../components/error_tracking_form_spec.js | 91 ++++++++++ .../components/project_dropdown_spec.js | 109 ++++++++++++ spec/javascripts/error_tracking_settings/mock.js | 92 ++++++++++ .../error_tracking_settings/store/actions_spec.js | 191 +++++++++++++++++++++ .../error_tracking_settings/store/getters_spec.js | 93 ++++++++++ .../error_tracking_settings/store/mutation_spec.js | 82 +++++++++ .../error_tracking_settings/utils_spec.js | 29 ++++ .../project_error_tracking_setting_spec.rb | 35 +++- .../error_tracking/list_projects_service_spec.rb | 4 +- .../projects/operations/update_service_spec.rb | 50 +++++- .../settings/operations/show.html.haml_spec.rb | 1 - 15 files changed, 996 insertions(+), 34 deletions(-) create mode 100644 spec/javascripts/error_tracking_settings/components/app_spec.js create mode 100644 spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js create mode 100644 spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js create mode 100644 spec/javascripts/error_tracking_settings/mock.js create mode 100644 spec/javascripts/error_tracking_settings/store/actions_spec.js create mode 100644 spec/javascripts/error_tracking_settings/store/getters_spec.js create mode 100644 spec/javascripts/error_tracking_settings/store/mutation_spec.js create mode 100644 spec/javascripts/error_tracking_settings/utils_spec.js (limited to 'spec') diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb index d989ec22481..02a392f23c2 100644 --- a/spec/controllers/projects/settings/operations_controller_spec.rb +++ b/spec/controllers/projects/settings/operations_controller_spec.rb @@ -74,38 +74,55 @@ describe Projects::Settings::OperationsController do { error_tracking_setting_attributes: { enabled: '1', - api_url: 'http://url', - token: 'token' + api_host: 'http://url', + token: 'token', + project: { + slug: 'sentry-project', + name: 'Sentry Project', + organization_slug: 'sentry-org', + organization_name: 'Sentry Org' + } } } end + let(:error_tracking_permitted) do ActionController::Parameters.new(error_tracking_params).permit! end - context 'when update succeeds' do - before do - stub_operations_update_service_returning(status: :success) - end - - it 'shows a notice' do - patch :update, params: project_params(project, error_tracking_params) - - expect(response).to redirect_to(operations_url) - expect(flash[:notice]).to eq _('Your changes have been saved') - end - end - - context 'when update fails' do - before do - stub_operations_update_service_returning(status: :error) + context 'format json' do + context 'when update succeeds' do + before do + stub_operations_update_service_returning(status: :success) + end + + it 'returns success status' do + patch :update, + params: project_params(project, error_tracking_params), + format: :json + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq('status' => 'success') + expect(flash[:notice]).to eq('Your changes have been saved') + end end - it 'renders show page' do - patch :update, params: project_params(project, error_tracking_params) - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template(:show) + context 'when update fails' do + before do + stub_operations_update_service_returning( + status: :error, + message: 'error message' + ) + end + + it 'returns error' do + patch :update, + params: project_params(project, error_tracking_params), + format: :json + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).not_to be_nil + end end end diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb index 06290c67c70..af56cb0d4ee 100644 --- a/spec/features/projects/settings/operations_settings_spec.rb +++ b/spec/features/projects/settings/operations_settings_spec.rb @@ -20,4 +20,81 @@ describe 'Projects > Settings > For a forked project', :js do expect(page).to have_selector('a[title="Operations"]', visible: false) end end + + describe 'Settings > Operations' do + context 'error tracking settings form' do + let(:sentry_list_projects_url) { 'http://sentry.example.com/api/0/projects/' } + + context 'success path' do + let(:projects_sample_response) do + Gitlab::Utils.deep_indifferent_access( + JSON.parse(fixture_file('sentry/list_projects_sample_response.json')) + ) + end + + before do + WebMock.stub_request(:get, sentry_list_projects_url) + .to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: projects_sample_response.to_json + ) + end + + it 'successfully fills and submits the form' do + visit project_settings_operations_path(project) + + wait_for_requests + + expect(page).to have_content('Sentry API URL') + expect(page.body).to include('Error Tracking') + expect(page).to have_button('Connect') + + check('Active') + fill_in('error-tracking-api-host', with: 'http://sentry.example.com') + fill_in('error-tracking-token', with: 'token') + + click_button('Connect') + + within('div#project-dropdown') do + click_button('Select project') + click_button('Sentry | Internal') + end + + click_button('Save changes') + + wait_for_requests + + assert_text('Your changes have been saved') + end + end + + context 'project dropdown fails to load' do + before do + WebMock.stub_request(:get, sentry_list_projects_url) + .to_return( + status: 400, + headers: { 'Content-Type' => 'application/json' }, + body: { + message: 'Sentry response code: 401' + }.to_json + ) + end + + it 'displays error message' do + visit project_settings_operations_path(project) + + wait_for_requests + + check('Active') + fill_in('error-tracking-api-host', with: 'http://sentry.example.com') + fill_in('error-tracking-token', with: 'token') + + click_button('Connect') + + assert_text('Connection has failed. Re-check Auth Token and try again.') + end + end + end + end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 49895b0680b..291eafece94 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -3,6 +3,56 @@ require 'spec_helper' describe ProjectsHelper do include ProjectForksHelper + describe '#error_tracking_setting_project_json' do + let(:project) { create(:project) } + + context 'error tracking setting does not exist' do + before do + helper.instance_variable_set(:@project, project) + end + + it 'returns nil' do + expect(helper.error_tracking_setting_project_json).to be_nil + end + end + + context 'error tracking setting exists' do + let!(:error_tracking_setting) { create(:project_error_tracking_setting, project: project) } + + context 'api_url present' do + let(:json) do + { + name: error_tracking_setting.project_name, + organization_name: error_tracking_setting.organization_name, + organization_slug: error_tracking_setting.organization_slug, + slug: error_tracking_setting.project_slug + }.to_json + end + + before do + helper.instance_variable_set(:@project, project) + end + + it 'returns error tracking json' do + expect(helper.error_tracking_setting_project_json).to eq(json) + end + end + + context 'api_url not present' do + before do + project.error_tracking_setting.api_url = nil + project.error_tracking_setting.enabled = false + + helper.instance_variable_set(:@project, project) + end + + it 'returns nil' do + expect(helper.error_tracking_setting_project_json).to be_nil + end + end + end + end + describe "#project_status_css_class" do it "returns appropriate class" do expect(project_status_css_class("started")).to eq("table-active") diff --git a/spec/javascripts/error_tracking_settings/components/app_spec.js b/spec/javascripts/error_tracking_settings/components/app_spec.js new file mode 100644 index 00000000000..2e52a45fd34 --- /dev/null +++ b/spec/javascripts/error_tracking_settings/components/app_spec.js @@ -0,0 +1,63 @@ +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue'; +import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue'; +import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue'; +import createStore from '~/error_tracking_settings/store'; +import { TEST_HOST } from 'spec/test_constants'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('error tracking settings app', () => { + let store; + let wrapper; + + function mountComponent() { + wrapper = shallowMount(ErrorTrackingSettings, { + localVue, + store, // Override the imported store + propsData: { + initialEnabled: 'true', + initialApiHost: TEST_HOST, + initialToken: 'someToken', + initialProject: null, + listProjectsEndpoint: TEST_HOST, + operationsSettingsEndpoint: TEST_HOST, + }, + }); + } + + beforeEach(() => { + store = createStore(); + + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('section', () => { + it('renders the form and dropdown', () => { + expect(wrapper.find(ErrorTrackingForm).exists()).toBeTruthy(); + expect(wrapper.find(ProjectDropdown).exists()).toBeTruthy(); + }); + + it('renders the Save Changes button', () => { + expect(wrapper.find('.js-error-tracking-button').exists()).toBeTruthy(); + }); + + it('enables the button by default', () => { + expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeFalsy(); + }); + + it('disables the button when saving', () => { + store.state.settingsLoading = true; + + expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeTruthy(); + }); + }); +}); diff --git a/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js b/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js new file mode 100644 index 00000000000..23e57c4bbf1 --- /dev/null +++ b/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js @@ -0,0 +1,91 @@ +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlButton, GlFormInput } from '@gitlab/ui'; +import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue'; +import { defaultProps } from '../mock'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('error tracking settings form', () => { + let wrapper; + + function mountComponent() { + wrapper = shallowMount(ErrorTrackingForm, { + localVue, + propsData: defaultProps, + }); + } + + beforeEach(() => { + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('an empty form', () => { + it('is rendered', () => { + expect(wrapper.findAll(GlFormInput).length).toBe(2); + expect(wrapper.find(GlFormInput).attributes('id')).toBe('error-tracking-api-host'); + expect( + wrapper + .findAll(GlFormInput) + .at(1) + .attributes('id'), + ).toBe('error-tracking-token'); + + expect(wrapper.findAll(GlButton).exists()).toBe(true); + }); + + it('is rendered with labels and placeholders', () => { + const pageText = wrapper.text(); + + expect(pageText).toContain('Find your hostname in your Sentry account settings page'); + expect(pageText).toContain( + "After adding your Auth Token, use the 'Connect' button to load projects", + ); + + expect(pageText).not.toContain('Connection has failed. Re-check Auth Token and try again'); + expect( + wrapper + .findAll(GlFormInput) + .at(0) + .attributes('placeholder'), + ).toContain('https://mysentryserver.com'); + }); + }); + + describe('after a successful connection', () => { + beforeEach(() => { + wrapper.setProps({ connectSuccessful: true }); + }); + + it('shows the success checkmark', () => { + expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(true); + }); + + it('does not show an error', () => { + expect(wrapper.text()).not.toContain( + 'Connection has failed. Re-check Auth Token and try again', + ); + }); + }); + + describe('after an unsuccessful connection', () => { + beforeEach(() => { + wrapper.setProps({ connectError: true }); + }); + + it('does not show the check mark', () => { + expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(false); + }); + + it('shows an error', () => { + expect(wrapper.text()).toContain('Connection has failed. Re-check Auth Token and try again'); + }); + }); +}); diff --git a/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js b/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js new file mode 100644 index 00000000000..8e5dbe28452 --- /dev/null +++ b/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js @@ -0,0 +1,109 @@ +import _ from 'underscore'; +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue'; +import { defaultProps, projectList, staleProject } from '../mock'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('error tracking settings project dropdown', () => { + let wrapper; + + function mountComponent() { + wrapper = shallowMount(ProjectDropdown, { + localVue, + propsData: { + ..._.pick( + defaultProps, + 'dropdownLabel', + 'invalidProjectLabel', + 'projects', + 'projectSelectionLabel', + 'selectedProject', + 'token', + ), + hasProjects: false, + isProjectInvalid: false, + }, + }); + } + + beforeEach(() => { + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('empty project list', () => { + it('renders the dropdown', () => { + expect(wrapper.find('#project-dropdown').exists()).toBeTruthy(); + expect(wrapper.find(GlDropdown).exists()).toBeTruthy(); + }); + + it('shows helper text', () => { + expect(wrapper.find('.js-project-dropdown-label').exists()).toBeTruthy(); + expect(wrapper.find('.js-project-dropdown-label').text()).toContain( + 'To enable project selection', + ); + }); + + it('does not show an error', () => { + expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy(); + }); + + it('does not contain any dropdown items', () => { + expect(wrapper.find(GlDropdownItem).exists()).toBeFalsy(); + expect(wrapper.find(GlDropdown).props('text')).toBe('No projects available'); + }); + }); + + describe('populated project list', () => { + beforeEach(() => { + wrapper.setProps({ projects: _.clone(projectList), hasProjects: true }); + }); + + it('renders the dropdown', () => { + expect(wrapper.find('#project-dropdown').exists()).toBeTruthy(); + expect(wrapper.find(GlDropdown).exists()).toBeTruthy(); + }); + + it('contains a number of dropdown items', () => { + expect(wrapper.find(GlDropdownItem).exists()).toBeTruthy(); + expect(wrapper.findAll(GlDropdownItem).length).toBe(2); + }); + }); + + describe('selected project', () => { + const selectedProject = _.clone(projectList[0]); + + beforeEach(() => { + wrapper.setProps({ projects: _.clone(projectList), selectedProject, hasProjects: true }); + }); + + it('does not show helper text', () => { + expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy(); + expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy(); + }); + }); + + describe('invalid project selected', () => { + beforeEach(() => { + wrapper.setProps({ + projects: _.clone(projectList), + selectedProject: staleProject, + isProjectInvalid: true, + }); + }); + + it('displays a error', () => { + expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy(); + expect(wrapper.find('.js-project-dropdown-error').exists()).toBeTruthy(); + }); + }); +}); diff --git a/spec/javascripts/error_tracking_settings/mock.js b/spec/javascripts/error_tracking_settings/mock.js new file mode 100644 index 00000000000..32cdba33c14 --- /dev/null +++ b/spec/javascripts/error_tracking_settings/mock.js @@ -0,0 +1,92 @@ +import createStore from '~/error_tracking_settings/store'; +import { TEST_HOST } from 'spec/test_constants'; + +const defaultStore = createStore(); + +export const projectList = [ + { + name: 'name', + slug: 'slug', + organizationName: 'organizationName', + organizationSlug: 'organizationSlug', + }, + { + name: 'name2', + slug: 'slug2', + organizationName: 'organizationName2', + organizationSlug: 'organizationSlug2', + }, +]; + +export const staleProject = { + name: 'staleName', + slug: 'staleSlug', + organizationName: 'staleOrganizationName', + organizationSlug: 'staleOrganizationSlug', +}; + +export const normalizedProject = { + name: 'name', + slug: 'slug', + organizationName: 'organization_name', + organizationSlug: 'organization_slug', +}; + +export const sampleBackendProject = { + name: normalizedProject.name, + slug: normalizedProject.slug, + organization_name: normalizedProject.organizationName, + organization_slug: normalizedProject.organizationSlug, +}; + +export const sampleFrontendSettings = { + apiHost: 'apiHost', + enabled: false, + token: 'token', + selectedProject: { + slug: normalizedProject.slug, + name: normalizedProject.name, + organizationName: normalizedProject.organizationName, + organizationSlug: normalizedProject.organizationSlug, + }, +}; + +export const transformedSettings = { + api_host: 'apiHost', + enabled: false, + token: 'token', + project: { + slug: normalizedProject.slug, + name: normalizedProject.name, + organization_name: normalizedProject.organizationName, + organization_slug: normalizedProject.organizationSlug, + }, +}; + +export const defaultProps = { + ...defaultStore.state, + ...defaultStore.getters, +}; + +export const initialEmptyState = { + apiHost: '', + enabled: false, + project: null, + token: '', + listProjectsEndpoint: TEST_HOST, + operationsSettingsEndpoint: TEST_HOST, +}; + +export const initialPopulatedState = { + apiHost: 'apiHost', + enabled: true, + project: JSON.stringify(projectList[0]), + token: 'token', + listProjectsEndpoint: TEST_HOST, + operationsSettingsEndpoint: TEST_HOST, +}; + +export const projectWithHtmlTemplate = { + ...projectList[0], + name: 'bold', +}; diff --git a/spec/javascripts/error_tracking_settings/store/actions_spec.js b/spec/javascripts/error_tracking_settings/store/actions_spec.js new file mode 100644 index 00000000000..0255b3a7aa4 --- /dev/null +++ b/spec/javascripts/error_tracking_settings/store/actions_spec.js @@ -0,0 +1,191 @@ +import MockAdapter from 'axios-mock-adapter'; +import testAction from 'spec/helpers/vuex_action_helper'; +import { TEST_HOST } from 'spec/test_constants'; +import axios from '~/lib/utils/axios_utils'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import actionsDefaultExport, * as actions from '~/error_tracking_settings/store/actions'; +import * as types from '~/error_tracking_settings/store/mutation_types'; +import defaultState from '~/error_tracking_settings/store/state'; +import { projectList } from '../mock'; + +describe('error tracking settings actions', () => { + let state; + + describe('project list actions', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + state = { ...defaultState(), listProjectsEndpoint: TEST_HOST }; + }); + + afterEach(() => { + mock.restore(); + }); + + it('should request and transform the project list', done => { + mock.onPost(TEST_HOST).reply(() => [200, { projects: projectList }]); + testAction( + actions.fetchProjects, + null, + state, + [], + [ + { type: 'requestProjects' }, + { + type: 'receiveProjectsSuccess', + payload: projectList.map(convertObjectPropsToCamelCase), + }, + ], + () => { + expect(mock.history.post.length).toBe(1); + done(); + }, + ); + }); + + it('should handle a server error', done => { + mock.onPost(`${TEST_HOST}.json`).reply(() => [400]); + testAction( + actions.fetchProjects, + null, + state, + [], + [ + { type: 'requestProjects' }, + { + type: 'receiveProjectsError', + }, + ], + () => { + expect(mock.history.post.length).toBe(1); + done(); + }, + ); + }); + + it('should request projects correctly', done => { + testAction(actions.requestProjects, null, state, [{ type: types.RESET_CONNECT }], [], done); + }); + + it('should receive projects correctly', done => { + const testPayload = []; + testAction( + actions.receiveProjectsSuccess, + testPayload, + state, + [ + { type: types.UPDATE_CONNECT_SUCCESS }, + { type: types.RECEIVE_PROJECTS, payload: testPayload }, + ], + [], + done, + ); + }); + + it('should handle errors when receiving projects', done => { + const testPayload = []; + testAction( + actions.receiveProjectsError, + testPayload, + state, + [{ type: types.UPDATE_CONNECT_ERROR }, { type: types.CLEAR_PROJECTS }], + [], + done, + ); + }); + }); + + describe('save changes actions', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + state = { + operationsSettingsEndpoint: TEST_HOST, + }; + }); + + afterEach(() => { + mock.restore(); + }); + + it('should save the page', done => { + const refreshCurrentPage = spyOnDependency(actionsDefaultExport, 'refreshCurrentPage'); + mock.onPatch(TEST_HOST).reply(200); + testAction(actions.updateSettings, null, state, [], [{ type: 'requestSettings' }], () => { + expect(mock.history.patch.length).toBe(1); + expect(refreshCurrentPage).toHaveBeenCalled(); + done(); + }); + }); + + it('should handle a server error', done => { + mock.onPatch(TEST_HOST).reply(400); + testAction( + actions.updateSettings, + null, + state, + [], + [ + { type: 'requestSettings' }, + { + type: 'receiveSettingsError', + payload: new Error('Request failed with status code 400'), + }, + ], + () => { + expect(mock.history.patch.length).toBe(1); + done(); + }, + ); + }); + + it('should request to save the page', done => { + testAction( + actions.requestSettings, + null, + state, + [{ type: types.UPDATE_SETTINGS_LOADING, payload: true }], + [], + done, + ); + }); + + it('should handle errors when requesting to save the page', done => { + testAction( + actions.receiveSettingsError, + {}, + state, + [{ type: types.UPDATE_SETTINGS_LOADING, payload: false }], + [], + done, + ); + }); + }); + + describe('generic actions to update the store', () => { + const testData = 'test'; + it('should reset the `connect success` flag when updating the api host', done => { + testAction( + actions.updateApiHost, + testData, + state, + [{ type: types.UPDATE_API_HOST, payload: testData }, { type: types.RESET_CONNECT }], + [], + done, + ); + }); + + it('should reset the `connect success` flag when updating the token', done => { + testAction( + actions.updateToken, + testData, + state, + [{ type: types.UPDATE_TOKEN, payload: testData }, { type: types.RESET_CONNECT }], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/error_tracking_settings/store/getters_spec.js b/spec/javascripts/error_tracking_settings/store/getters_spec.js new file mode 100644 index 00000000000..2c5ff084b8a --- /dev/null +++ b/spec/javascripts/error_tracking_settings/store/getters_spec.js @@ -0,0 +1,93 @@ +import * as getters from '~/error_tracking_settings/store/getters'; +import defaultState from '~/error_tracking_settings/store/state'; +import { projectList, projectWithHtmlTemplate, staleProject } from '../mock'; + +describe('Error Tracking Settings - Getters', () => { + let state; + + beforeEach(() => { + state = defaultState(); + }); + + describe('hasProjects', () => { + it('should reflect when no projects exist', () => { + expect(getters.hasProjects(state)).toEqual(false); + }); + + it('should reflect when projects exist', () => { + state.projects = projectList; + + expect(getters.hasProjects(state)).toEqual(true); + }); + }); + + describe('isProjectInvalid', () => { + const mockGetters = { hasProjects: true }; + it('should show when a project is valid', () => { + state.projects = projectList; + [state.selectedProject] = projectList; + + expect(getters.isProjectInvalid(state, mockGetters)).toEqual(false); + }); + + it('should show when a project is invalid', () => { + state.projects = projectList; + state.selectedProject = staleProject; + + expect(getters.isProjectInvalid(state, mockGetters)).toEqual(true); + }); + }); + + describe('dropdownLabel', () => { + const mockGetters = { hasProjects: false }; + it('should display correctly when there are no projects available', () => { + expect(getters.dropdownLabel(state, mockGetters)).toEqual('No projects available'); + }); + + it('should display correctly when a project is selected', () => { + [state.selectedProject] = projectList; + + expect(getters.dropdownLabel(state, mockGetters)).toEqual('organizationName | name'); + }); + + it('should display correctly when no project is selected', () => { + state.projects = projectList; + + expect(getters.dropdownLabel(state, { hasProjects: true })).toEqual('Select project'); + }); + }); + + describe('invalidProjectLabel', () => { + it('should display an error containing the project name', () => { + [state.selectedProject] = projectList; + + expect(getters.invalidProjectLabel(state)).toEqual( + 'Project "name" is no longer available. Select another project to continue.', + ); + }); + + it('should properly escape the label text', () => { + state.selectedProject = projectWithHtmlTemplate; + + expect(getters.invalidProjectLabel(state)).toEqual( + 'Project "<strong>bold</strong>" is no longer available. Select another project to continue.', + ); + }); + }); + + describe('projectSelectionLabel', () => { + it('should show the correct message when the token is empty', () => { + expect(getters.projectSelectionLabel(state)).toEqual( + 'To enable project selection, enter a valid Auth Token', + ); + }); + + it('should show the correct message when token exists', () => { + state.token = 'test-token'; + + expect(getters.projectSelectionLabel(state)).toEqual( + "Click 'Connect' to re-establish the connection to Sentry and activate the dropdown.", + ); + }); + }); +}); diff --git a/spec/javascripts/error_tracking_settings/store/mutation_spec.js b/spec/javascripts/error_tracking_settings/store/mutation_spec.js new file mode 100644 index 00000000000..bb1f1da784e --- /dev/null +++ b/spec/javascripts/error_tracking_settings/store/mutation_spec.js @@ -0,0 +1,82 @@ +import { TEST_HOST } from 'spec/test_constants'; +import mutations from '~/error_tracking_settings/store/mutations'; +import defaultState from '~/error_tracking_settings/store/state'; +import * as types from '~/error_tracking_settings/store/mutation_types'; +import { + initialEmptyState, + initialPopulatedState, + projectList, + sampleBackendProject, + normalizedProject, +} from '../mock'; + +describe('error tracking settings mutations', () => { + describe('mutations', () => { + let state; + + beforeEach(() => { + state = defaultState(); + }); + + it('should create an empty initial state correctly', () => { + mutations[types.SET_INITIAL_STATE](state, { + ...initialEmptyState, + }); + + expect(state.apiHost).toEqual(''); + expect(state.enabled).toEqual(false); + expect(state.selectedProject).toEqual(null); + expect(state.token).toEqual(''); + expect(state.listProjectsEndpoint).toEqual(TEST_HOST); + expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST); + }); + + it('should populate the initial state correctly', () => { + mutations[types.SET_INITIAL_STATE](state, { + ...initialPopulatedState, + }); + + expect(state.apiHost).toEqual('apiHost'); + expect(state.enabled).toEqual(true); + expect(state.selectedProject).toEqual(projectList[0]); + expect(state.token).toEqual('token'); + expect(state.listProjectsEndpoint).toEqual(TEST_HOST); + expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST); + }); + + it('should receive projects successfully', () => { + mutations[types.RECEIVE_PROJECTS](state, [sampleBackendProject]); + + expect(state.projects).toEqual([normalizedProject]); + }); + + it('should strip out unnecessary project properties', () => { + mutations[types.RECEIVE_PROJECTS](state, [ + { ...sampleBackendProject, extra_property: 'extra_property' }, + ]); + + expect(state.projects).toEqual([normalizedProject]); + }); + + it('should update state when connect is successful', () => { + mutations[types.UPDATE_CONNECT_SUCCESS](state); + + expect(state.connectSuccessful).toBe(true); + expect(state.connectError).toBe(false); + }); + + it('should update state when connect fails', () => { + mutations[types.UPDATE_CONNECT_ERROR](state); + + expect(state.connectSuccessful).toBe(false); + expect(state.connectError).toBe(true); + }); + + it('should update state when connect is reset', () => { + mutations[types.RESET_CONNECT](state); + + expect(state.connectSuccessful).toBe(false); + expect(state.connectError).toBe(false); + }); + }); +}); diff --git a/spec/javascripts/error_tracking_settings/utils_spec.js b/spec/javascripts/error_tracking_settings/utils_spec.js new file mode 100644 index 00000000000..4b144f7daf1 --- /dev/null +++ b/spec/javascripts/error_tracking_settings/utils_spec.js @@ -0,0 +1,29 @@ +import { transformFrontendSettings } from '~/error_tracking_settings/utils'; +import { sampleFrontendSettings, transformedSettings } from './mock'; + +describe('error tracking settings utils', () => { + describe('data transform functions', () => { + it('should transform settings successfully for the backend', () => { + expect(transformFrontendSettings(sampleFrontendSettings)).toEqual(transformedSettings); + }); + + it('should transform empty values in the settings object to null', () => { + const emptyFrontendSettingsObject = { + apiHost: '', + enabled: false, + token: '', + selectedProject: null, + }; + const transformedEmptySettingsObject = { + api_host: null, + enabled: false, + token: null, + project: null, + }; + + expect(transformFrontendSettings(emptyFrontendSettingsObject)).toEqual( + transformedEmptySettingsObject, + ); + }); + }); +}); diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index 076ccc96041..cbde13a2c7a 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -62,11 +62,32 @@ describe ErrorTracking::ProjectErrorTrackingSetting do end context 'URL path' do - it 'fails validation with wrong path' do + it 'fails validation without api/0/projects' do subject.api_url = 'http://gitlab.com/project1/something' expect(subject).not_to be_valid - expect(subject.errors.messages[:api_url]).to include('path needs to start with /api/0/projects') + expect(subject.errors.messages[:api_url]).to include('is invalid') + end + + it 'fails validation without org and project slugs' do + subject.api_url = 'http://gitlab.com/api/0/projects/' + + expect(subject).not_to be_valid + expect(subject.errors.messages[:project]).to include('is a required field') + end + + it 'fails validation when api_url has extra parts' do + subject.api_url = 'http://gitlab.com/api/0/projects/org/proj/something' + + expect(subject).not_to be_valid + expect(subject.errors.messages[:api_url]).to include("is invalid") + end + + it 'fails validation when api_url has less parts' do + subject.api_url = 'http://gitlab.com/api/0/projects/org' + + expect(subject).not_to be_valid + expect(subject.errors.messages[:api_url]).to include("is invalid") end it 'passes validation with correct path' do @@ -275,6 +296,16 @@ describe ErrorTracking::ProjectErrorTrackingSetting do expect(api_url).to eq(':::') end + + it 'returns nil when api_host is blank' do + api_url = described_class.build_api_url_from( + api_host: '', + organization_slug: 'org-slug', + project_slug: 'proj-slug' + ) + + expect(api_url).to be_nil + end end describe '#api_host' do diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb index 9f25a633deb..a92d3376f7b 100644 --- a/spec/services/error_tracking/list_projects_service_spec.rb +++ b/spec/services/error_tracking/list_projects_service_spec.rb @@ -32,7 +32,7 @@ describe ErrorTracking::ListProjectsService do end context 'set model attributes to new values' do - let(:new_api_url) { new_api_host + 'api/0/projects/' } + let(:new_api_url) { new_api_host + 'api/0/projects/org/proj/' } before do expect(error_tracking_setting).to receive(:list_sentry_projects) @@ -121,7 +121,7 @@ describe ErrorTracking::ListProjectsService do context 'error_tracking_setting is nil' do let(:error_tracking_setting) { build(:project_error_tracking_setting) } - let(:new_api_url) { new_api_host + 'api/0/projects/' } + let(:new_api_url) { new_api_host + 'api/0/projects/org/proj/' } before do expect(project).to receive(:build_error_tracking_setting).once diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb index 6afae3da80c..86b1ec83f50 100644 --- a/spec/services/projects/operations/update_service_spec.rb +++ b/spec/services/projects/operations/update_service_spec.rb @@ -17,8 +17,14 @@ describe Projects::Operations::UpdateService do { error_tracking_setting_attributes: { enabled: false, - api_url: 'http://gitlab.com/api/0/projects/org/project', - token: 'token' + api_host: 'http://gitlab.com/', + token: 'token', + project: { + slug: 'project', + name: 'Project', + organization_slug: 'org', + organization_name: 'Org' + } } } end @@ -32,8 +38,30 @@ describe Projects::Operations::UpdateService do project.reload expect(project.error_tracking_setting).not_to be_enabled - expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project') + expect(project.error_tracking_setting.api_url).to eq( + 'http://gitlab.com/api/0/projects/org/project/' + ) expect(project.error_tracking_setting.token).to eq('token') + expect(project.error_tracking_setting[:project_name]).to eq('Project') + expect(project.error_tracking_setting[:organization_name]).to eq('Org') + end + + context 'disable error tracking' do + before do + params[:error_tracking_setting_attributes][:api_host] = '' + params[:error_tracking_setting_attributes][:enabled] = false + end + + it 'can set api_url to nil' do + expect(result[:status]).to eq(:success) + + project.reload + expect(project.error_tracking_setting).not_to be_enabled + expect(project.error_tracking_setting.api_url).to be_nil + expect(project.error_tracking_setting.token).to eq('token') + expect(project.error_tracking_setting[:project_name]).to eq('Project') + expect(project.error_tracking_setting[:organization_name]).to eq('Org') + end end end @@ -42,8 +70,14 @@ describe Projects::Operations::UpdateService do { error_tracking_setting_attributes: { enabled: true, - api_url: 'http://gitlab.com/api/0/projects/org/project', - token: 'token' + api_host: 'http://gitlab.com/', + token: 'token', + project: { + slug: 'project', + name: 'Project', + organization_slug: 'org', + organization_name: 'Org' + } } } end @@ -52,8 +86,12 @@ describe Projects::Operations::UpdateService do expect(result[:status]).to eq(:success) expect(project.error_tracking_setting).to be_enabled - expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project') + expect(project.error_tracking_setting.api_url).to eq( + 'http://gitlab.com/api/0/projects/org/project/' + ) expect(project.error_tracking_setting.token).to eq('token') + expect(project.error_tracking_setting[:project_name]).to eq('Project') + expect(project.error_tracking_setting[:organization_name]).to eq('Org') end end diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb index 8e34521c7c8..1bca8bba940 100644 --- a/spec/views/projects/settings/operations/show.html.haml_spec.rb +++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb @@ -30,7 +30,6 @@ describe 'projects/settings/operations/show' do expect(rendered).to have_content _('Error Tracking') expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token') - expect(rendered).to have_content _('Active') end end end -- cgit v1.2.1 From c78861bc4268049777bd279f5de5981ec4e78019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 14 Jan 2019 11:55:51 +0100 Subject: Allow to recursively expand includes This change introduces a support for nesting the includes, allowing to evaluate them in context of the target, by properly respecting the relative inclusions and user permissions of another projects, or templates. --- .../gitlab/ci/config/external/file/base_spec.rb | 18 ++++- .../gitlab/ci/config/external/file/local_spec.rb | 36 +++++++++- .../gitlab/ci/config/external/file/project_spec.rb | 47 ++++++++----- .../gitlab/ci/config/external/file/remote_spec.rb | 12 +++- .../ci/config/external/file/template_spec.rb | 35 ++++++--- spec/lib/gitlab/ci/config/external/mapper_spec.rb | 35 ++++++++- .../gitlab/ci/config/external/processor_spec.rb | 82 +++++++++++++++++++++- 7 files changed, 231 insertions(+), 34 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb index 1a6b3587599..fa39b32d7ab 100644 --- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -3,7 +3,7 @@ require 'fast_spec_helper' describe Gitlab::Ci::Config::External::File::Base do - let(:context) { described_class::Context.new(nil, 'HEAD', nil) } + let(:context) { described_class::Context.new(nil, 'HEAD', nil, Set.new) } let(:test_class) do Class.new(described_class) do @@ -79,4 +79,20 @@ describe Gitlab::Ci::Config::External::File::Base do end end end + + describe '#to_hash' do + context 'with includes' do + let(:location) { 'some/file/config.yml' } + let(:content) { 'include: { template: Bash.gitlab-ci.yml }'} + + before do + allow_any_instance_of(test_class) + .to receive(:content).and_return(content) + end + + it 'does expand hash to include the template' do + expect(subject.to_hash).to include(:before_script) + end + end + end end diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index ff67a765da0..dc14b07287e 100644 --- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -4,8 +4,10 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::File::Local do set(:project) { create(:project, :repository) } + set(:user) { create(:user) } - let(:context) { described_class::Context.new(project, '12345', nil) } + let(:sha) { '12345' } + let(:context) { described_class::Context.new(project, sha, user, Set.new) } let(:params) { { local: location } } let(:local_file) { described_class.new(params, context) } @@ -103,4 +105,36 @@ describe Gitlab::Ci::Config::External::File::Local do expect(local_file.error_message).to eq("Local file `#{location}` does not exist!") end end + + describe '#expand_context' do + let(:location) { 'location.yml' } + + subject { local_file.send(:expand_context) } + + it 'inherits project, user and sha' do + is_expected.to include(user: user, project: project, sha: sha) + end + end + + describe '#to_hash' do + context 'properly includes another local file in the same repository' do + let(:location) { 'some/file/config.yml' } + let(:content) { 'include: { local: another-config.yml }'} + + let(:another_location) { 'another-config.yml' } + let(:another_content) { 'rspec: JOB' } + + before do + allow(project.repository).to receive(:blob_data_at).with(sha, location) + .and_return(content) + + allow(project.repository).to receive(:blob_data_at).with(sha, another_location) + .and_return(another_content) + end + + it 'does expand hash to include the template' do + expect(local_file.to_hash).to include(:rspec) + end + end + end end diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index 11809adcaf6..6e89bb1b30f 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -3,12 +3,13 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::File::Project do + set(:context_project) { create(:project) } set(:project) { create(:project, :repository) } set(:user) { create(:user) } let(:context_user) { user } - let(:context) { described_class::Context.new(nil, '12345', context_user) } - let(:subject) { described_class.new(params, context) } + let(:context) { described_class::Context.new(context_project, '12345', context_user, Set.new) } + let(:project_file) { described_class.new(params, context) } before do project.add_developer(user) @@ -19,7 +20,7 @@ describe Gitlab::Ci::Config::External::File::Project do let(:params) { { file: 'file.yml', project: 'project' } } it 'should return true' do - expect(subject).to be_matching + expect(project_file).to be_matching end end @@ -27,7 +28,7 @@ describe Gitlab::Ci::Config::External::File::Project do let(:params) { { file: 'file.yml' } } it 'should return false' do - expect(subject).not_to be_matching + expect(project_file).not_to be_matching end end @@ -35,7 +36,7 @@ describe Gitlab::Ci::Config::External::File::Project do let(:params) { { project: 'project' } } it 'should return false' do - expect(subject).not_to be_matching + expect(project_file).not_to be_matching end end @@ -43,7 +44,7 @@ describe Gitlab::Ci::Config::External::File::Project do let(:params) { {} } it 'should return false' do - expect(subject).not_to be_matching + expect(project_file).not_to be_matching end end end @@ -61,15 +62,15 @@ describe Gitlab::Ci::Config::External::File::Project do end it 'should return true' do - expect(subject).to be_valid + expect(project_file).to be_valid end context 'when user does not have permission to access file' do let(:context_user) { create(:user) } it 'should return false' do - expect(subject).not_to be_valid - expect(subject.error_message).to include("Project `#{project.full_path}` not found or access denied!") + expect(project_file).not_to be_valid + expect(project_file.error_message).to include("Project `#{project.full_path}` not found or access denied!") end end end @@ -86,7 +87,7 @@ describe Gitlab::Ci::Config::External::File::Project do end it 'should return true' do - expect(subject).to be_valid + expect(project_file).to be_valid end end @@ -102,8 +103,8 @@ describe Gitlab::Ci::Config::External::File::Project do end it 'should return false' do - expect(subject).not_to be_valid - expect(subject.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!") + expect(project_file).not_to be_valid + expect(project_file.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!") end end @@ -113,8 +114,8 @@ describe Gitlab::Ci::Config::External::File::Project do end it 'should return false' do - expect(subject).not_to be_valid - expect(subject.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!") + expect(project_file).not_to be_valid + expect(project_file.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!") end end @@ -124,8 +125,8 @@ describe Gitlab::Ci::Config::External::File::Project do end it 'should return false' do - expect(subject).not_to be_valid - expect(subject.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!") + expect(project_file).not_to be_valid + expect(project_file.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!") end end @@ -135,12 +136,22 @@ describe Gitlab::Ci::Config::External::File::Project do end it 'should return false' do - expect(subject).not_to be_valid - expect(subject.error_message).to include('Included file `/invalid-file` does not have YAML extension!') + expect(project_file).not_to be_valid + expect(project_file.error_message).to include('Included file `/invalid-file` does not have YAML extension!') end end end + describe '#expand_context' do + let(:params) { { file: 'file.yml', project: project.full_path, ref: 'master' } } + + subject { project_file.send(:expand_context) } + + it 'inherits user, and target project and sha' do + is_expected.to include(user: user, project: project, sha: project.commit('master').id) + end + end + private def stub_project_blob(ref, path) diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index 3e0fda9c308..c5b32c29759 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::File::Remote do - let(:context) { described_class::Context.new(nil, '12345', nil) } + let(:context) { described_class::Context.new(nil, '12345', nil, Set.new) } let(:params) { { remote: location } } let(:remote_file) { described_class.new(params, context) } let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } @@ -181,4 +181,14 @@ describe Gitlab::Ci::Config::External::File::Remote do end end end + + describe '#expand_context' do + let(:params) { { remote: 'http://remote' } } + + subject { remote_file.send(:expand_context) } + + it 'drops all parameters' do + is_expected.to include(user: nil, project: nil, sha: nil) + end + end end diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb index 1fb5655309a..8ecaf4800f8 100644 --- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb @@ -3,18 +3,21 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::File::Template do - let(:context) { described_class::Context.new(nil, '12345') } + set(:project) { create(:project) } + set(:user) { create(:user) } + + let(:context) { described_class::Context.new(project, '12345', user, Set.new) } let(:template) { 'Auto-DevOps.gitlab-ci.yml' } let(:params) { { template: template } } - subject { described_class.new(params, context) } + let(:template_file) { described_class.new(params, context) } describe '#matching?' do context 'when a template is specified' do let(:params) { { template: 'some-template' } } it 'should return true' do - expect(subject).to be_matching + expect(template_file).to be_matching end end @@ -22,7 +25,7 @@ describe Gitlab::Ci::Config::External::File::Template do let(:params) { { template: nil } } it 'should return false' do - expect(subject).not_to be_matching + expect(template_file).not_to be_matching end end @@ -30,7 +33,7 @@ describe Gitlab::Ci::Config::External::File::Template do let(:params) { {} } it 'should return false' do - expect(subject).not_to be_matching + expect(template_file).not_to be_matching end end end @@ -40,7 +43,7 @@ describe Gitlab::Ci::Config::External::File::Template do let(:template) { 'Auto-DevOps.gitlab-ci.yml' } it 'should return true' do - expect(subject).to be_valid + expect(template_file).to be_valid end end @@ -48,8 +51,8 @@ describe Gitlab::Ci::Config::External::File::Template do let(:template) { 'Template.yml' } it 'should return false' do - expect(subject).not_to be_valid - expect(subject.error_message).to include('Template file `Template.yml` is not a valid location!') + expect(template_file).not_to be_valid + expect(template_file.error_message).to include('Template file `Template.yml` is not a valid location!') end end @@ -57,14 +60,14 @@ describe Gitlab::Ci::Config::External::File::Template do let(:template) { 'I-Do-Not-Have-This-Template.gitlab-ci.yml' } it 'should return false' do - expect(subject).not_to be_valid - expect(subject.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!') + expect(template_file).not_to be_valid + expect(template_file.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!') end end end describe '#template_name' do - let(:template_name) { subject.send(:template_name) } + let(:template_name) { template_file.send(:template_name) } context 'when template does end with .gitlab-ci.yml' do let(:template) { 'my-template.gitlab-ci.yml' } @@ -90,4 +93,14 @@ describe Gitlab::Ci::Config::External::File::Template do end end end + + describe '#expand_context' do + let(:location) { 'location.yml' } + + subject { template_file.send(:expand_context) } + + it 'drops all parameters' do + is_expected.to include(user: nil, project: nil, sha: nil) + end + end end diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index 4cab4961b0f..136974569de 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -9,6 +9,7 @@ describe Gitlab::Ci::Config::External::Mapper do let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' } let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' } + let(:expandset) { Set.new } let(:file_content) do <<~HEREDOC @@ -21,7 +22,7 @@ describe Gitlab::Ci::Config::External::Mapper do end describe '#process' do - subject { described_class.new(values, project: project, sha: '123456', user: user).process } + subject { described_class.new(values, project: project, sha: '123456', user: user, expandset: expandset).process } context "when single 'include' keyword is defined" do context 'when the string is a local file' do @@ -141,5 +142,37 @@ describe Gitlab::Ci::Config::External::Mapper do expect(subject).to be_empty end end + + context "when duplicate 'include' is defined" do + let(:values) do + { include: [ + { 'local' => local_file }, + { 'local' => local_file } + ], + image: 'ruby:2.2' } + end + + it 'raises an exception' do + expect { subject }.to raise_error(described_class::DuplicateIncludesError) + end + end + + context "when too many 'includes' are defined" do + let(:values) do + { include: [ + { 'local' => local_file }, + { 'remote' => remote_url } + ], + image: 'ruby:2.2' } + end + + before do + stub_const("#{described_class}::MAX_INCLUDES", 1) + end + + it 'raises an exception' do + expect { subject }.to raise_error(described_class::TooManyIncludesError) + end + end end end diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 1ac58139b25..3f6f6d7c5d9 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -4,15 +4,20 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::Processor do set(:project) { create(:project, :repository) } + set(:another_project) { create(:project, :repository) } set(:user) { create(:user) } - let(:processor) { described_class.new(values, project: project, sha: '12345', user: user) } + let(:expandset) { Set.new } + let(:sha) { '12345' } + let(:processor) { described_class.new(values, project: project, sha: '12345', user: user, expandset: expandset) } before do project.add_developer(user) end describe "#perform" do + subject { processor.perform } + context 'when no external files defined' do let(:values) { { image: 'ruby:2.2' } } @@ -190,5 +195,80 @@ describe Gitlab::Ci::Config::External::Processor do expect(processor.perform[:image]).to eq('ruby:2.2') end end + + context "when a nested includes are defined" do + let(:values) do + { + include: [ + { local: '/local/file.yml' } + ], + image: 'ruby:2.2' + } + end + + before do + allow(project.repository).to receive(:blob_data_at).with('12345', '/local/file.yml') do + <<~HEREDOC + include: + - template: Ruby.gitlab-ci.yml + - remote: http://my.domain.com/config.yml + - project: #{another_project.full_path} + file: /templates/my-workflow.yml + HEREDOC + end + + allow_any_instance_of(Repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-workflow.yml') do + <<~HEREDOC + include: + - local: /templates/my-build.yml + HEREDOC + end + + allow_any_instance_of(Repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-build.yml') do + <<~HEREDOC + my_build: + script: echo Hello World + HEREDOC + end + + WebMock.stub_request(:get, 'http://my.domain.com/config.yml').to_return(body: 'remote_build: { script: echo Hello World }') + end + + context 'when project is public' do + before do + another_project.update!(visibility: 'public') + end + + it 'properly expands all includes' do + is_expected.to include(:my_build, :remote_build, :rspec) + end + end + + context 'when user is reporter of another project' do + before do + another_project.add_reporter(user) + end + + it 'properly expands all includes' do + is_expected.to include(:my_build, :remote_build, :rspec) + end + end + + context 'when user is not allowed' do + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError, /not found or access denied/) + end + end + + context 'when too many includes is included' do + before do + stub_const('Gitlab::Ci::Config::External::Mapper::MAX_INCLUDES', 1) + end + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError, /Maximum of 1 nested/) + end + end + end end end -- cgit v1.2.1 From b93af54ce23667379bd151f614e5988523a40c68 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Wed, 30 Jan 2019 13:40:22 -0600 Subject: Fix error tracking page, not showing an empty state This MR adds a missing empty state for when a backend response contains an empty error list, it shows the table but with `no data to display` empty state, also adds a reload button that allows to refresh for data --- .../components/error_tracking_list_spec.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js index 08bbb390993..503af3920a8 100644 --- a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js +++ b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js @@ -1,7 +1,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; -import { GlButton, GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui'; +import { GlButton, GlEmptyState, GlLoadingIcon, GlTable, GlLink } from '@gitlab/ui'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -9,6 +9,7 @@ localVue.use(Vuex); describe('ErrorTrackingList', () => { let store; let wrapper; + let actions; function mountComponent({ errorTrackingEnabled = true } = {}) { wrapper = shallowMount(ErrorTrackingList, { @@ -20,12 +21,17 @@ describe('ErrorTrackingList', () => { errorTrackingEnabled, illustrationPath: 'illustration/path', }, + stubs: { + 'gl-link': GlLink, + }, }); } beforeEach(() => { - const actions = { + actions = { getErrorList: () => {}, + startPolling: () => {}, + restartPolling: jasmine.createSpy('restartPolling'), }; const state = { @@ -83,6 +89,18 @@ describe('ErrorTrackingList', () => { expect(wrapper.find(GlTable).exists()).toBeTruthy(); expect(wrapper.find(GlButton).exists()).toBeTruthy(); }); + + it('shows a message prompting to refresh', () => { + const refreshLink = wrapper.vm.$refs.empty.querySelector('a'); + + expect(refreshLink.textContent.trim()).toContain('Check again'); + }); + + it('restarts polling', () => { + wrapper.find('.js-try-again').trigger('click'); + + expect(actions.restartPolling).toHaveBeenCalled(); + }); }); describe('error tracking feature disabled', () => { -- cgit v1.2.1 From fb6a4e21d4a5142e72ac5864fae29192cfc59d64 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 21 Feb 2019 13:07:26 -0800 Subject: Bring back Rugged implementation of find_commit This brings back some of the changes in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20339. For users using Gitaly on top of NFS, accessing the Git data directly via Rugged is more performant than Gitaly. This merge request introduces the feature flag `rugged_find_commit` to activate Rugged paths. There are also Rake tasks `gitlab:features:enable_rugged` and `gitlab:features:disable_rugged` to enable/disable these feature flags altogether. Part of four Rugged changes identified in https://gitlab.com/gitlab-org/gitlab-ce/issues/57317. --- spec/lib/gitlab/git/commit_spec.rb | 16 +++++++++++++++- spec/lib/gitlab/gitaly_client/storage_settings_spec.rb | 10 ++++++++++ spec/spec_helper.rb | 9 ++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 2611ebed25b..3fb41a626b2 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -112,7 +112,7 @@ describe Gitlab::Git::Commit, :seed_helper do end context 'Class methods' do - describe '.find' do + shared_examples '.find' do it "should return first head commit if without params" do expect(described_class.last(repository).id).to eq( rugged_repo.head.target.oid @@ -154,6 +154,20 @@ describe Gitlab::Git::Commit, :seed_helper do end end + describe '.find with Gitaly enabled' do + it_should_behave_like '.find' + end + + describe '.find with Rugged enabled', :enable_rugged do + it 'calls out to the Rugged implementation' do + allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original + + described_class.find(repository, SeedRepo::Commit::ID) + end + + it_should_behave_like '.find' + end + describe '.last_for_path' do context 'no path' do subject { described_class.last_for_path(repository, 'master') } diff --git a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb index c89913ec8e9..bb10be2a4dc 100644 --- a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb +++ b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb @@ -26,4 +26,14 @@ describe Gitlab::GitalyClient::StorageSettings do end end end + + describe '.disk_access_denied?' do + it 'return false when Rugged is enabled', :enable_rugged do + expect(described_class.disk_access_denied?).to be_falsey + end + + it 'returns true' do + expect(described_class.disk_access_denied?).to be_truthy + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 97e7a019222..e8d7b18bf04 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -115,10 +115,17 @@ RSpec.configure do |config| TestEnv.clean_test_path end - config.before do + config.before do |example| # Enable all features by default for testing allow(Feature).to receive(:enabled?) { true } + enabled = example.metadata[:enable_rugged].present? + + # Disable Rugged features by default + Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag| + allow(Feature).to receive(:enabled?).with(flag).and_return(enabled) + end + # The following can be removed when we remove the staged rollout strategy # and we can just enable it using instance wide settings # (ie. ApplicationSetting#auto_devops_enabled) -- cgit v1.2.1 From a87fe585f23b78732af31d636bf134a90781ca4c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 25 Feb 2019 22:17:11 +0000 Subject: Fix transient dirty submit --- spec/javascripts/dirty_submit/dirty_submit_form_spec.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js index ae2a785de52..95cc90dcb0f 100644 --- a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js +++ b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js @@ -13,6 +13,8 @@ function expectToToggleDisableOnDirtyUpdate(submit, input) { } describe('DirtySubmitForm', () => { + DirtySubmitForm.THROTTLE_DURATION = 0; + it('disables submit until there are changes', done => { const { form, input, submit } = createForm(); -- cgit v1.2.1 From a769292c672dea9fae702dd75f643a14752110ee Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 1 Mar 2019 10:12:50 -0800 Subject: Eliminate most N+1 queries loading UserController#calendar_activities We can reduce a significant number of queries by preloading the associations for events. On GitLab.com, for a date that had 456 events, this brought the load time down from 8.7 to 1.2 s. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/58392 --- spec/controllers/users_controller_spec.rb | 34 ++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index af61026098b..4f6a6881193 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -185,13 +185,32 @@ describe UsersController do context 'for user' do context 'with public profile' do - it 'renders calendar_activities' do - push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) - EventCreateService.new.push(project, public_user, push_data) + let(:issue) { create(:issue, project: project, author: user) } + let(:note) { create(:note, noteable: issue, author: user, project: project) } + + render_views + + before do + create_push_event + create_note_event + end + it 'renders calendar_activities' do get :calendar_activities, params: { username: public_user.username } + expect(assigns[:events]).not_to be_empty end + + it 'avoids N+1 queries', :request_store do + get :calendar_activities, params: { username: public_user.username } + + control = ActiveRecord::QueryRecorder.new { get :calendar_activities, params: { username: public_user.username } } + + create_push_event + create_note_event + + expect { get :calendar_activities, params: { username: public_user.username } }.not_to exceed_query_limit(control) + end end context 'with private profile' do @@ -203,6 +222,15 @@ describe UsersController do expect(response).to have_gitlab_http_status(:not_found) end end + + def create_push_event + push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) + EventCreateService.new.push(project, public_user, push_data) + end + + def create_note_event + EventCreateService.new.leave_note(note, public_user) + end end end -- cgit v1.2.1 From dea631545f580d22e63ff09f9d9f194a559d2612 Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Tue, 26 Feb 2019 00:27:16 +0100 Subject: fix group without owner after transfer --- spec/services/groups/transfer_service_spec.rb | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'spec') diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index 6b48c993c57..79d504b9b45 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -410,5 +410,34 @@ describe Groups::TransferService, :postgresql do end end end + + context 'when transferring a subgroup into root group' do + let(:group) { create(:group, :public, :nested) } + let(:subgroup) { create(:group, :public, parent: group) } + let(:transfer_service) { described_class.new(subgroup, user) } + + it 'ensures there is still an owner for the transferred group' do + expect(subgroup.owners).to be_empty + + transfer_service.execute(nil) + subgroup.reload + + expect(subgroup.owners).to match_array(user) + end + + context 'when group has explicit owner' do + let(:another_owner) { create(:user) } + let!(:another_member) { create(:group_member, :owner, group: subgroup, user: another_owner) } + + it 'does not add additional owner' do + expect(subgroup.owners).to match_array(another_owner) + + transfer_service.execute(nil) + subgroup.reload + + expect(subgroup.owners).to match_array(another_owner) + end + end + end end end -- cgit v1.2.1 From 294c5c41beaac1fbc60c67df2c8745f7583544a1 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 1 Mar 2019 16:24:47 -0300 Subject: Remove auto vacuum logic, decrease batch size and interval --- .../schedule_sync_issuables_state_id_spec.rb | 35 ++-------------------- 1 file changed, 2 insertions(+), 33 deletions(-) (limited to 'spec') diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb index 6c4c31ae554..6b1ea804342 100644 --- a/spec/migrations/schedule_sync_issuables_state_id_spec.rb +++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb @@ -26,33 +26,14 @@ describe ScheduleSyncIssuablesStateId, :migration do Timecop.freeze do migrate! - expect(migration).to be_scheduled_delayed_migration(5.minutes, resource_1.id, resource_2.id) - expect(migration).to be_scheduled_delayed_migration(10.minutes, resource_3.id, resource_4.id) + expect(migration).to be_scheduled_delayed_migration(120.seconds, resource_1.id, resource_2.id) + expect(migration).to be_scheduled_delayed_migration(240.seconds, resource_3.id, resource_4.id) expect(BackgroundMigrationWorker.jobs.size).to eq(2) end end end end - shared_examples 'rescheduling migrations' do - before do - Sidekiq::Worker.clear_all - end - - it 'reschedules migrations when should wait for dead tuple vacuum' do - worker = worker_class.new - - Sidekiq::Testing.fake! do - allow(worker).to receive(:wait_for_deadtuple_vacuum?) { true } - - worker.perform(resource_1.id, resource_2.id) - - expect(worker_class.name.demodulize).to be_scheduled_delayed_migration(5.minutes, resource_1.id, resource_2.id) - expect(BackgroundMigrationWorker.jobs.size).to eq(1) - end - end - end - describe '#up' do context 'issues' do it 'migrates state column to integer' do @@ -76,12 +57,6 @@ describe ScheduleSyncIssuablesStateId, :migration do let!(:resource_3) { issues.create!(description: 'third', state: 'closed') } let!(:resource_4) { issues.create!(description: 'fourth', state: 'closed') } end - - it_behaves_like 'rescheduling migrations' do - let(:worker_class) { Gitlab::BackgroundMigration::SyncIssuesStateId } - let!(:resource_1) { issues.create!(description: 'first', state: 'opened') } - let!(:resource_2) { issues.create!(description: 'second', state: 'closed') } - end end context 'merge requests' do @@ -108,12 +83,6 @@ describe ScheduleSyncIssuablesStateId, :migration do let!(:resource_3) { merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') } let!(:resource_4) { merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') } end - - it_behaves_like 'rescheduling migrations' do - let(:worker_class) { Gitlab::BackgroundMigration::SyncMergeRequestsStateId } - let(:resource_1) { merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') } - let(:resource_2) { merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') } - end end end end -- cgit v1.2.1 From 87adc799ebeb5b69f398e9a4ed37640a0d8c6c44 Mon Sep 17 00:00:00 2001 From: John Cai Date: Wed, 27 Feb 2019 13:37:33 -0800 Subject: Removing old code path for search_files_by_content In 11.8, we added a fix for the SearchFilesByContent RPC in gitaly to send back the response in chunks. However, we kept in the old code path for backwards compatibility. Now that the change is fully deployed, we can remove that old codepath. --- spec/lib/gitlab/git/repository_spec.rb | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index e3dd02f1478..7e6dfa30e37 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -619,16 +619,6 @@ describe Gitlab::Git::Repository, :seed_helper do repository.search_files_by_content('search-files-by-content', 'search-files-by-content-branch') end end - - it_should_behave_like 'search files by content' do - let(:search_results) do - repository.gitaly_repository_client.search_files_by_content( - 'search-files-by-content-branch', - 'search-files-by-content', - chunked_response: false - ) - end - end end describe '#find_remote_root_ref' do -- cgit v1.2.1 From 981c2488633072ae013ed13ba6663e8f87fd357b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 2 Mar 2019 15:18:14 +0100 Subject: Do not raise when adding undefined variables resource --- spec/lib/gitlab/ci/variables/collection_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index edb209c0cf4..8e732d44d5d 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -66,6 +66,14 @@ describe Gitlab::Ci::Variables::Collection do expect(collection).to include(key: 'VAR_3', value: '3', public: true) end + it 'does not concatenate resource if it undefined' do + collection = described_class.new([{ key: 'VAR_1', value: '1' }]) + + collection.concat(nil) + + expect(collection).to be_one + end + it 'returns self' do expect(subject.concat([key: 'VAR', value: 'test'])) .to eq subject -- cgit v1.2.1 From b6a972787048a2ab9024051ecfe40f1a46bcc67d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 1 Mar 2019 23:29:04 -0800 Subject: Show pre-receive error message in merge request widget When a merge fails due to a locked file, no feedback was given to the user as to why it failed if the user used the Merge button. However, if the push attempt happened directly in the Web UI, the pre-receive message would be displayed. To be consistent and to make it clear why a merge failed, we now include the pre-receive error message in the merge request widget. Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/10165 --- spec/services/merge_requests/merge_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index ede79b87bcc..9d674263259 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -244,7 +244,7 @@ describe MergeRequests::MergeService do service.execute(merge_request) - expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook') + expect(merge_request.merge_error).to include("Something went wrong during merge pre-receive hook: #{error_message}") expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) end -- cgit v1.2.1 From 552a32c01545c82df3790dffa5545a451fd63529 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 2 Mar 2019 15:29:04 +0100 Subject: Extract scoped CI/CD variables to a separate concern This commits extract code responsible for calculating essential CI/CD variables to a separate concern. This makes it possible to share this code between a `Ci::Build` and a `Ci::Bridge`. We might want to refactor this to use composition instead of inheritance. --- spec/models/ci/bridge_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'spec') diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 741cdfef1a5..c5908eed00a 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -22,4 +22,25 @@ describe Ci::Bridge do expect(status).to be_a Gitlab::Ci::Status::Success end end + + describe '#scoped_variables_hash' do + it 'returns a hash representing variables' do + expect(bridge.scoped_variables_hash.keys).to eq %w[ + CI GITLAB_CI GITLAB_FEATURES CI_SERVER_NAME + CI_SERVER_VERSION CI_SERVER_VERSION_MAJOR + CI_SERVER_VERSION_MINOR CI_SERVER_VERSION_PATCH + CI_SERVER_REVISION CI_JOB_NAME CI_JOB_STAGE + CI_COMMIT_SHA CI_COMMIT_SHORT_SHA CI_COMMIT_BEFORE_SHA + CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG CI_NODE_TOTAL + CI_BUILD_REF CI_BUILD_BEFORE_SHA CI_BUILD_REF_NAME + CI_BUILD_REF_SLUG CI_BUILD_NAME CI_BUILD_STAGE + CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH + CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_URL + CI_PROJECT_VISIBILITY CI_PAGES_DOMAIN CI_PAGES_URL + CI_REGISTRY CI_REGISTRY_IMAGE CI_API_V4_URL + CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE + CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION + ] + end + end end -- cgit v1.2.1 From 9e12baea1aa768f22d834fb1dbe7c788ea453c0b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 2 Mar 2019 15:46:56 +0100 Subject: Add test for evaluating bridge variables policy --- spec/lib/gitlab/ci/build/policy/variables_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb index c2c0742efc3..9b016901a20 100644 --- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb @@ -15,6 +15,7 @@ describe Gitlab::Ci::Build::Policy::Variables do before do pipeline.variables.build(key: 'CI_PROJECT_NAME', value: '') + pipeline.variables.build(key: 'MY_VARIABLE', value: 'my-var') end describe '#satisfied_by?' do @@ -24,6 +25,12 @@ describe Gitlab::Ci::Build::Policy::Variables do expect(policy).to be_satisfied_by(pipeline, seed) end + it 'is satisfied by a matching pipeline variable' do + policy = described_class.new(['$MY_VARIABLE']) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + it 'is not satisfied by an overridden empty variable' do policy = described_class.new(['$CI_PROJECT_NAME']) @@ -68,5 +75,19 @@ describe Gitlab::Ci::Build::Policy::Variables do expect(pipeline).not_to be_persisted expect(seed.to_resource).not_to be_persisted end + + context 'when a bridge job is used' do + let(:bridge) do + build(:ci_bridge, pipeline: pipeline, project: project, ref: 'master') + end + + let(:seed) { double('bridge seed', to_resource: bridge) } + + it 'is satisfied by a matching expression for a bridge job' do + policy = described_class.new(['$MY_VARIABLE']) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + end end end -- cgit v1.2.1 From a5ac7e7a0c9aa5e6c9a429444d1ae2a90718eb94 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 2 Mar 2019 19:21:19 +0100 Subject: Fix CI/CD bridge variables unit test --- spec/models/ci/bridge_spec.rb | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index c5908eed00a..b5ec8991720 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -25,22 +25,16 @@ describe Ci::Bridge do describe '#scoped_variables_hash' do it 'returns a hash representing variables' do - expect(bridge.scoped_variables_hash.keys).to eq %w[ - CI GITLAB_CI GITLAB_FEATURES CI_SERVER_NAME - CI_SERVER_VERSION CI_SERVER_VERSION_MAJOR - CI_SERVER_VERSION_MINOR CI_SERVER_VERSION_PATCH - CI_SERVER_REVISION CI_JOB_NAME CI_JOB_STAGE - CI_COMMIT_SHA CI_COMMIT_SHORT_SHA CI_COMMIT_BEFORE_SHA - CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG CI_NODE_TOTAL - CI_BUILD_REF CI_BUILD_BEFORE_SHA CI_BUILD_REF_NAME - CI_BUILD_REF_SLUG CI_BUILD_NAME CI_BUILD_STAGE + variables = %w[ + CI_JOB_NAME CI_JOB_STAGE CI_COMMIT_SHA CI_COMMIT_SHORT_SHA + CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH - CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_URL - CI_PROJECT_VISIBILITY CI_PAGES_DOMAIN CI_PAGES_URL - CI_REGISTRY CI_REGISTRY_IMAGE CI_API_V4_URL - CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE - CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION + CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID + CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE + CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION ] + + expect(bridge.scoped_variables_hash.keys).to include(*variables) end end end -- cgit v1.2.1 From cf4bbd4fe18222234e21c9bc4a12ea195a4bd11d Mon Sep 17 00:00:00 2001 From: haghighi_ahmad Date: Sun, 24 Feb 2019 09:12:22 +0330 Subject: Add Saturday to first day of the week fix #58023 docs for adding Saturday for first day of the week add related settings for Saturday as first day of the week firstDayOfWeek: Use enumeration, replace day's numbers with corresponding names make some variables lowercase (follow camelCase) add CHANGELOG entry Author: haghighi_ahmad modified: app/assets/javascripts/pages/users/activity_calendar.js modified: app/helpers/preferences_helper.rb new file: changelogs/unreleased/58023-add-Saturday-to-localization-first-day-of-the-week.yml modified: doc/api/settings.md modified: doc/user/profile/preferences.md modified: locale/gitlab.pot modified: spec/helpers/preferences_helper_spec.rb --- spec/helpers/preferences_helper_spec.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index e0e8ebd0c3c..db0d45c3692 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -36,10 +36,11 @@ describe PreferencesHelper do end describe '#first_day_of_week_choices' do - it 'returns Sunday and Monday as choices' do + it 'returns Saturday, Sunday and Monday as choices' do expect(helper.first_day_of_week_choices).to eq [ ['Sunday', 0], - ['Monday', 1] + ['Monday', 1], + ['Saturday', 6] ] end end @@ -47,14 +48,21 @@ describe PreferencesHelper do describe '#first_day_of_week_choices_with_default' do it 'returns choices including system default' do expect(helper.first_day_of_week_choices_with_default).to eq [ - ['System default (Sunday)', nil], ['Sunday', 0], ['Monday', 1] + ['System default (Sunday)', nil], ['Sunday', 0], ['Monday', 1], ['Saturday', 6] ] end it 'returns choices including system default set to Monday' do stub_application_setting(first_day_of_week: 1) expect(helper.first_day_of_week_choices_with_default).to eq [ - ['System default (Monday)', nil], ['Sunday', 0], ['Monday', 1] + ['System default (Monday)', nil], ['Sunday', 0], ['Monday', 1], ['Saturday', 6] + ] + end + + it 'returns choices including system default set to Saturday' do + stub_application_setting(first_day_of_week: 6) + expect(helper.first_day_of_week_choices_with_default).to eq [ + ['System default (Saturday)', nil], ['Sunday', 0], ['Monday', 1], ['Saturday', 6] ] end end -- cgit v1.2.1 From ec7835946a0c981e9b263d2840e68f16eb1b3789 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 4 Mar 2019 08:20:37 +0000 Subject: Creates a function to check if repo is EE Adds EE information to gon Creates a global vue mixin --- spec/javascripts/test_bundle.js | 3 +++ spec/lib/gitlab_spec.rb | 14 ++++++++++++++ 2 files changed, 17 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index b2b0a50911d..5eef5682bbd 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -8,6 +8,7 @@ import '~/commons'; import Vue from 'vue'; import VueResource from 'vue-resource'; import Translate from '~/vue_shared/translate'; +import CheckEE from '~/vue_shared/mixins/is_ee'; import jasmineDiff from 'jasmine-diff'; import { getDefaultAdapter } from '~/lib/utils/axios_utils'; @@ -43,6 +44,7 @@ Vue.config.errorHandler = function(err) { Vue.use(VueResource); Vue.use(Translate); +Vue.use(CheckEE); // enable test fixtures jasmine.getFixtures().fixturesPath = FIXTURES_PATH; @@ -67,6 +69,7 @@ window.gl = window.gl || {}; window.gl.TEST_HOST = TEST_HOST; window.gon = window.gon || {}; window.gon.test_env = true; +window.gon.ee = false; gon.relative_url_root = ''; let hasUnhandledPromiseRejections = false; diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 5f7a0cca351..8232715d00e 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -95,4 +95,18 @@ describe Gitlab do expect(described_class.com?).to eq false end end + + describe '.ee?' do + it 'returns true when using Enterprise Edition' do + stub_const('License', Class.new) + + expect(described_class.ee?).to eq(true) + end + + it 'returns false when using Community Edition' do + hide_const('License') + + expect(described_class.ee?).to eq(false) + end + end end -- cgit v1.2.1 From 78dc1b58a64be03e6d3622aab5605e1d5f6a6643 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 4 Mar 2019 09:21:47 +0000 Subject: Show commands applied message when promoting issues Fix 'commands applied' messages not being shown when issue is promoted to epic using slash commands. --- .../issues/user_uses_quick_actions_spec.rb | 4 +++- .../quick_actions/command_definition_spec.rb | 7 ++++++- spec/services/notes/create_service_spec.rb | 13 ++++++++++++ spec/services/notes/quick_actions_service_spec.rb | 24 +++++++++++----------- .../quick_actions/interpret_service_spec.rb | 10 +++++++++ 5 files changed, 44 insertions(+), 14 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 27cffdc5f8b..b5e7c3954e2 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -243,7 +243,9 @@ describe 'Issues > User uses quick actions', :js do it 'does not move the issue' do add_note("/move not/valid") - expect(page).not_to have_content 'Commands applied' + wait_for_requests + + expect(page).to have_content 'Commands applied' expect(issue.reload).to be_open end end diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb index 5dae82a63b4..136cfb5bcc5 100644 --- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb +++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb @@ -72,7 +72,7 @@ describe Gitlab::QuickActions::CommandDefinition do end describe "#execute" do - let(:context) { OpenStruct.new(run: false) } + let(:context) { OpenStruct.new(run: false, commands_executed_count: nil) } context "when the command is a noop" do it "doesn't execute the command" do @@ -80,6 +80,7 @@ describe Gitlab::QuickActions::CommandDefinition do subject.execute(context, nil) + expect(context.commands_executed_count).to be_nil expect(context.run).to be false end end @@ -97,6 +98,7 @@ describe Gitlab::QuickActions::CommandDefinition do it "doesn't execute the command" do subject.execute(context, nil) + expect(context.commands_executed_count).to be_nil expect(context.run).to be false end end @@ -112,6 +114,7 @@ describe Gitlab::QuickActions::CommandDefinition do subject.execute(context, true) expect(context.run).to be true + expect(context.commands_executed_count).to eq(1) end end @@ -120,6 +123,7 @@ describe Gitlab::QuickActions::CommandDefinition do subject.execute(context, nil) expect(context.run).to be true + expect(context.commands_executed_count).to eq(1) end end end @@ -134,6 +138,7 @@ describe Gitlab::QuickActions::CommandDefinition do subject.execute(context, true) expect(context.run).to be true + expect(context.commands_executed_count).to eq(1) end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 1645b67c329..8d8e81173ff 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -220,6 +220,19 @@ describe Notes::CreateService do expect(note.note).to eq "HELLO\nWORLD" end end + + context 'when note only have commands' do + it 'adds commands applied message to note errors' do + note_text = %(/close) + service = double(:service) + allow(Issues::UpdateService).to receive(:new).and_return(service) + expect(service).to receive(:execute) + + note = described_class.new(project, user, opts.merge(note: note_text)).execute + + expect(note.errors[:commands_only]).to be_present + end + end end context 'as a user who cannot update the target' do diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 14d62763a5b..7d2b6d5b8a7 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -28,8 +28,8 @@ describe Notes::QuickActionsService do end it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do - content, command_params = service.extract_commands(note) - service.execute(command_params, note) + content, update_params = service.execute(note) + service.apply_updates(update_params, note) expect(content).to eq '' expect(note.noteable).to be_closed @@ -47,8 +47,8 @@ describe Notes::QuickActionsService do let(:note_text) { '/reopen' } it 'opens the noteable, and leave no note' do - content, command_params = service.extract_commands(note) - service.execute(command_params, note) + content, update_params = service.execute(note) + service.apply_updates(update_params, note) expect(content).to eq '' expect(note.noteable).to be_open @@ -59,8 +59,8 @@ describe Notes::QuickActionsService do let(:note_text) { '/spend 1h' } it 'updates the spent time on the noteable' do - content, command_params = service.extract_commands(note) - service.execute(command_params, note) + content, update_params = service.execute(note) + service.apply_updates(update_params, note) expect(content).to eq '' expect(note.noteable.time_spent).to eq(3600) @@ -75,8 +75,8 @@ describe Notes::QuickActionsService do end it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do - content, command_params = service.extract_commands(note) - service.execute(command_params, note) + content, update_params = service.execute(note) + service.apply_updates(update_params, note) expect(content).to eq "HELLO\nWORLD" expect(note.noteable).to be_closed @@ -94,8 +94,8 @@ describe Notes::QuickActionsService do let(:note_text) { "HELLO\n/reopen\nWORLD" } it 'opens the noteable' do - content, command_params = service.extract_commands(note) - service.execute(command_params, note) + content, update_params = service.execute(note) + service.apply_updates(update_params, note) expect(content).to eq "HELLO\nWORLD" expect(note.noteable).to be_open @@ -190,8 +190,8 @@ describe Notes::QuickActionsService do end it 'adds only one assignee from the list' do - _, command_params = service.extract_commands(note) - service.execute(command_params, note) + _, update_params = service.execute(note) + service.apply_updates(update_params, note) expect(note.noteable.assignees.count).to eq(1) end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 938764f40b0..ea33d156c8a 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1526,5 +1526,15 @@ describe QuickActions::InterpretService do end end end + + context "#commands_executed_count" do + it 'counts commands executed' do + content = "/close and \n/assign me and \n/title new title" + + service.execute(content, issue) + + expect(service.commands_executed_count).to eq(3) + end + end end end -- cgit v1.2.1 From 294f0f399156475048c8c03fc8474e21f9f3e89b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 4 Mar 2019 11:27:57 +0000 Subject: CE backport of batch-comments-ee-store-module --- spec/javascripts/diffs/components/diff_content_spec.js | 2 +- spec/javascripts/diffs/components/diff_file_spec.js | 2 +- spec/javascripts/diffs/components/inline_diff_view_spec.js | 2 +- spec/javascripts/diffs/components/parallel_diff_view_spec.js | 6 +++++- 4 files changed, 8 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js index a1bb51963d6..bc9288e4150 100644 --- a/spec/javascripts/diffs/components/diff_content_spec.js +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import DiffContentComponent from '~/diffs/components/diff_content.vue'; -import { createStore } from '~/mr_notes/stores'; +import { createStore } from 'ee_else_ce/mr_notes/stores'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants'; import '~/behaviors/markdown/render_gfm'; diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index 65a1c9b8f15..ba04c8c4a4c 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import DiffFileComponent from '~/diffs/components/diff_file.vue'; import { diffViewerModes, diffViewerErrors } from '~/ide/constants'; -import store from '~/mr_notes/stores'; +import store from 'ee_else_ce/mr_notes/stores'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import diffFileMockData from '../mock_data/diff_file'; diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js index 2316ee29106..4452106580a 100644 --- a/spec/javascripts/diffs/components/inline_diff_view_spec.js +++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import '~/behaviors/markdown/render_gfm'; import InlineDiffView from '~/diffs/components/inline_diff_view.vue'; -import store from '~/mr_notes/stores'; +import store from 'ee_else_ce/mr_notes/stores'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import diffFileMockData from '../mock_data/diff_file'; import discussionsMockData from '../mock_data/diff_discussions'; diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js index 6f6b1c41915..236bda96145 100644 --- a/spec/javascripts/diffs/components/parallel_diff_view_spec.js +++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue'; -import store from '~/mr_notes/stores'; +import store from 'ee_else_ce/mr_notes/stores'; import * as constants from '~/diffs/constants'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import diffFileMockData from '../mock_data/diff_file'; @@ -18,6 +18,10 @@ describe('ParallelDiffView', () => { }).$mount(); }); + afterEach(() => { + component.$destroy(); + }); + describe('assigned', () => { describe('diffLines', () => { it('should normalize lines for empty cells', () => { -- cgit v1.2.1 From 6c19900509862d1dfb30e62ea37536adf764972e Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Mon, 4 Mar 2019 11:37:14 +0000 Subject: Fix username escaping when clicking 'assign to me' Add spec for assigning user with apostrophe in name --- spec/features/issues/user_creates_issue_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'spec') diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index e60486f6dcb..0f604db870f 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -93,4 +93,22 @@ describe "User creates issue" do end end end + + context "when signed in as user with special characters in their name" do + let(:user_special) { create(:user, name: "Jon O'Shea") } + + before do + project.add_developer(user_special) + sign_in(user_special) + + visit(new_project_issue_path(project)) + end + + it "will correctly escape user names with an apostrophe when clicking 'Assign to me'", :js do + first('.assign-to-me-link').click + + expect(page).to have_content(user_special.name) + expect(page.find('input[name="issue[assignee_ids][]"]', visible: false)['data-meta']).to eq(user_special.name) + end + end end -- cgit v1.2.1 From d9ba40aa044e63b95de2e1dae1d54da7f500cd6b Mon Sep 17 00:00:00 2001 From: Natalia Tepluhina Date: Mon, 4 Mar 2019 11:39:55 +0000 Subject: Fixed mutation for root - fixed test spec - added warning only if file is deleted --- .../ide/components/new_dropdown/modal_spec.js | 55 +++++++++++++++++++++- spec/javascripts/ide/stores/actions_spec.js | 28 ++++++++--- spec/javascripts/ide/stores/mutations_spec.js | 14 +++++- 3 files changed, 88 insertions(+), 9 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js index d94cc1a8faa..d1a0964ccdd 100644 --- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js @@ -18,6 +18,9 @@ describe('new file modal component', () => { store.state.entryModal = { type, path: '', + entry: { + path: '', + }, }; vm = createComponentWithStore(Component, store).$mount(); @@ -74,6 +77,7 @@ describe('new file modal component', () => { entry: { name: 'test', type: 'blob', + path: 'test-path', }, }; @@ -97,7 +101,7 @@ describe('new file modal component', () => { describe('entryName', () => { it('returns entries name', () => { - expect(vm.entryName).toBe('test'); + expect(vm.entryName).toBe('test-path'); }); it('updated name', () => { @@ -107,4 +111,53 @@ describe('new file modal component', () => { }); }); }); + + describe('submitForm', () => { + it('throws an error when target entry exists', () => { + const store = createStore(); + store.state.entryModal = { + type: 'rename', + path: 'test-path/test', + entry: { + name: 'test', + type: 'blob', + path: 'test-path/test', + }, + }; + store.state.entries = { + 'test-path/test': { + name: 'test', + deleted: false, + }, + }; + + vm = createComponentWithStore(Component, store).$mount(); + const flashSpy = spyOnDependency(modal, 'flash'); + vm.submitForm(); + + expect(flashSpy).toHaveBeenCalled(); + }); + + it('calls createTempEntry when target path does not exist', () => { + const store = createStore(); + store.state.entryModal = { + type: 'rename', + path: 'test-path/test', + entry: { + name: 'test', + type: 'blob', + path: 'test-path1/test', + }, + }; + + vm = createComponentWithStore(Component, store).$mount(); + spyOn(vm, 'createTempEntry').and.callFake(() => Promise.resolve()); + vm.submitForm(); + + expect(vm.createTempEntry).toHaveBeenCalledWith({ + name: 'test-path1', + type: 'tree', + }); + }); + }); }); diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index df291ade3f7..0b5587d02ae 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -499,12 +499,12 @@ describe('Multi-file store actions', () => { testAction( renameEntry, - { path: 'test', name: 'new-name' }, + { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, store.state, [ { type: types.RENAME_ENTRY, - payload: { path: 'test', name: 'new-name', entryPath: null }, + payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, }, ], [{ type: 'deleteEntry', payload: 'test' }], @@ -527,17 +527,33 @@ describe('Multi-file store actions', () => { testAction( renameEntry, - { path: 'test', name: 'new-name' }, + { path: 'test', name: 'new-name', parentPath: 'parent-path' }, store.state, [ { type: types.RENAME_ENTRY, - payload: { path: 'test', name: 'new-name', entryPath: null }, + payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, }, ], [ - { type: 'renameEntry', payload: { path: 'test', name: 'new-name', entryPath: 'tree-1' } }, - { type: 'renameEntry', payload: { path: 'test', name: 'new-name', entryPath: 'tree-2' } }, + { + type: 'renameEntry', + payload: { + path: 'test', + name: 'new-name', + entryPath: 'tree-1', + parentPath: 'parent-path/new-name', + }, + }, + { + type: 'renameEntry', + payload: { + path: 'test', + name: 'new-name', + entryPath: 'tree-2', + parentPath: 'parent-path/new-name', + }, + }, { type: 'deleteEntry', payload: 'test' }, ], done, diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 41dd3d3c67f..5ee098bf17f 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -298,7 +298,12 @@ describe('Multi-file store mutations', () => { }); it('creates new renamed entry', () => { - mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + mutations.RENAME_ENTRY(localState, { + path: 'oldPath', + name: 'newPath', + entryPath: null, + parentPath: '', + }); expect(localState.entries.newPath).toEqual({ ...localState.entries.oldPath, @@ -335,7 +340,12 @@ describe('Multi-file store mutations', () => { ...file(), }; - mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + mutations.RENAME_ENTRY(localState, { + path: 'oldPath', + name: 'newPath', + entryPath: null, + parentPath: 'parentPath', + }); expect(localState.entries.parentPath.tree.length).toBe(1); }); -- cgit v1.2.1 From d9fb27f42ac65b72c92fecdc2a3a292c76e9d7d7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 27 Feb 2019 18:12:28 +0100 Subject: Make serverless template a valid abstract template --- spec/lib/gitlab/ci/templates/templates_spec.rb | 37 +++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index 0dd74399a47..5dad8b1ba92 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -3,9 +3,40 @@ require 'spec_helper' describe "CI YML Templates" do - Gitlab::Template::GitlabCiYmlTemplate.all.each do |template| - it "#{template.name} should be valid" do - expect { Gitlab::Ci::YamlProcessor.new(template.content) }.not_to raise_error + ABSTRACT_TEMPLATES = %w[Serverless] + + def self.concrete_templates + Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template| + ABSTRACT_TEMPLATES.include?(template.name) + end + end + + def self.abstract_templates + Gitlab::Template::GitlabCiYmlTemplate.all.select do |template| + ABSTRACT_TEMPLATES.include?(template.name) + end + end + + describe 'concrete templates with CI/CD jobs' do + concrete_templates.each do |template| + it "#{template.name} template should be valid" do + expect { Gitlab::Ci::YamlProcessor.new(template.content) } + .not_to raise_error + end + end + end + + describe 'abstract templates without concrete jobs defined' do + abstract_templates.each do |template| + it "#{template.name} template should be valid after being implemented" do + content = template.content + <<~EOS + concrete_build_implemented_by_a_user: + stage: build + script: do something + EOS + + expect { Gitlab::Ci::YamlProcessor.new(content) }.not_to raise_error + end end end end -- cgit v1.2.1 From b491e3f91b4ec6a961c37222a43ce39c009ba3b0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 28 Feb 2019 14:26:56 +0100 Subject: Freeze abstract templates constant in specs --- spec/lib/gitlab/ci/templates/templates_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index 5dad8b1ba92..fbbd58280a9 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe "CI YML Templates" do - ABSTRACT_TEMPLATES = %w[Serverless] + ABSTRACT_TEMPLATES = %w[Serverless].freeze def self.concrete_templates Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template| -- cgit v1.2.1 From 6b3966407baff221a0f1f9019fba34c29bafa087 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 30 Jan 2019 04:42:50 +0000 Subject: Add tests for nav sidebar collapsed on render Adds rspec view tests that expect the nav sidebar to have a class that will cause the nav sidebar to be rendered collapsed with or without js. Tests the nav sidebar on the admin, group, instance statistics, user profile and project sidebars. --- spec/support/shared_examples/views/nav_sidebar.rb | 9 +++++++++ spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb | 2 ++ spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb | 13 +++++++++++++ .../nav/sidebar/_instance_statistics.html.haml_spec.rb | 7 +++++++ spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb | 13 +++++++++++++ spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb | 2 ++ 6 files changed, 46 insertions(+) create mode 100644 spec/support/shared_examples/views/nav_sidebar.rb create mode 100644 spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb create mode 100644 spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb create mode 100644 spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb (limited to 'spec') diff --git a/spec/support/shared_examples/views/nav_sidebar.rb b/spec/support/shared_examples/views/nav_sidebar.rb new file mode 100644 index 00000000000..3e709c8ab77 --- /dev/null +++ b/spec/support/shared_examples/views/nav_sidebar.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +shared_examples 'has nav sidebar' do + it 'has collapsed nav sidebar on mobile' do + render + + expect(rendered).to have_selector('.nav-sidebar.sidebar-collapsed-mobile') + end +end diff --git a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb index 05c2f61a606..bf63021a7fa 100644 --- a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb @@ -26,6 +26,8 @@ describe 'layouts/nav/sidebar/_admin' do it_behaves_like 'page has active tab', 'Overview' end + it_behaves_like 'has nav sidebar' + context 'on projects' do before do allow(controller).to receive(:controller_name).and_return('projects') diff --git a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb new file mode 100644 index 00000000000..24b66a0e767 --- /dev/null +++ b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'layouts/nav/sidebar/_group' do + let(:group) { create(:group) } + + before do + assign(:group, group) + end + + it_behaves_like 'has nav sidebar' +end diff --git a/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb new file mode 100644 index 00000000000..7f7f5637035 --- /dev/null +++ b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'layouts/nav/sidebar/_instance_statistics' do + it_behaves_like 'has nav sidebar' +end diff --git a/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb new file mode 100644 index 00000000000..6b820ab0b4c --- /dev/null +++ b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'layouts/nav/sidebar/_profile' do + let(:user) { create(:user) } + + before do + allow(view).to receive(:current_user).and_return(user) + end + + it_behaves_like 'has nav sidebar' +end diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index d9f05e5f94f..2c60ccfb754 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -11,6 +11,8 @@ describe 'layouts/nav/sidebar/_project' do allow(view).to receive(:can?).and_return(true) end + it_behaves_like 'has nav sidebar' + describe 'issue boards' do it 'has board tab' do render -- cgit v1.2.1 From 9ef058bee2b13b975cc52d9c9c688c6250ad2363 Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Thu, 7 Feb 2019 18:22:11 +1100 Subject: Updated nav-sidebar to collapse on load Adds the collapsed mixin to the styling for .nav-sidebar ensuring the nav-sidebar is collapsed on load via media queries. Removes the need for an intermediate class to collapse the sidebar on mobile viewports Updated toggleCollapsedSidebar to check for desktop breakpoint --- spec/support/shared_examples/views/nav_sidebar.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/support/shared_examples/views/nav_sidebar.rb b/spec/support/shared_examples/views/nav_sidebar.rb index 3e709c8ab77..6ac5abe275d 100644 --- a/spec/support/shared_examples/views/nav_sidebar.rb +++ b/spec/support/shared_examples/views/nav_sidebar.rb @@ -4,6 +4,8 @@ shared_examples 'has nav sidebar' do it 'has collapsed nav sidebar on mobile' do render - expect(rendered).to have_selector('.nav-sidebar.sidebar-collapsed-mobile') + expect(rendered).to have_selector('.nav-sidebar') + expect(rendered).not_to have_selector('.sidebar-collapsed-desktop') + expect(rendered).not_to have_selector('.sidebar-expanded-mobile') end end -- cgit v1.2.1 From d53005ef167de0a4b5b560d173435f3e0d0b4d9f Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Mon, 4 Mar 2019 15:49:18 +1100 Subject: Added test for interacting with the nav-sidebar on smaller screens Use mobile collapsing behaviour at width 1200px --- spec/features/projects/user_sees_sidebar_spec.rb | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index ee5734a9bf1..43fe477fb29 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -4,6 +4,90 @@ describe 'Projects > User sees sidebar' do let(:user) { create(:user) } let(:project) { create(:project, :private, public_builds: false, namespace: user.namespace) } + context 'on a smaller screen', :js do + include MobileHelpers + + before do + sign_in(user) + end + + # refactor behaviours into shared behaviours + + shared_examples 'has a collapsible mobile nav sidebar' do + it 'has a collapsed desktop nav-sidebar on load' do + expect(page).not_to have_content('Collapse sidebar') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + + it 'can expand the nav-sidebar' do + page.find('.nav-sidebar .js-toggle-sidebar').click + expect(page).to have_selector('.sidebar-expanded-mobile') + expect(page).to have_content('Collapse sidebar') + end + end + + context 'with xs size' do + before do + resize_screen_xs + visit project_path(project) + expect(page).to have_selector('.nav-sidebar') + expect(page).to have_selector('.toggle-mobile-nav') + end + + it 'has a collapsed nav-sidebar on load' do + expect(page).not_to have_content('.mobile-nav-open') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + + it 'can expand the nav-sidebar' do + page.find('.toggle-mobile-nav').click + expect(page).to have_selector('.mobile-nav-open') + expect(page).to have_selector('.sidebar-expanded-mobile') + end + end + + context 'with sm size' do + before do + resize_screen_sm + visit project_path(project) + expect(page).to have_selector('.nav-sidebar') + end + + it_behaves_like 'has a collapsible mobile nav sidebar' + end + + context 'at 1200px exactly' do + before do + resize_window(1200, 800) + visit project_path(project) + expect(page).to have_selector('.nav-sidebar') + end + + it_behaves_like 'has a collapsible mobile nav sidebar' + end + + context 'at 1201px exactly' do + before do + resize_window(1201, 800) + visit project_path(project) + expect(page).to have_selector('.nav-sidebar') + end + + it 'has a expanded desktop nav-sidebar on load' do + expect(page).to have_content('Collapse sidebar') + expect(page).not_to have_selector('.sidebar-collapsed-desktop') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + + it 'can collapse the nav-sidebar' do + page.find('.nav-sidebar .js-toggle-sidebar').click + expect(page).to have_selector('.sidebar-collapsed-desktop') + expect(page).not_to have_content('Collapse sidebar') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + end + end + context 'as owner' do before do sign_in(user) -- cgit v1.2.1 From e4f9c3f63ad7e8147acecd2324de90b63ffcd940 Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Mon, 4 Mar 2019 16:57:40 +1100 Subject: Fix collapsing behaviour at 1200px --- spec/features/projects/user_sees_sidebar_spec.rb | 38 +++++++++++++----------- 1 file changed, 20 insertions(+), 18 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index 43fe477fb29..736a574e937 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -11,8 +11,6 @@ describe 'Projects > User sees sidebar' do sign_in(user) end - # refactor behaviours into shared behaviours - shared_examples 'has a collapsible mobile nav sidebar' do it 'has a collapsed desktop nav-sidebar on load' do expect(page).not_to have_content('Collapse sidebar') @@ -26,6 +24,21 @@ describe 'Projects > User sees sidebar' do end end + shared_examples 'has a desktop nav sidebar' do + it 'has a expanded desktop nav-sidebar on load' do + expect(page).to have_content('Collapse sidebar') + expect(page).not_to have_selector('.sidebar-collapsed-desktop') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + + it 'can collapse the nav-sidebar' do + page.find('.nav-sidebar .js-toggle-sidebar').click + expect(page).to have_selector('.sidebar-collapsed-desktop') + expect(page).not_to have_content('Collapse sidebar') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + end + context 'with xs size' do before do resize_screen_xs @@ -56,9 +69,9 @@ describe 'Projects > User sees sidebar' do it_behaves_like 'has a collapsible mobile nav sidebar' end - context 'at 1200px exactly' do + context 'with size 1199px' do before do - resize_window(1200, 800) + resize_window(1199, 800) visit project_path(project) expect(page).to have_selector('.nav-sidebar') end @@ -66,25 +79,14 @@ describe 'Projects > User sees sidebar' do it_behaves_like 'has a collapsible mobile nav sidebar' end - context 'at 1201px exactly' do + context 'with a larger screen' do before do - resize_window(1201, 800) + resize_window(1200, 800) visit project_path(project) expect(page).to have_selector('.nav-sidebar') end - it 'has a expanded desktop nav-sidebar on load' do - expect(page).to have_content('Collapse sidebar') - expect(page).not_to have_selector('.sidebar-collapsed-desktop') - expect(page).not_to have_selector('.sidebar-expanded-mobile') - end - - it 'can collapse the nav-sidebar' do - page.find('.nav-sidebar .js-toggle-sidebar').click - expect(page).to have_selector('.sidebar-collapsed-desktop') - expect(page).not_to have_content('Collapse sidebar') - expect(page).not_to have_selector('.sidebar-expanded-mobile') - end + it_behaves_like 'has a desktop nav sidebar' end end -- cgit v1.2.1 From ed29dd5293896aa3848d799420bb63991c55af49 Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Mon, 4 Mar 2019 19:23:55 +1100 Subject: Align nav-sidebar with design.gitlab.com docs Updated nav-sidebar to reflect the documented behaviour from the design.gitlab.com docs: https://design.gitlab.com/regions/navigation#contextual-navigation Updated rspec tests to reflect the expected behaviour --- spec/features/projects/user_sees_sidebar_spec.rb | 70 +++++++++++++--------- .../features/reportable_note_shared_examples.rb | 2 +- 2 files changed, 44 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index 736a574e937..383e8824b7b 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -4,14 +4,30 @@ describe 'Projects > User sees sidebar' do let(:user) { create(:user) } let(:project) { create(:project, :private, public_builds: false, namespace: user.namespace) } - context 'on a smaller screen', :js do + # NOTE: See documented behaviour https://design.gitlab.com/regions/navigation#contextual-navigation + context 'on different viewports', :js do include MobileHelpers before do sign_in(user) end - shared_examples 'has a collapsible mobile nav sidebar' do + shared_examples 'has a expanded nav sidebar' do + it 'has a expanded desktop nav-sidebar on load' do + expect(page).to have_content('Collapse sidebar') + expect(page).not_to have_selector('.sidebar-collapsed-desktop') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + + it 'can collapse the nav-sidebar' do + page.find('.nav-sidebar .js-toggle-sidebar').click + expect(page).to have_selector('.sidebar-collapsed-desktop') + expect(page).not_to have_content('Collapse sidebar') + expect(page).not_to have_selector('.sidebar-expanded-mobile') + end + end + + shared_examples 'has a collapsed nav sidebar' do it 'has a collapsed desktop nav-sidebar on load' do expect(page).not_to have_content('Collapse sidebar') expect(page).not_to have_selector('.sidebar-expanded-mobile') @@ -24,22 +40,20 @@ describe 'Projects > User sees sidebar' do end end - shared_examples 'has a desktop nav sidebar' do - it 'has a expanded desktop nav-sidebar on load' do - expect(page).to have_content('Collapse sidebar') - expect(page).not_to have_selector('.sidebar-collapsed-desktop') + shared_examples 'has a mobile nav-sidebar' do + it 'has a hidden nav-sidebar on load' do + expect(page).not_to have_content('.mobile-nav-open') expect(page).not_to have_selector('.sidebar-expanded-mobile') end - it 'can collapse the nav-sidebar' do - page.find('.nav-sidebar .js-toggle-sidebar').click - expect(page).to have_selector('.sidebar-collapsed-desktop') - expect(page).not_to have_content('Collapse sidebar') - expect(page).not_to have_selector('.sidebar-expanded-mobile') + it 'can expand the nav-sidebar' do + page.find('.toggle-mobile-nav').click + expect(page).to have_selector('.mobile-nav-open') + expect(page).to have_selector('.sidebar-expanded-mobile') end end - context 'with xs size' do + context 'with a extra small viewport' do before do resize_screen_xs visit project_path(project) @@ -47,46 +61,48 @@ describe 'Projects > User sees sidebar' do expect(page).to have_selector('.toggle-mobile-nav') end - it 'has a collapsed nav-sidebar on load' do - expect(page).not_to have_content('.mobile-nav-open') - expect(page).not_to have_selector('.sidebar-expanded-mobile') - end + it_behaves_like 'has a mobile nav-sidebar' + end - it 'can expand the nav-sidebar' do - page.find('.toggle-mobile-nav').click - expect(page).to have_selector('.mobile-nav-open') - expect(page).to have_selector('.sidebar-expanded-mobile') + context 'with a small size viewport' do + before do + resize_screen_sm + visit project_path(project) + expect(page).to have_selector('.nav-sidebar') + expect(page).to have_selector('.toggle-mobile-nav') end + + it_behaves_like 'has a mobile nav-sidebar' end - context 'with sm size' do + context 'with medium size viewport' do before do - resize_screen_sm + resize_window(768, 800) visit project_path(project) expect(page).to have_selector('.nav-sidebar') end - it_behaves_like 'has a collapsible mobile nav sidebar' + it_behaves_like 'has a collapsed nav sidebar' end - context 'with size 1199px' do + context 'with viewport size 1199px' do before do resize_window(1199, 800) visit project_path(project) expect(page).to have_selector('.nav-sidebar') end - it_behaves_like 'has a collapsible mobile nav sidebar' + it_behaves_like 'has a collapsed nav sidebar' end - context 'with a larger screen' do + context 'with a extra large viewport' do before do resize_window(1200, 800) visit project_path(project) expect(page).to have_selector('.nav-sidebar') end - it_behaves_like 'has a desktop nav sidebar' + it_behaves_like 'has a expanded nav sidebar' end end diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb index 8cfce49da8a..89dfbf931d2 100644 --- a/spec/support/features/reportable_note_shared_examples.rb +++ b/spec/support/features/reportable_note_shared_examples.rb @@ -41,7 +41,7 @@ shared_examples 'reportable note' do |type| def open_dropdown(dropdown) # make window wide enough that tooltip doesn't trigger horizontal scrollbar - resize_window(1200, 800) + restore_window_size dropdown.find('.more-actions-toggle').click dropdown.find('.dropdown-menu li', match: :first) -- cgit v1.2.1 From f6350faca1a9680c3ab8f68a05f289c89a4a2272 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 29 May 2018 15:33:13 -0500 Subject: Allow raw `tls_options` to be passed in LDAP configuration We've previously exposed ca_file and ssl_version but there are many possible options that can be used inside tls_options. Instead of exposing individual ones, simply expose the entire hash so it can be passed in and we won't have to add things in the future. --- spec/lib/gitlab/auth/ldap/config_spec.rb | 153 ++++++++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb index d3ab599d5a0..b91a09e3137 100644 --- a/spec/lib/gitlab/auth/ldap/config_spec.rb +++ b/spec/lib/gitlab/auth/ldap/config_spec.rb @@ -5,6 +5,65 @@ describe Gitlab::Auth::LDAP::Config do let(:config) { described_class.new('ldapmain') } + def raw_cert + <<-EOS +-----BEGIN CERTIFICATE----- +MIIDZjCCAk4CCQDX+u/9fICksDANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJV +UzEMMAoGA1UECAwDRm9vMQwwCgYDVQQHDANCYXIxDDAKBgNVBAoMA0JhejEMMAoG +A1UECwwDUXV4MQ0wCwYDVQQDDARsZGFwMR8wHQYJKoZIhvcNAQkBFhBsZGFwQGV4 +YW1wbGUuY29tMB4XDTE5MDIyNzE1NTUxNFoXDTE5MDMyOTE1NTUxNFowdTELMAkG +A1UEBhMCVVMxDDAKBgNVBAgMA0ZvbzEMMAoGA1UEBwwDQmFyMQwwCgYDVQQKDANC +YXoxDDAKBgNVBAsMA1F1eDENMAsGA1UEAwwEbGRhcDEfMB0GCSqGSIb3DQEJARYQ +bGRhcEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +APuDB/4/AUmTEmhYzN13no4Kt8hkRbLQuENRHlOeQw05/MVdoB1AWLOPzIXn4kex +GD9tHkoJl8S0QPmAAcPHn5O97e+gd0ze5dRQZl/cSd2/j5zeaMvZ1mCrPN/dOluM +94Oj+wQU4bEcOlrqIMSh0ezJw10R3IHXCQFeGtIZU57WmKcrryQX4kP7KTOgRw/t +CYp+NivQHtLbBEj1MU0l10qMS2+w8Qpqov4MdW4gx4wTgId2j1ZZ56+n6Jsc9qoI +wBWBNL4XU5a3kwhYZDOJoOvI9po33KLdT1dXS81uOFXClp3LGmKDgLTwQ1w+RmQG ++JG4EvTfDIShdcTDXEaOfCECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAJM9Btu5g +k8qDiz5TilvpyoGuI4viCwusARFAFmOB/my/cHlVvkuq4bbfV1KJoWWGJg8GcklL +cnIdxc35uYM5icr6xXQyrW0GqAO+LEXyUxVQqYETxrQ/LJ03xhBnuF7hvZJIBiky +GwUy0clJxGfaCeEM8zXwePawLgGjuUawDDQOwigysoWqoMu3VFW8zl8UPa84bow9 +Kn2QmPAkLw4EcqYSCNSSvnyzu5SM64jwLWRXFsmlqD7773oT29vTkqM1EQANFEfT +7gQomLyPqoPBoFph5oSNn6Rf31QX1Sie92EAKVnZ1XmD68hKzjv6ChCtzTv4jABg +XrDwnLkORIAF/Q== +-----END CERTIFICATE----- + EOS + end + + def raw_key + <<-EOS +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD7gwf+PwFJkxJo +WMzdd56OCrfIZEWy0LhDUR5TnkMNOfzFXaAdQFizj8yF5+JHsRg/bR5KCZfEtED5 +gAHDx5+Tve3voHdM3uXUUGZf3Endv4+c3mjL2dZgqzzf3TpbjPeDo/sEFOGxHDpa +6iDEodHsycNdEdyB1wkBXhrSGVOe1pinK68kF+JD+ykzoEcP7QmKfjYr0B7S2wRI +9TFNJddKjEtvsPEKaqL+DHVuIMeME4CHdo9WWeevp+ibHPaqCMAVgTS+F1OWt5MI +WGQziaDryPaaN9yi3U9XV0vNbjhVwpadyxpig4C08ENcPkZkBviRuBL03wyEoXXE +w1xGjnwhAgMBAAECggEAbw82GVui6uUpjLAhjm3CssAi1TcJ2+L0aq1IMe5Bd3ay +mkg0apY+VNPboQl6zuNxbJh3doPz42UhB8sxfE0Ktwd4KIb4Bxap7+2stwmkCGoN +NVy0c8d2NWuHzuZ2XXTK2vMu5Wd/HWD0l66o14sJEoEpZlB7yU216UevmjSayxjh +aBTSaYyyrf24haTaCuqwph/V73ZlMpFdSALGny0uiP/5inxciMCkMpHfX6BflSb4 +EGKsIYt9BJ0kY4GNG5bCP7971UCxp2eEJhU2fV8HuFGCOD12IqSpUqPxHxjsWpfx +T7FZ3V2kM/58Ca+5LB2y3atcPIdY0/g7/43V4VD+7QKBgQD/PO4/0cmZuuLU1LPT +C/C596kPK0JLlvvRqhbz4byRAkW/n7uQFG7TMtFNle3UmT7rk7pjtbHnByqzEd+9 +jMhBysjHOMg0+DWm7fEtSg/tJ3qLVO3nbdA4qmXYobLcLoG+PCYRLskEHHqTG/Bv +QZLbavOU6rrTqckNr1TMpNBmXwKBgQD8Q0C2YTOpwgjRUe8i6Chnc3o4x8a1i98y +9la6c7y7acWHSbEczMkNfEBrbM73rTb+bBA0Zqw+Z1gkv8bGpvGxX8kbSfJJ2YKW +9koxpLNTVNVapqBa9ImiaozV285dz9Ukx8bnMOJlTELpOl7RRV7iF0smYjfHIl3D +Yxyda/MtfwKBgHb9l/Dmw77IkqE4PFFimqqIHCe3OiP1UpavXh36midcUNoCBLYp +4HTTlyI9iG/5tYysBVQgy7xx6eUrqww6Ss3pVOsTvLp9EL4u5aYAhiZApm+4e2TO +HCmevvZcg/8EK3Zdoj2Wex5QjJBykQe9IVLrrH07ZTfySon3uGfjWkivAoGAGvqS +VC8HGHOw/7n0ilYr5Ax8mM/813OzFj80PVKdb6m7P2HJOFxKcE/Gj/aeF+0FgaZL +AV+tsirZSWzdNGesV5z35Bw/dlh11/FVNAP6TcI34y8I3VFj2uPsVf7hDjVpBTr8 +ccNPoyfJzCm69ESoBiQZnGxKrNhnELtr1wYxhr8CgYApWwf4hVrTWV1zs+pEJenh +AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK +0Ff8afd2Q/OfBeUdq9KA4JO9fNqzEwOWvv8Ryn4ZSYcAuLP7IVJKjjI6R7rYaO/G +3OWJdizbykGOi0BFDu+3dw== +-----END PRIVATE KEY----- + EOS + end + describe '.servers' do it 'returns empty array if no server information is available' do allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false) @@ -89,6 +148,42 @@ describe Gitlab::Auth::LDAP::Config do expect(config.adapter_options[:encryption]).to include({ method: :start_tls }) end + it 'transforms SSL cert and key to OpenSSL objects' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'start_tls', + 'tls_options' => { + 'cert' => raw_cert, + 'key' => raw_key + } + } + ) + + expect(config.adapter_options[:encryption][:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate) + expect(config.adapter_options[:encryption][:tls_options][:key]).to be_a(OpenSSL::PKey::RSA) + end + + it 'logs an error when an invalid key or cert are configured' do + allow(Rails.logger).to receive(:error) + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'start_tls', + 'tls_options' => { + 'cert' => 'invalid cert', + 'key' => 'invalid_key' + } + } + ) + + config.adapter_options + + expect(Rails.logger).to have_received(:error).with(/LDAP TLS Options/).twice + end + context 'when verify_certificates is enabled' do it 'sets tls_options to OpenSSL defaults' do stub_ldap_config( @@ -130,7 +225,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ca_file' => '/etc/ca.pem' + 'tls_options' => { + 'ca_file' => '/etc/ca.pem' + } } ) @@ -145,7 +242,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ca_file' => ' ' + 'tls_options' => { + 'ca_file' => ' ' + } } ) @@ -160,7 +259,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ssl_version' => 'TLSv1_2' + 'tls_options' => { + 'ssl_version' => 'TLSv1_2' + } } ) @@ -175,7 +276,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ssl_version' => ' ' + 'tls_options' => { + 'ssl_version' => ' ' + } } ) @@ -223,6 +326,23 @@ describe Gitlab::Auth::LDAP::Config do ) end + it 'transforms SSL cert and key to OpenSSL objects' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'start_tls', + 'tls_options' => { + 'cert' => raw_cert, + 'key' => raw_key + } + } + ) + + expect(config.omniauth_options[:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate) + expect(config.omniauth_options[:tls_options][:key]).to be_a(OpenSSL::PKey::RSA) + end + context 'when verify_certificates is enabled' do it 'specifies disable_verify_certificates as false' do stub_ldap_config( @@ -261,11 +381,13 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ca_file' => '/etc/ca.pem' + 'tls_options' => { + 'ca_file' => '/etc/ca.pem' + } } ) - expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' }) + expect(config.omniauth_options[:tls_options]).to include({ ca_file: '/etc/ca.pem' }) end end @@ -277,11 +399,13 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ca_file' => ' ' + 'tls_options' => { + 'ca_file' => ' ' + } } ) - expect(config.omniauth_options).not_to have_key(:ca_file) + expect(config.omniauth_options[:tls_options]).not_to have_key(:ca_file) end end @@ -293,11 +417,13 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ssl_version' => 'TLSv1_2' + 'tls_options' => { + 'ssl_version' => 'TLSv1_2' + } } ) - expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' }) + expect(config.omniauth_options[:tls_options]).to include({ ssl_version: 'TLSv1_2' }) end end @@ -309,11 +435,14 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ssl_version' => ' ' + 'tls_options' => { + 'ssl_version' => ' ' + } } ) - expect(config.omniauth_options).not_to have_key(:ssl_version) + # OpenSSL default params includes `ssl_version` so we just check that it's not blank + expect(config.omniauth_options[:tls_options]).not_to include({ ssl_version: ' ' }) end end end -- cgit v1.2.1 From f0c52df5e540e825be0babd04cc557f3f40cf1c6 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 28 Feb 2019 23:25:37 +0000 Subject: sidekiq: terminate child processes at shutdown Sidekiq jobs frequently spawn long-lived child processes to do work. In some circumstances, these can be reparented to init when sidekiq is terminated, leading to duplication of work and strange concurrency problems. This commit changes sidekiq so that, if run as a process group leader, it will forward `INT` and `TERM` signals to the whole process group. If the memory killer is active, it will also use the process group when resorting to `kill -9` to shut down. These changes mean that a naive `kill ` will now do the right thing, killing any child processes spawned by sidekiq, as long as the process supervisor placed it in its own process group. If sidekiq isn't a process group leader, this new code is skipped. --- .../sidekiq_middleware/memory_killer_spec.rb | 13 +++- spec/lib/gitlab/sidekiq_signals_spec.rb | 69 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 spec/lib/gitlab/sidekiq_signals_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb index 5df56178df2..ff8c0825ee4 100644 --- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb @@ -35,7 +35,7 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do stub_const("#{described_class}::MAX_RSS", 5.kilobytes) end - it 'sends the STP, TERM and KILL signals at expected times' do + it 'sends the TSTP, TERM and KILL signals at expected times' do expect(subject).to receive(:sleep).with(15 * 60).ordered expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered @@ -47,6 +47,17 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do run end + + it 'sends TSTP and TERM to the pid, but KILL to the pgroup, when running as process leader' do + allow(Process).to receive(:getpgrp) { pid } + allow(subject).to receive(:sleep) + + expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered + expect(Process).to receive(:kill).with('SIGTERM', pid).ordered + expect(Process).to receive(:kill).with('SIGKILL', "-#{pid}").ordered + + run + end end context 'when MAX_RSS is not exceeded' do diff --git a/spec/lib/gitlab/sidekiq_signals_spec.rb b/spec/lib/gitlab/sidekiq_signals_spec.rb new file mode 100644 index 00000000000..4483224f49d --- /dev/null +++ b/spec/lib/gitlab/sidekiq_signals_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Gitlab::SidekiqSignals do + describe '.install' do + let(:result) { Hash.new { |h, k| h[k] = 0 } } + let(:int_handler) { -> (_) { result['INT'] += 1 } } + let(:term_handler) { -> (_) { result['TERM'] += 1 } } + let(:other_handler) { -> (_) { result['OTHER'] += 1 } } + let(:signals) { { 'INT' => int_handler, 'TERM' => term_handler, 'OTHER' => other_handler } } + + context 'not a process group leader' do + before do + allow(Process).to receive(:getpgrp) { Process.pid * 2 } + end + + it 'does nothing' do + expect { described_class.install!(signals) } + .not_to change { signals } + end + end + + context 'as a process group leader' do + before do + allow(Process).to receive(:getpgrp) { Process.pid } + end + + it 'installs its own signal handlers for TERM and INT only' do + described_class.install!(signals) + + expect(signals['INT']).not_to eq(int_handler) + expect(signals['TERM']).not_to eq(term_handler) + expect(signals['OTHER']).to eq(other_handler) + end + + %w[INT TERM].each do |signal| + it "installs a forwarding signal handler for #{signal}" do + described_class.install!(signals) + + expect(described_class) + .to receive(:trap) + .with(signal, 'IGNORE') + .and_return(:original_trap) + .ordered + + expect(Process) + .to receive(:kill) + .with(signal, "-#{Process.pid}") + .ordered + + expect(described_class) + .to receive(:trap) + .with(signal, :original_trap) + .ordered + + signals[signal].call(:cli) + + expect(result[signal]).to eq(1) + end + + it "raises if sidekiq no longer traps SIG#{signal}" do + signals.delete(signal) + + expect { described_class.install!(signals) } + .to raise_error(/Sidekiq should have registered/) + end + end + end + end +end -- cgit v1.2.1 From a1b0b3e4ebb61e952b6e60e7be05bd96286bc298 Mon Sep 17 00:00:00 2001 From: Denys Mishunov Date: Fri, 1 Mar 2019 17:33:18 +0100 Subject: Extended Web IDE API to get MRs for a certain branch in a project New `Api.projectMergeRequests` allows: - to query for all MRs on a project without specifying `mergeRequestId` - to filter the returned MRs using parameters from https://bit.ly/2H7We4V The new API request is used for fetching information about MRs associated with a particular branch in Web IDE to have IdeSidebar behave consistently in both scenarios: - getting to a branch from and MR (where we already have info about relevant MR), or - getting to a branch somehow differently directly For cases where there are several merge requests that the current branch is associated with, mark the most recent one as 'current' Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/49663 --- spec/javascripts/api_spec.js | 34 ++++++++ .../ide/stores/actions/merge_request_spec.js | 93 ++++++++++++++++++++++ .../javascripts/ide/stores/actions/project_spec.js | 1 + 3 files changed, 128 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 1e9470970ff..e537e0e8afc 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -139,6 +139,40 @@ describe('Api', () => { }); }); + describe('projectMergeRequests', () => { + const projectPath = 'abc'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`; + + it('fetches all merge requests for a project', done => { + const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }]; + mock.onGet(expectedUrl).reply(200, mockData); + Api.projectMergeRequests(projectPath) + .then(({ data }) => { + expect(data.length).toEqual(2); + expect(data[0].source_branch).toBe('foo'); + expect(data[1].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + + it('fetches merge requests filtered with passed params', done => { + const params = { + source_branch: 'bar', + }; + const mockData = [{ source_branch: 'bar' }]; + mock.onGet(expectedUrl, { params }).reply(200, mockData); + + Api.projectMergeRequests(projectPath, params) + .then(({ data }) => { + expect(data.length).toEqual(1); + expect(data[0].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('projectMergeRequest', () => { it('fetches a merge request', done => { const projectPath = 'abc'; diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js index 9bfc7c397b8..a5839630657 100644 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js @@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; import actions, { + getMergeRequestsForBranch, getMergeRequestData, getMergeRequestChanges, getMergeRequestVersions, @@ -27,6 +28,98 @@ describe('IDE store merge request actions', () => { resetStore(store); }); + describe('getMergeRequestsForBranch', () => { + describe('success', () => { + const mrData = { iid: 2, source_branch: 'bar' }; + const mockData = [mrData]; + + describe('base case', () => { + beforeEach(() => { + spyOn(service, 'getProjectMergeRequests').and.callThrough(); + mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, mockData); + }); + + it('calls getProjectMergeRequests service method', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + expect(service.getProjectMergeRequests).toHaveBeenCalledWith('abcproject', { + source_branch: 'bar', + order_by: 'created_at', + per_page: 1, + }); + + done(); + }) + .catch(done.fail); + }); + + it('sets the "Merge Request" Object', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(1); + expect(Object.keys(store.state.projects.abcproject.mergeRequests)[0]).toEqual('2'); + expect(store.state.projects.abcproject.mergeRequests[2]).toEqual( + jasmine.objectContaining(mrData), + ); + done(); + }) + .catch(done.fail); + }); + + it('sets "Current Merge Request" object to the most recent MR', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + expect(store.state.currentMergeRequestId).toEqual('2'); + done(); + }) + .catch(done.fail); + }); + }); + + describe('no merge requests for branch available case', () => { + beforeEach(() => { + spyOn(service, 'getProjectMergeRequests').and.callThrough(); + mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, []); + }); + + it('does not fail if there are no merge requests for current branch', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'foo' }) + .then(() => { + expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(0); + expect(store.state.currentMergeRequestId).toEqual(''); + done(); + }) + .catch(done.fail); + }); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).networkError(); + }); + + it('flashes message, if error', done => { + const flashSpy = spyOnDependency(actions, 'flash'); + + getMergeRequestsForBranch({ commit() {} }, { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + fail('Expected getMergeRequestsForBranch to throw an error'); + }) + .catch(() => { + expect(flashSpy).toHaveBeenCalled(); + expect(flashSpy.calls.argsFor(0)[0]).toEqual('Error fetching merge requests for bar'); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + describe('getMergeRequestData', () => { describe('success', () => { beforeEach(() => { diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 7d8c9edd965..7b0963713fb 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -249,6 +249,7 @@ describe('IDE store project actions', () => { ['setCurrentBranchId', branch.branchId], ['getBranchData', branch], ['getFiles', branch], + ['getMergeRequestsForBranch', branch], ]); }) .then(done) -- cgit v1.2.1 From ebf16ada856efb85424a98848c141f21e609886a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Mon, 4 Mar 2019 18:36:34 +0000 Subject: Arbitrary file read via MergeRequestDiff --- .../merge_request/user_sees_versions_spec.rb | 6 +++- .../import_export/merge_request_parser_spec.rb | 16 +++++++++ spec/models/merge_request_diff_spec.rb | 14 +++++++- spec/validators/sha_validator_spec.rb | 40 ++++++++++++++++++++++ ..._head_pipeline_for_merge_request_worker_spec.rb | 2 +- 5 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 spec/validators/sha_validator_spec.rb (limited to 'spec') diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index aa91ade46ca..5c45e363997 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -1,7 +1,11 @@ require 'rails_helper' describe 'Merge request > User sees versions', :js do - let(:merge_request) { create(:merge_request, importing: true) } + let(:merge_request) do + create(:merge_request).tap do |mr| + mr.merge_request_diff.destroy + end + end let(:project) { merge_request.source_project } let(:user) { project.creator } let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb index 68eaa70e6b6..4b234411a44 100644 --- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb +++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb @@ -41,4 +41,20 @@ describe Gitlab::ImportExport::MergeRequestParser do expect(parsed_merge_request).to eq(merge_request) end + + context 'when the merge request has diffs' do + let(:merge_request) do + build(:merge_request, source_project: forked_project, target_project: project) + end + + context 'when the diff is invalid' do + let(:merge_request_diff) { build(:merge_request_diff, merge_request: merge_request, base_commit_sha: 'foobar') } + + it 'sets the diff to nil' do + expect(merge_request_diff).to be_invalid + expect(merge_request_diff.merge_request).to eq merge_request + expect(parsed_merge_request.merge_request_diff).to be_nil + end + end + end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 1849d3bac12..e530e0302f5 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -3,6 +3,18 @@ require 'spec_helper' describe MergeRequestDiff do let(:diff_with_commits) { create(:merge_request).merge_request_diff } + describe 'validations' do + subject { diff_with_commits } + + it 'checks sha format of base_commit_sha, head_commit_sha and start_commit_sha' do + subject.base_commit_sha = subject.head_commit_sha = subject.start_commit_sha = 'foobar' + + expect(subject.valid?).to be false + expect(subject.errors.count).to eq 3 + expect(subject.errors).to all(include('is not a valid SHA')) + end + end + describe 'create new record' do subject { diff_with_commits } @@ -78,7 +90,7 @@ describe MergeRequestDiff do it 'returns persisted diffs if cannot compare with diff refs' do expect(diff).to receive(:load_diffs).and_call_original - diff.update!(head_commit_sha: 'invalid-sha') + diff.update!(head_commit_sha: Digest::SHA1.hexdigest(SecureRandom.hex)) diff.diffs.diff_files end diff --git a/spec/validators/sha_validator_spec.rb b/spec/validators/sha_validator_spec.rb new file mode 100644 index 00000000000..b9242ef931e --- /dev/null +++ b/spec/validators/sha_validator_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe ShaValidator do + let(:validator) { described_class.new(attributes: [:base_commit_sha]) } + let(:merge_diff) { build(:merge_request_diff) } + + subject { validator.validate_each(merge_diff, :base_commit_sha, value) } + + context 'with empty value' do + let(:value) { nil } + + it 'does not add any error if value is empty' do + subject + + expect(merge_diff.errors).to be_empty + end + end + + context 'with valid sha' do + let(:value) { Digest::SHA1.hexdigest(SecureRandom.hex) } + + it 'does not add any error if value is empty' do + subject + + expect(merge_diff.errors).to be_empty + end + end + + context 'with invalid sha' do + let(:value) { 'foo' } + + it 'adds error to the record' do + expect(merge_diff.errors).to be_empty + + subject + + expect(merge_diff.errors).not_to be_empty + end + end +end diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb index 963237ceadf..f29e49f202a 100644 --- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb +++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb @@ -18,7 +18,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do context 'when merge request sha does not equal pipeline sha' do before do - merge_request.merge_request_diff.update(head_commit_sha: 'different_sha') + merge_request.merge_request_diff.update(head_commit_sha: Digest::SHA1.hexdigest(SecureRandom.hex)) end it 'does not update head pipeline' do -- cgit v1.2.1 From 50591efc75e465c1b626c1bb2d4d65447ee9a726 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Mon, 4 Mar 2019 18:36:43 +0000 Subject: Check validity of prometheus_service before query Check validity before querying so that if the dns entry for the api_url has been changed to something invalid after the model was saved and checked for validity, it will not query. This is to solve a toctou (time of check to time of use) issue. --- .../project_services/prometheus_service_spec.rb | 61 +++++++++++++++------- 1 file changed, 43 insertions(+), 18 deletions(-) (limited to 'spec') diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index b6cf4c72450..e9c7c94ad70 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -33,18 +33,38 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do describe 'Validations' do context 'when manual_configuration is enabled' do before do - subject.manual_configuration = true + service.manual_configuration = true end - it { is_expected.to validate_presence_of(:api_url) } + it 'validates presence of api_url' do + expect(service).to validate_presence_of(:api_url) + end end context 'when manual configuration is disabled' do before do - subject.manual_configuration = false + service.manual_configuration = false end - it { is_expected.not_to validate_presence_of(:api_url) } + it 'does not validate presence of api_url' do + expect(service).not_to validate_presence_of(:api_url) + end + end + + context 'when the api_url domain points to localhost or local network' do + let(:domain) { Addressable::URI.parse(service.api_url).hostname } + + it 'cannot query' do + expect(service.can_query?).to be true + + aggregate_failures do + ['127.0.0.1', '192.168.2.3'].each do |url| + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) + + expect(service.can_query?).to be false + end + end + end end end @@ -74,30 +94,35 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end describe '#prometheus_client' do + let(:api_url) { 'http://some_url' } + + before do + service.active = true + service.api_url = api_url + service.manual_configuration = manual_configuration + end + context 'manual configuration is enabled' do - let(:api_url) { 'http://some_url' } + let(:manual_configuration) { true } - before do - subject.active = true - subject.manual_configuration = true - subject.api_url = api_url + it 'returns rest client from api_url' do + expect(service.prometheus_client.url).to eq(api_url) end - it 'returns rest client from api_url' do - expect(subject.prometheus_client.url).to eq(api_url) + it 'calls valid?' do + allow(service).to receive(:valid?).and_call_original + + expect(service.prometheus_client).not_to be_nil + + expect(service).to have_received(:valid?) end end context 'manual configuration is disabled' do - let(:api_url) { 'http://some_url' } - - before do - subject.manual_configuration = false - subject.api_url = api_url - end + let(:manual_configuration) { false } it 'no client provided' do - expect(subject.prometheus_client).to be_nil + expect(service.prometheus_client).to be_nil end end end -- cgit v1.2.1 From 5169dafccf70bf50d780bfdbeea0802f094520cc Mon Sep 17 00:00:00 2001 From: Igor Drozdov Date: Mon, 4 Mar 2019 18:37:08 +0000 Subject: Forbid creating discussions for users with restricted access --- spec/policies/commit_policy_spec.rb | 53 ++++++++++++ spec/policies/note_policy_spec.rb | 94 +++++++++++----------- .../shared_examples/requests/api/discussions.rb | 31 +++++++ 3 files changed, 129 insertions(+), 49 deletions(-) create mode 100644 spec/policies/commit_policy_spec.rb (limited to 'spec') diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb new file mode 100644 index 00000000000..2259693cf01 --- /dev/null +++ b/spec/policies/commit_policy_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe CommitPolicy do + describe '#rules' do + let(:user) { create(:user) } + let(:commit) { project.repository.head_commit } + let(:policy) { described_class.new(user, commit) } + + context 'when project is public' do + let(:project) { create(:project, :public, :repository) } + + it 'can read commit and create a note' do + expect(policy).to be_allowed(:read_commit) + end + + context 'when repository access level is private' do + let(:project) { create(:project, :public, :repository, :repository_private) } + + it 'can not read commit and create a note' do + expect(policy).to be_disallowed(:read_commit) + end + + context 'when the user is a project member' do + before do + project.add_developer(user) + end + + it 'can read commit and create a note' do + expect(policy).to be_allowed(:read_commit) + end + end + end + end + + context 'when project is private' do + let(:project) { create(:project, :private, :repository) } + + it 'can not read commit and create a note' do + expect(policy).to be_disallowed(:read_commit) + end + + context 'when the user is a project member' do + before do + project.add_developer(user) + end + + it 'can read commit and create a note' do + expect(policy).to be_allowed(:read_commit) + end + end + end + end +end diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb index 0e848c74659..4be7a0266d1 100644 --- a/spec/policies/note_policy_spec.rb +++ b/spec/policies/note_policy_spec.rb @@ -1,28 +1,15 @@ require 'spec_helper' -describe NotePolicy, mdoels: true do +describe NotePolicy do describe '#rules' do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } - - def policies(noteable = nil) - return @policies if @policies - - noteable ||= issue - note = if noteable.is_a?(Commit) - create(:note_on_commit, commit_id: noteable.id, author: user, project: project) - else - create(:note, noteable: noteable, author: user, project: project) - end - - @policies = described_class.new(user, note) - end + let(:noteable) { issue } + let(:policy) { described_class.new(user, note) } + let(:note) { create(:note, noteable: noteable, author: user, project: project) } shared_examples_for 'a discussion with a private noteable' do - let(:noteable) { issue } - let(:policy) { policies(noteable) } - context 'when the note author can no longer see the noteable' do it 'can not edit nor read the note' do expect(policy).to be_disallowed(:admin_note) @@ -46,12 +33,21 @@ describe NotePolicy, mdoels: true do end end - context 'when the project is private' do - let(:project) { create(:project, :private, :repository) } + context 'when the noteable is a commit' do + let(:commit) { project.repository.head_commit } + let(:note) { create(:note_on_commit, commit_id: commit.id, author: user, project: project) } + + context 'when the project is private' do + let(:project) { create(:project, :private, :repository) } + + it_behaves_like 'a discussion with a private noteable' + end - context 'when the noteable is a commit' do - it_behaves_like 'a discussion with a private noteable' do - let(:noteable) { project.repository.head_commit } + context 'when the project is public' do + context 'when repository access level is private' do + let(:project) { create(:project, :public, :repository, :repository_private) } + + it_behaves_like 'a discussion with a private noteable' end end end @@ -59,44 +55,44 @@ describe NotePolicy, mdoels: true do context 'when the project is public' do context 'when the note author is not a project member' do it 'can edit a note' do - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end end context 'when the noteable is a project snippet' do - it 'can edit note' do - policies = policies(create(:project_snippet, :public, project: project)) + let(:noteable) { create(:project_snippet, :public, project: project) } - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + it 'can edit note' do + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end context 'when it is private' do - it_behaves_like 'a discussion with a private noteable' do - let(:noteable) { create(:project_snippet, :private, project: project) } - end + let(:noteable) { create(:project_snippet, :private, project: project) } + + it_behaves_like 'a discussion with a private noteable' end end context 'when the noteable is a personal snippet' do - it 'can edit note' do - policies = policies(create(:personal_snippet, :public)) + let(:noteable) { create(:personal_snippet, :public) } - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + it 'can edit note' do + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end context 'when it is private' do - it 'can not edit nor read the note' do - policies = policies(create(:personal_snippet, :private)) + let(:noteable) { create(:personal_snippet, :private) } - expect(policies).to be_disallowed(:admin_note) - expect(policies).to be_disallowed(:resolve_note) - expect(policies).to be_disallowed(:read_note) + it 'can not edit nor read the note' do + expect(policy).to be_disallowed(:admin_note) + expect(policy).to be_disallowed(:resolve_note) + expect(policy).to be_disallowed(:read_note) end end end @@ -120,20 +116,20 @@ describe NotePolicy, mdoels: true do end it 'can edit a note' do - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end end context 'when the note author is not a project member' do it 'can not edit a note' do - expect(policies).to be_disallowed(:admin_note) - expect(policies).to be_disallowed(:resolve_note) + expect(policy).to be_disallowed(:admin_note) + expect(policy).to be_disallowed(:resolve_note) end it 'can read a note' do - expect(policies).to be_allowed(:read_note) + expect(policy).to be_allowed(:read_note) end end end diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb index e44da4faa5a..eff8e401bad 100644 --- a/spec/support/shared_examples/requests/api/discussions.rb +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -86,6 +86,37 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| expect(response).to have_gitlab_http_status(404) end end + + context 'when a project is public with private repo access' do + let!(:parent) { create(:project, :public, :repository, :repository_private, :snippets_private) } + let!(:user_without_access) { create(:user) } + + context 'when user is not a team member of private repo' do + before do + project.team.truncate + end + + context "creating a new note" do + before do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user_without_access), params: { body: 'hi!' } + end + + it 'raises 404 error' do + expect(response).to have_gitlab_http_status(404) + end + end + + context "fetching a discussion" do + before do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{note.discussion_id}", user_without_access) + end + + it 'raises 404 error' do + expect(response).to have_gitlab_http_status(404) + end + end + end + end end describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do -- cgit v1.2.1 From 9d294467bda78c358c868bd2ba3e39c01aea44f8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 1 Mar 2019 14:21:16 -0800 Subject: Bring back Rugged implementation of CommitIsAncestor This brings back some of the changes in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20095. For users using Gitaly on top of NFS, accessing the Git data directly via Rugged is more performant than Gitaly. This merge request introduces the feature flag `rugged_commit_is_ancestor` to activate the Rugged path. Part of four Rugged changes identified in https://gitlab.com/gitlab-org/gitlab-ce/issues/57317. --- spec/models/repository_spec.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 17201d8b90a..70630467d24 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2237,7 +2237,7 @@ describe Repository do rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id) end - describe '#ancestor?' do + shared_examples '#ancestor?' do let(:commit) { repository.commit } let(:ancestor) { commit.parents.first } @@ -2261,6 +2261,20 @@ describe Repository do end end + describe '#ancestor? with Gitaly enabled' do + it_behaves_like "#ancestor?" + end + + describe '#ancestor? with Rugged enabled', :enable_rugged do + it 'calls out to the Rugged implementation' do + allow_any_instance_of(Rugged).to receive(:merge_base).with(repository.commit.id, Gitlab::Git::BLANK_SHA).and_call_original + + repository.ancestor?(repository.commit.id, Gitlab::Git::BLANK_SHA) + end + + it_behaves_like '#ancestor?' + end + describe '#archive_metadata' do let(:ref) { 'master' } let(:storage_path) { '/tmp' } -- cgit v1.2.1 From 2b0a0f9b03d8411c0b6e312e9399fe74cfa5dfea Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Mon, 4 Mar 2019 21:07:36 +0000 Subject: Merge branch 'master' into expose-additional-merge-request-pipeline-variables Conflicts: doc/ci/variables/README.md spec/models/ci/pipeline_spec.rb --- spec/models/ci/pipeline_spec.rb | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 462793b259b..3a6de57bd63 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -513,9 +513,16 @@ describe Ci::Pipeline, :mailer do source_project: project, source_branch: 'feature', target_project: project, - target_branch: 'master') + target_branch: 'master', + assignee: assignee, + milestone: milestone, + labels: labels) end + let(:assignee) { create(:user) } + let(:milestone) { create(:milestone) } + let(:labels) { create_list(:label, 2) } + it 'exposes merge request pipeline variables' do expect(subject.to_hash) .to include( @@ -531,7 +538,11 @@ describe Ci::Pipeline, :mailer do 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path, 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url, 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s) + 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s, + 'CI_MERGE_REQUEST_TITLE' => merge_request.title, + 'CI_MERGE_REQUEST_ASSIGNEES' => assignee.username, + 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, + 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(',')) end context 'when source project does not exist' do @@ -547,6 +558,30 @@ describe Ci::Pipeline, :mailer do CI_MERGE_REQUEST_SOURCE_BRANCH_NAME]) end end + + context 'without assignee' do + let(:assignee) { nil } + + it 'does not expose assignee variable' do + expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES') + end + end + + context 'without milestone' do + let(:milestone) { nil } + + it 'does not expose milestone variable' do + expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE') + end + end + + context 'without labels' do + let(:labels) { [] } + + it 'does not expose labels variable' do + expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS') + end + end end end -- cgit v1.2.1 From be99adb1498420be74d05d3e328632b24b5725a6 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 14 Dec 2018 10:38:03 +1300 Subject: Show pg_stat_activity before each test run Maybe this will help us debug https://gitlab.com/gitlab-org/gitlab-ee/issues/10154 --- spec/support/pg_stat_activity.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 spec/support/pg_stat_activity.rb (limited to 'spec') diff --git a/spec/support/pg_stat_activity.rb b/spec/support/pg_stat_activity.rb new file mode 100644 index 00000000000..f93fba08a19 --- /dev/null +++ b/spec/support/pg_stat_activity.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.before do + if Gitlab::Database.postgresql? && ENV['PG_STAT_WARNING_THRESHOLD'] + warning_threshold = ENV['PG_STAT_WARNING_THRESHOLD'].to_i + results = ActiveRecord::Base.connection.execute('SELECT * FROM pg_stat_activity') + ntuples = results.ntuples + + warn("pg_stat_activity count: #{ntuples}") + + if ntuples > warning_threshold + results.each do |result| + warn result.inspect + end + end + end + end +end -- cgit v1.2.1 From b4f205020797319f06c52f769f385876e6427309 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 5 Mar 2019 03:59:17 +0100 Subject: Skip project validation when switching storage layouts This is a fix for the Hashed Storage migration and Rollback procedure to ignore any project-level validation error that can happen in a long-running instance. There are many situations where defaults and acceptable values changed but, because we didn't provide a migration to "valid" attributes, it can happen that project will not be `valid? => true`. Because the changes we are making are limited to setting a project as read_only or changing the storage_level, it's safe to bypass validation. --- .../projects/hashed_storage/migrate_attachments_service_spec.rb | 6 ++++++ .../projects/hashed_storage/migrate_repository_service_spec.rb | 6 ++++++ .../projects/hashed_storage/rollback_attachments_service_spec.rb | 6 ++++++ .../projects/hashed_storage/rollback_repository_service_spec.rb | 6 ++++++ 4 files changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index 639dd930618..efe15139717 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -76,6 +76,12 @@ describe Projects::HashedStorage::MigrateAttachmentsService do expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError) end end + + it 'works even when project validation fails' do + allow(project).to receive(:valid?) { false } + + expect { service.execute }.to change { project.hashed_storage?(:attachments) }.to(true) + end end context '#old_disk_path' do diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index e77e2198439..42b0d256cbf 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -102,6 +102,12 @@ describe Projects::HashedStorage::MigrateRepositoryService do end end + it 'works even when project validation fails' do + allow(project).to receive(:valid?) { false } + + expect { service.execute }.to change { project.hashed_storage?(:repository) }.to(true) + end + def expect_move_repository(from_name, to_name) expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage, from_name, to_name).and_call_original end diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb index 6f4154d6011..815c85e0866 100644 --- a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb @@ -78,6 +78,12 @@ describe Projects::HashedStorage::RollbackAttachmentsService do expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError) end end + + it 'works even when project validation fails' do + allow(project).to receive(:valid?) { false } + + expect { service.execute }.to change { project.hashed_storage?(:attachments) }.to(false) + end end context '#old_disk_path' do diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb index 41927934501..bd4354a7df3 100644 --- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb @@ -104,6 +104,12 @@ describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis end end + it 'works even when project validation fails' do + allow(project).to receive(:valid?) { false } + + expect { service.execute }.to change { project.legacy_storage? }.to(true) + end + def expect_move_repository(from_name, to_name) expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage, from_name, to_name).and_call_original end -- cgit v1.2.1 From 18889696bda82d6ec50f97ca06edc97ff49d8fb4 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 1 Mar 2019 14:10:07 +0900 Subject: Rename ambiguous association names in Ci::Pipeline fix it ok Fix head pipeline --- spec/lib/gitlab/import_export/all_models.yml | 2 +- spec/models/ci/pipeline_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 018a5d3dd3d..01da3ea7081 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -127,7 +127,7 @@ ci_pipelines: - scheduled_actions - artifacts - pipeline_schedule -- merge_requests +- merge_requests_as_head_pipeline - merge_request - deployments - environments diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 3a6de57bd63..332278ce077 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2252,7 +2252,7 @@ describe Ci::Pipeline, :mailer do end end - describe "#merge_requests" do + describe "#merge_requests_as_head_pipeline" do let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') } @@ -2260,20 +2260,20 @@ describe Ci::Pipeline, :mailer do allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' } merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref) - expect(pipeline.merge_requests).to eq([merge_request]) + expect(pipeline.merge_requests_as_head_pipeline).to eq([merge_request]) end it "doesn't return merge requests whose source branch doesn't match the pipeline's ref" do create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') - expect(pipeline.merge_requests).to be_empty + expect(pipeline.merge_requests_as_head_pipeline).to be_empty end it "doesn't return merge requests whose `diff_head_sha` doesn't match the pipeline's SHA" do create(:merge_request, source_project: project, source_branch: pipeline.ref) allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { '97de212e80737a608d939f648d959671fb0a0142b' } - expect(pipeline.merge_requests).to be_empty + expect(pipeline.merge_requests_as_head_pipeline).to be_empty end end -- cgit v1.2.1 From cf1b85dd726c1947f9ff2af8d89aa240747f462d Mon Sep 17 00:00:00 2001 From: jerasmus Date: Tue, 19 Feb 2019 11:24:37 +0200 Subject: Add ability to edit Knative domain Added the functionality to edit the Knative domain --- .../clusters/components/application_row_spec.js | 10 +- .../clusters/components/applications_spec.js | 169 +++++++++++---------- spec/javascripts/clusters/services/mock_data.js | 12 +- .../clusters/stores/clusters_store_spec.js | 1 + 4 files changed, 108 insertions(+), 84 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js index 8cb9713964e..a2dd4e93daf 100644 --- a/spec/javascripts/clusters/components/application_row_spec.js +++ b/spec/javascripts/clusters/components/application_row_spec.js @@ -230,7 +230,7 @@ describe('Application Row', () => { expect(upgradeBtn.innerHTML).toContain('Upgrade'); }); - it('has enabled "Retry upgrade" when APPLICATION_STATUS.UPDATE_ERRORED', () => { + it('has enabled "Retry update" when APPLICATION_STATUS.UPDATE_ERRORED', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATE_ERRORED, @@ -239,10 +239,10 @@ describe('Application Row', () => { expect(upgradeBtn).not.toBe(null); expect(vm.upgradeFailed).toBe(true); - expect(upgradeBtn.innerHTML).toContain('Retry upgrade'); + expect(upgradeBtn.innerHTML).toContain('Retry update'); }); - it('has disabled "Retry upgrade" when APPLICATION_STATUS.UPDATING', () => { + it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATING, @@ -251,7 +251,7 @@ describe('Application Row', () => { expect(upgradeBtn).not.toBe(null); expect(vm.isUpgrading).toBe(true); - expect(upgradeBtn.innerHTML).toContain('Upgrading'); + expect(upgradeBtn.innerHTML).toContain('Updating'); }); it('clicking upgrade button emits event', () => { @@ -295,7 +295,7 @@ describe('Application Row', () => { expect(failureMessage).not.toBe(null); expect(failureMessage.innerHTML).toContain( - 'Something went wrong when upgrading GitLab Runner. Please check the logs and try again.', + 'Update failed. Please check the logs and try again.', ); }); }); diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js index 14ef1193984..8daf0282184 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/javascripts/clusters/components/applications_spec.js @@ -1,7 +1,9 @@ import Vue from 'vue'; import applications from '~/clusters/components/applications.vue'; import { CLUSTER_TYPE } from '~/clusters/constants'; +import eventHub from '~/clusters/event_hub'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { APPLICATIONS_MOCK_STATE } from '../services/mock_data'; describe('Applications', () => { let vm; @@ -18,16 +20,8 @@ describe('Applications', () => { describe('Project cluster applications', () => { beforeEach(() => { vm = mountComponent(Applications, { + applications: APPLICATIONS_MOCK_STATE, type: CLUSTER_TYPE.PROJECT, - applications: { - helm: { title: 'Helm Tiller' }, - ingress: { title: 'Ingress' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub' }, - knative: { title: 'Knative' }, - }, }); }); @@ -64,15 +58,7 @@ describe('Applications', () => { beforeEach(() => { vm = mountComponent(Applications, { type: CLUSTER_TYPE.GROUP, - applications: { - helm: { title: 'Helm Tiller' }, - ingress: { title: 'Ingress' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub' }, - knative: { title: 'Knative' }, - }, + applications: APPLICATIONS_MOCK_STATE, }); }); @@ -111,17 +97,12 @@ describe('Applications', () => { it('renders ip address with a clipboard button', () => { vm = mountComponent(Applications, { applications: { + ...APPLICATIONS_MOCK_STATE, ingress: { title: 'Ingress', status: 'installed', externalIp: '0.0.0.0', }, - helm: { title: 'Helm Tiller' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '' }, - knative: { title: 'Knative', hostname: '' }, }, }); @@ -137,16 +118,11 @@ describe('Applications', () => { it('renders an input text with a question mark and an alert text', () => { vm = mountComponent(Applications, { applications: { + ...APPLICATIONS_MOCK_STATE, ingress: { title: 'Ingress', status: 'installed', }, - helm: { title: 'Helm Tiller' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '' }, - knative: { title: 'Knative', hostname: '' }, }, }); @@ -160,15 +136,7 @@ describe('Applications', () => { describe('before installing', () => { it('does not render the IP address', () => { vm = mountComponent(Applications, { - applications: { - helm: { title: 'Helm Tiller' }, - ingress: { title: 'Ingress' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '' }, - knative: { title: 'Knative', hostname: '' }, - }, + applications: APPLICATIONS_MOCK_STATE, }); expect(vm.$el.textContent).not.toContain('Ingress IP Address'); @@ -181,17 +149,12 @@ describe('Applications', () => { it('renders email & allows editing', () => { vm = mountComponent(Applications, { applications: { - helm: { title: 'Helm Tiller', status: 'installed' }, - ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + ...APPLICATIONS_MOCK_STATE, cert_manager: { title: 'Cert-Manager', email: 'before@example.com', status: 'installable', }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, - knative: { title: 'Knative', hostname: '', status: 'installable' }, }, }); @@ -204,17 +167,12 @@ describe('Applications', () => { it('renders email in readonly', () => { vm = mountComponent(Applications, { applications: { - helm: { title: 'Helm Tiller', status: 'installed' }, - ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + ...APPLICATIONS_MOCK_STATE, cert_manager: { title: 'Cert-Manager', email: 'after@example.com', status: 'installed', }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, - knative: { title: 'Knative', hostname: '', status: 'installable' }, }, }); @@ -229,13 +187,12 @@ describe('Applications', () => { it('renders hostname active input', () => { vm = mountComponent(Applications, { applications: { - helm: { title: 'Helm Tiller', status: 'installed' }, - ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, - knative: { title: 'Knative', hostname: '', status: 'installable' }, + ...APPLICATIONS_MOCK_STATE, + ingress: { + title: 'Ingress', + status: 'installed', + externalIp: '1.1.1.1', + }, }, }); @@ -247,13 +204,8 @@ describe('Applications', () => { it('does not render hostname input', () => { vm = mountComponent(Applications, { applications: { - helm: { title: 'Helm Tiller', status: 'installed' }, + ...APPLICATIONS_MOCK_STATE, ingress: { title: 'Ingress', status: 'installed' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, - knative: { title: 'Knative', hostname: '', status: 'installable' }, }, }); @@ -265,13 +217,9 @@ describe('Applications', () => { it('renders readonly input', () => { vm = mountComponent(Applications, { applications: { - helm: { title: 'Helm Tiller', status: 'installed' }, + ...APPLICATIONS_MOCK_STATE, ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' }, - knative: { title: 'Knative', status: 'installed', hostname: '' }, }, }); @@ -282,15 +230,7 @@ describe('Applications', () => { describe('without ingress installed', () => { beforeEach(() => { vm = mountComponent(Applications, { - applications: { - helm: { title: 'Helm Tiller' }, - ingress: { title: 'Ingress' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', status: 'not_installable' }, - knative: { title: 'Knative' }, - }, + applications: APPLICATIONS_MOCK_STATE, }); }); @@ -310,4 +250,77 @@ describe('Applications', () => { }); }); }); + + describe('Knative application', () => { + describe('when installed', () => { + describe('with ip address', () => { + const props = { + applications: { + ...APPLICATIONS_MOCK_STATE, + knative: { + title: 'Knative', + hostname: 'example.com', + status: 'installed', + externalIp: '1.1.1.1', + }, + }, + }; + it('renders ip address with a clipboard button', () => { + vm = mountComponent(Applications, props); + + expect(vm.$el.querySelector('.js-knative-ip-address').value).toEqual('1.1.1.1'); + + expect( + vm.$el + .querySelector('.js-knative-ip-clipboard-btn') + .getAttribute('data-clipboard-text'), + ).toEqual('1.1.1.1'); + }); + + it('renders domain & allows editing', () => { + expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com'); + expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe( + null, + ); + }); + + it('renders an update/save Knative domain button', () => { + expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null); + }); + + it('emits event when clicking Save changes button', () => { + spyOn(eventHub, '$emit'); + vm = mountComponent(Applications, props); + + const saveButton = vm.$el.querySelector('.js-knative-save-domain-button'); + + saveButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { + id: 'knative', + params: { hostname: 'example.com' }, + }); + }); + }); + + describe('without ip address', () => { + it('renders an input text with a question mark and an alert text', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + knative: { + title: 'Knative', + hostname: 'example.com', + status: 'installed', + }, + }, + }); + + expect(vm.$el.querySelector('.js-knative-ip-address').value).toEqual('?'); + + expect(vm.$el.querySelector('.js-no-knative-ip-message')).not.toBe(null); + }); + }); + }); + }); }); diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js index 3c3d9977ffb..3ace19c6401 100644 --- a/spec/javascripts/clusters/services/mock_data.js +++ b/spec/javascripts/clusters/services/mock_data.js @@ -115,4 +115,14 @@ const DEFAULT_APPLICATION_STATE = { requestReason: null, }; -export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE }; +const APPLICATIONS_MOCK_STATE = { + helm: { title: 'Helm Tiller', status: 'installable' }, + ingress: { title: 'Ingress', status: 'installable' }, + cert_manager: { title: 'Cert-Manager', status: 'installable' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' }, + knative: { title: 'Knative ', status: 'installable', hostname: '' }, +}; + +export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE }; diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js index 37a4d6614f6..09bcdf91d91 100644 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ b/spec/javascripts/clusters/stores/clusters_store_spec.js @@ -111,6 +111,7 @@ describe('Clusters Store', () => { requestStatus: null, requestReason: null, hostname: null, + isEditingHostName: false, externalIp: null, }, cert_manager: { -- cgit v1.2.1 From f8234d9a086a43a95698da13d2734fe62ddb9ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Mon, 18 Feb 2019 17:06:51 +0000 Subject: Creates Clusterss::ApplciationsController update endpoint - Creates new route - Creates new controller action - Creates call stack: Clusterss::ApplciationsController calls --> Clusters::Applications::UpdateService calls --> Clusters::Applications::ScheduleUpdateService calls --> ClusterUpdateAppWorker calls --> Clusters::Applications::PatchService --> ClusterWaitForAppInstallationWorker DRY req params Adds gcp_cluster:cluster_update_app queue Schedule_update_service is uneeded Extract common logic to a parent class (UpdateService will need it) Introduce new UpdateService Fix rescue class namespace Fix RuboCop offenses Adds BaseService for create and update services Remove request_handler code duplication Fixes update command Move update_command to ApplicationCore so all apps can use it Adds tests for Knative update_command Adds specs for PatchService Raise error if update receives an unistalled app Adds update_service spec Fix RuboCop offense Use subject in favor of go Adds update endpoint specs for project namespace Adds update endpoint specs for group namespace --- .../clusters/applications_controller_spec.rb | 100 ++++++++++++---- .../clusters/applications_controller_spec.rb | 97 +++++++++++++--- spec/models/clusters/applications/knative_spec.rb | 26 ++++- .../clusters/applications/patch_service_spec.rb | 128 +++++++++++++++++++++ .../clusters/applications/update_service_spec.rb | 72 ++++++++++++ 5 files changed, 381 insertions(+), 42 deletions(-) create mode 100644 spec/services/clusters/applications/patch_service_spec.rb create mode 100644 spec/services/clusters/applications/update_service_spec.rb (limited to 'spec') diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb index dd5263b077c..84da43fe2ef 100644 --- a/spec/controllers/groups/clusters/applications_controller_spec.rb +++ b/spec/controllers/groups/clusters/applications_controller_spec.rb @@ -9,9 +9,25 @@ describe Groups::Clusters::ApplicationsController do Clusters::Cluster::APPLICATIONS[application] end + shared_examples 'a secure endpoint' do + it { expect { subject }.to be_allowed_for(:admin) } + it { expect { subject }.to be_allowed_for(:owner).of(group) } + it { expect { subject }.to be_allowed_for(:maintainer).of(group) } + it { expect { subject }.to be_denied_for(:developer).of(group) } + it { expect { subject }.to be_denied_for(:reporter).of(group) } + it { expect { subject }.to be_denied_for(:guest).of(group) } + it { expect { subject }.to be_denied_for(:user) } + it { expect { subject }.to be_denied_for(:external) } + end + + let(:cluster) { create(:cluster, :group, :provided_by_gcp) } + let(:group) { cluster.group } + describe 'POST create' do - let(:cluster) { create(:cluster, :group, :provided_by_gcp) } - let(:group) { cluster.group } + subject do + post :create, params: params.merge(group_id: group) + end + let(:application) { 'helm' } let(:params) { { application: application, id: cluster.id } } @@ -26,7 +42,7 @@ describe Groups::Clusters::ApplicationsController do it 'schedule an application installation' do expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once - expect { go }.to change { current_application.count } + expect { subject }.to change { current_application.count } expect(response).to have_http_status(:no_content) expect(cluster.application_helm).to be_scheduled end @@ -37,7 +53,7 @@ describe Groups::Clusters::ApplicationsController do end it 'return 404' do - expect { go }.not_to change { current_application.count } + expect { subject }.not_to change { current_application.count } expect(response).to have_http_status(:not_found) end end @@ -46,9 +62,7 @@ describe Groups::Clusters::ApplicationsController do let(:application) { 'unkwnown-app' } it 'return 404' do - go - - expect(response).to have_http_status(:not_found) + is_expected.to have_http_status(:not_found) end end @@ -58,9 +72,7 @@ describe Groups::Clusters::ApplicationsController do end it 'returns 400' do - go - - expect(response).to have_http_status(:bad_request) + is_expected.to have_http_status(:bad_request) end end end @@ -70,18 +82,66 @@ describe Groups::Clusters::ApplicationsController do allow(ClusterInstallAppWorker).to receive(:perform_async) end - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_allowed_for(:owner).of(group) } - it { expect { go }.to be_allowed_for(:maintainer).of(group) } - it { expect { go }.to be_denied_for(:developer).of(group) } - it { expect { go }.to be_denied_for(:reporter).of(group) } - it { expect { go }.to be_denied_for(:guest).of(group) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it_behaves_like 'a secure endpoint' end + end - def go - post :create, params: params.merge(group_id: group) + describe 'PATCH update' do + subject do + patch :update, params: params.merge(group_id: group) + end + + let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) } + let(:application_name) { application.name } + let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } } + + describe 'functionality' do + let(:user) { create(:user) } + + before do + group.add_maintainer(user) + sign_in(user) + end + + context "when cluster and app exists" do + it "schedules an application update" do + expect(ClusterUpdateAppWorker).to receive(:perform_async).with(application.name, anything).once + + is_expected.to have_http_status(:no_content) + + expect(cluster.application_cert_manager).to be_scheduled + end + end + + context 'when cluster do not exists' do + before do + cluster.destroy! + end + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is unknown' do + let(:application_name) { 'unkwnown-app' } + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is already scheduled' do + before do + application.make_scheduled! + end + + it { is_expected.to have_http_status(:bad_request) } + end + end + + describe 'security' do + before do + allow(ClusterUpdateAppWorker).to receive(:perform_async) + end + + it_behaves_like 'a secure endpoint' end end end diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb index cb558259225..247adf3f8c7 100644 --- a/spec/controllers/projects/clusters/applications_controller_spec.rb +++ b/spec/controllers/projects/clusters/applications_controller_spec.rb @@ -9,7 +9,22 @@ describe Projects::Clusters::ApplicationsController do Clusters::Cluster::APPLICATIONS[application] end + shared_examples 'a secure endpoint' do + it { expect { subject }.to be_allowed_for(:admin) } + it { expect { subject }.to be_allowed_for(:owner).of(project) } + it { expect { subject }.to be_allowed_for(:maintainer).of(project) } + it { expect { subject }.to be_denied_for(:developer).of(project) } + it { expect { subject }.to be_denied_for(:reporter).of(project) } + it { expect { subject }.to be_denied_for(:guest).of(project) } + it { expect { subject }.to be_denied_for(:user) } + it { expect { subject }.to be_denied_for(:external) } + end + describe 'POST create' do + subject do + post :create, params: params.merge(namespace_id: project.namespace, project_id: project) + end + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:project) { cluster.project } let(:application) { 'helm' } @@ -26,7 +41,7 @@ describe Projects::Clusters::ApplicationsController do it 'schedule an application installation' do expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once - expect { go }.to change { current_application.count } + expect { subject }.to change { current_application.count } expect(response).to have_http_status(:no_content) expect(cluster.application_helm).to be_scheduled end @@ -37,7 +52,7 @@ describe Projects::Clusters::ApplicationsController do end it 'return 404' do - expect { go }.not_to change { current_application.count } + expect { subject }.not_to change { current_application.count } expect(response).to have_http_status(:not_found) end end @@ -46,9 +61,7 @@ describe Projects::Clusters::ApplicationsController do let(:application) { 'unkwnown-app' } it 'return 404' do - go - - expect(response).to have_http_status(:not_found) + is_expected.to have_http_status(:not_found) end end @@ -58,9 +71,7 @@ describe Projects::Clusters::ApplicationsController do end it 'returns 400' do - go - - expect(response).to have_http_status(:bad_request) + is_expected.to have_http_status(:bad_request) end end end @@ -70,18 +81,68 @@ describe Projects::Clusters::ApplicationsController do allow(ClusterInstallAppWorker).to receive(:perform_async) 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) } - it { expect { go }.to be_denied_for(:developer).of(project) } - it { expect { go }.to be_denied_for(:reporter).of(project) } - it { expect { go }.to be_denied_for(:guest).of(project) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it_behaves_like 'a secure endpoint' end + end - def go - post :create, params: params.merge(namespace_id: project.namespace, project_id: project) + describe 'PATCH update' do + subject do + patch :update, params: params.merge(namespace_id: project.namespace, project_id: project) + end + + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + let!(:application) { create(:clusters_applications_knative, :installed, cluster: cluster) } + let(:application_name) { application.name } + let(:params) { { application: application_name, id: cluster.id, hostname: "new.example.com" } } + + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_maintainer(user) + sign_in(user) + end + + context "when cluster and app exists" do + it "schedules an application update" do + expect(ClusterUpdateAppWorker).to receive(:perform_async).with(application.name, anything).once + + is_expected.to have_http_status(:no_content) + + expect(cluster.application_knative).to be_scheduled + end + end + + context 'when cluster do not exists' do + before do + cluster.destroy! + end + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is unknown' do + let(:application_name) { 'unkwnown-app' } + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is already scheduled' do + before do + application.make_scheduled! + end + + it { is_expected.to have_http_status(:bad_request) } + end + end + + describe 'security' do + before do + allow(ClusterUpdateAppWorker).to receive(:perform_async) + end + + it_behaves_like 'a secure endpoint' end end end diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 006b922ab27..4884a5927fb 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -66,9 +66,7 @@ describe Clusters::Applications::Knative do end end - describe '#install_command' do - subject { knative.install_command } - + shared_examples 'a command' do it 'should be an instance of Helm::InstallCommand' do expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) end @@ -76,7 +74,6 @@ describe Clusters::Applications::Knative do it 'should be initialized with knative arguments' do expect(subject.name).to eq('knative') expect(subject.chart).to eq('knative/knative') - expect(subject.version).to eq('0.2.2') expect(subject.files).to eq(knative.files) end @@ -98,6 +95,27 @@ describe Clusters::Applications::Knative do end end + describe '#install_command' do + subject { knative.install_command } + + it 'should be initialized with latest version' do + expect(subject.version).to eq('0.2.2') + end + + it_behaves_like 'a command' + end + + describe '#update_command' do + let!(:current_installed_version) { knative.version = '0.1.0' } + subject { knative.update_command } + + it 'should be initialized with current version' do + expect(subject.version).to eq(current_installed_version) + end + + it_behaves_like 'a command' + end + describe '#files' do let(:application) { knative } let(:values) { subject[:'values.yaml'] } diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb new file mode 100644 index 00000000000..d4ee3243b84 --- /dev/null +++ b/spec/services/clusters/applications/patch_service_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::PatchService do + describe '#execute' do + let(:application) { create(:clusters_applications_knative, :scheduled) } + let!(:update_command) { application.update_command } + let(:service) { described_class.new(application) } + let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } + + before do + allow(service).to receive(:update_command).and_return(update_command) + allow(service).to receive(:helm_api).and_return(helm_client) + end + + context 'when there are no errors' do + before do + expect(helm_client).to receive(:update).with(update_command) + allow(ClusterWaitForAppInstallationWorker).to receive(:perform_in).and_return(nil) + end + + it 'make the application updating' do + expect(application.cluster).not_to be_nil + service.execute + + expect(application).to be_updating + end + + it 'schedule async installation status check' do + expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once + + service.execute + end + end + + context 'when kubernetes cluster communication fails' do + let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) } + + before do + expect(helm_client).to receive(:update).with(update_command).and_raise(error) + end + + it 'make the application errored' do + service.execute + + expect(application).to be_update_errored + expect(application.status_reason).to match('Kubernetes error: 500') + end + + it 'logs errors' do + expect(service.send(:logger)).to receive(:error).with( + { + exception: 'Kubeclient::HttpError', + message: 'system failure', + service: 'Clusters::Applications::PatchService', + app_id: application.id, + project_ids: application.cluster.project_ids, + group_ids: [], + error_code: 500 + } + ) + + expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( + error, + extra: { + exception: 'Kubeclient::HttpError', + message: 'system failure', + service: 'Clusters::Applications::PatchService', + app_id: application.id, + project_ids: application.cluster.project_ids, + group_ids: [], + error_code: 500 + } + ) + + service.execute + end + end + + context 'a non kubernetes error happens' do + let(:application) { create(:clusters_applications_knative, :scheduled) } + let(:error) { StandardError.new('something bad happened') } + + before do + expect(application).to receive(:make_updating!).once.and_raise(error) + end + + it 'make the application errored' do + expect(helm_client).not_to receive(:update) + + service.execute + + expect(application).to be_update_errored + expect(application.status_reason).to eq("Can't start update process.") + end + + it 'logs errors' do + expect(service.send(:logger)).to receive(:error).with( + { + exception: 'StandardError', + error_code: nil, + message: 'something bad happened', + service: 'Clusters::Applications::PatchService', + app_id: application.id, + project_ids: application.cluster.projects.pluck(:id), + group_ids: [] + } + ) + + expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( + error, + extra: { + exception: 'StandardError', + error_code: nil, + message: 'something bad happened', + service: 'Clusters::Applications::PatchService', + app_id: application.id, + project_ids: application.cluster.projects.pluck(:id), + group_ids: [] + } + ) + + service.execute + end + end + end +end diff --git a/spec/services/clusters/applications/update_service_spec.rb b/spec/services/clusters/applications/update_service_spec.rb new file mode 100644 index 00000000000..22ad698f77d --- /dev/null +++ b/spec/services/clusters/applications/update_service_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::UpdateService do + include TestRequestHelpers + + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:user) { create(:user) } + let(:params) { { application: 'knative', hostname: 'udpate.example.com' } } + let(:service) { described_class.new(cluster, user, params) } + + subject { service.execute(test_request) } + + describe '#execute' do + before do + allow(ClusterUpdateAppWorker).to receive(:perform_async) + end + + context 'application is not installed' do + it 'raises Clusters::Applications::BaseService::InvalidApplicationError' do + expect(ClusterUpdateAppWorker).not_to receive(:perform_async) + + expect { subject } + .to raise_exception { Clusters::Applications::BaseService::InvalidApplicationError } + .and not_change { Clusters::Applications::Knative.count } + .and not_change { Clusters::Applications::Knative.with_status(:scheduled).count } + end + end + + context 'application is installed' do + context 'application is schedulable' do + let!(:application) do + create(:clusters_applications_knative, status: 3, cluster: cluster) + end + + it 'updates the application data' do + expect do + subject + end.to change { application.reload.hostname }.to(params[:hostname]) + end + + it 'makes application scheduled!' do + subject + + expect(application.reload).to be_scheduled + end + + it 'schedules ClusterUpdateAppWorker' do + expect(ClusterUpdateAppWorker).to receive(:perform_async) + + subject + end + end + + context 'application is not schedulable' do + let!(:application) do + create(:clusters_applications_knative, status: 4, cluster: cluster) + end + + it 'raises StateMachines::InvalidTransition' do + expect(ClusterUpdateAppWorker).not_to receive(:perform_async) + + expect { subject } + .to raise_exception { StateMachines::InvalidTransition } + .and not_change { application.reload.hostname } + .and not_change { Clusters::Applications::Knative.with_status(:scheduled).count } + end + end + end + end +end -- cgit v1.2.1 From 1186a6fd5408737f1995ac16ffc18f6aaf431cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Mon, 4 Mar 2019 10:08:53 +0000 Subject: Sends update route to the client - extends presenters to include update endpoint path - sends path to the client on clusters clusters show view. --- spec/presenters/group_clusterable_presenter_spec.rb | 8 ++++++++ spec/presenters/project_clusterable_presenter_spec.rb | 8 ++++++++ 2 files changed, 16 insertions(+) (limited to 'spec') diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb index 205160742bf..fa77273f6aa 100644 --- a/spec/presenters/group_clusterable_presenter_spec.rb +++ b/spec/presenters/group_clusterable_presenter_spec.rb @@ -69,6 +69,14 @@ describe GroupClusterablePresenter do it { is_expected.to eq(install_applications_group_cluster_path(group, cluster, application)) } end + describe '#update_applications_cluster_path' do + let(:application) { :helm } + + subject { presenter.update_applications_cluster_path(cluster, application) } + + it { is_expected.to eq(update_applications_group_cluster_path(group, cluster, application)) } + end + describe '#cluster_path' do subject { presenter.cluster_path(cluster) } diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb index c50d90ae1e8..6786a84243f 100644 --- a/spec/presenters/project_clusterable_presenter_spec.rb +++ b/spec/presenters/project_clusterable_presenter_spec.rb @@ -69,6 +69,14 @@ describe ProjectClusterablePresenter do it { is_expected.to eq(install_applications_project_cluster_path(project, cluster, application)) } end + describe '#update_applications_cluster_path' do + let(:application) { :helm } + + subject { presenter.update_applications_cluster_path(cluster, application) } + + it { is_expected.to eq(update_applications_project_cluster_path(project, cluster, application)) } + end + describe '#cluster_path' do subject { presenter.cluster_path(cluster) } -- cgit v1.2.1 From 997290fcba1058650390645d099a7245510470b6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 4 Mar 2019 22:33:37 -0800 Subject: Resolve conflicts in spec/mailers/notify_spec.rb --- spec/mailers/notify_spec.rb | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'spec') diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 67af2c36669..3c8897ed37c 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -208,15 +208,6 @@ describe Notify do let(:new_issue) { create(:issue) } subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } -<<<<<<< HEAD - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } - end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' - it_behaves_like 'appearance header and footer enabled' - it_behaves_like 'appearance header and footer not enabled' -======= context 'when a user has permissions to access the new issue' do before do new_issue.project.add_developer(recipient) @@ -234,7 +225,6 @@ describe Notify do it 'has the correct subject and body' do new_issue_url = project_issue_path(new_issue.project, new_issue) ->>>>>>> dev/master aggregate_failures do is_expected.to have_referable_subject(issue, reply: true) -- cgit v1.2.1 From b9fcefc895fe140018b97912891121f76afe6648 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 4 Mar 2019 22:38:02 -0800 Subject: Resolve conflicts in spec/policies/group_policy_spec.rb --- spec/policies/group_policy_spec.rb | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'spec') diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 5cb0183df3a..4c31ff30fc6 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -114,25 +114,13 @@ describe GroupPolicy do project.add_developer(current_user) end -<<<<<<< HEAD - it do - expect_allowed(:read_group, :read_list, :read_label) - end -======= it { expect_allowed(:read_label) } ->>>>>>> dev/master context 'in subgroups', :nested_groups do let(:subgroup) { create(:group, :private, parent: group) } let(:project) { create(:project, namespace: subgroup) } -<<<<<<< HEAD - it do - expect_allowed(:read_group, :read_list, :read_label) - end -======= it { expect_allowed(:read_label) } ->>>>>>> dev/master end end -- cgit v1.2.1 From ad2f711adfe151f15f19559884cd84a8dd26e93e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 4 Mar 2019 23:19:19 -0800 Subject: Add frozen_string_literal to new files --- spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb | 2 ++ spec/policies/commit_policy_spec.rb | 2 ++ spec/validators/sha_validator_spec.rb | 2 ++ 3 files changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb index f81dbcf62da..9bfb1b13a2b 100644 --- a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb +++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Gitlab::DependencyLinker::Parser::Gemfile do diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb index 2259693cf01..41f6fb08426 100644 --- a/spec/policies/commit_policy_spec.rb +++ b/spec/policies/commit_policy_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CommitPolicy do diff --git a/spec/validators/sha_validator_spec.rb b/spec/validators/sha_validator_spec.rb index b9242ef931e..dc1539cf318 100644 --- a/spec/validators/sha_validator_spec.rb +++ b/spec/validators/sha_validator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ShaValidator do -- cgit v1.2.1 From 434cb1d96f43c90f32427c60a82308307a9181e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Thu, 17 Jan 2019 20:41:53 +0100 Subject: Display only informaton visible to current user Display only labels and assignees of issues visible by the currently logged user Display only issues visible to user in the burndown chart --- spec/models/concerns/milestoneish_spec.rb | 95 ++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 4647eecbdef..81ca5b638fe 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -9,8 +9,10 @@ describe Milestone, 'Milestoneish' do let(:admin) { create(:admin) } let(:project) { create(:project, :public) } let(:milestone) { create(:milestone, project: project) } - let!(:issue) { create(:issue, project: project, milestone: milestone) } - let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) } + let(:label1) { create(:label, project: project) } + let(:label2) { create(:label, project: project) } + let!(:issue) { create(:issue, project: project, milestone: milestone, assignees: [member], labels: [label1]) } + let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone, labels: [label2]) } let!(:security_issue_2) { create(:issue, :confidential, project: project, assignees: [assignee], milestone: milestone) } let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) } let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) } @@ -42,6 +44,95 @@ describe Milestone, 'Milestoneish' do end end + context 'attributes visibility' do + using RSpec::Parameterized::TableSyntax + + let(:users) do + { + anonymous: nil, + non_member: non_member, + guest: guest, + member: member, + assignee: assignee + } + end + + let(:project_visibility_levels) do + { + public: Gitlab::VisibilityLevel::PUBLIC, + internal: Gitlab::VisibilityLevel::INTERNAL, + private: Gitlab::VisibilityLevel::PRIVATE + } + end + + describe '#issue_participants_visible_by_user' do + where(:visibility, :user_role, :result) do + :public | nil | [:member] + :public | :non_member | [:member] + :public | :guest | [:member] + :public | :member | [:member, :assignee] + :internal | nil | [] + :internal | :non_member | [:member] + :internal | :guest | [:member] + :internal | :member | [:member, :assignee] + :private | nil | [] + :private | :non_member | [] + :private | :guest | [:member] + :private | :member | [:member, :assignee] + end + + with_them do + before do + project.update(visibility_level: project_visibility_levels[visibility]) + end + + it 'returns the proper participants' do + user = users[user_role] + participants = result.map { |role| users[role] } + + expect(milestone.issue_participants_visible_by_user(user)).to match_array(participants) + end + end + end + + describe '#issue_labels_visible_by_user' do + let(:labels) do + { + label1: label1, + label2: label2 + } + end + + where(:visibility, :user_role, :result) do + :public | nil | [:label1] + :public | :non_member | [:label1] + :public | :guest | [:label1] + :public | :member | [:label1, :label2] + :internal | nil | [] + :internal | :non_member | [:label1] + :internal | :guest | [:label1] + :internal | :member | [:label1, :label2] + :private | nil | [] + :private | :non_member | [] + :private | :guest | [:label1] + :private | :member | [:label1, :label2] + end + + with_them do + before do + project.update(visibility_level: project_visibility_levels[visibility]) + end + + it 'returns the proper participants' do + user = users[user_role] + expected_labels = result.map { |label| labels[label] } + + expect(milestone.issue_labels_visible_by_user(user)).to match_array(expected_labels) + end + end + end + end + describe '#sorted_merge_requests' do it 'sorts merge requests by label priority' do merge_request_1 = create(:labeled_merge_request, labels: [label_2], source_project: project, source_branch: 'branch_1', milestone: milestone) -- cgit v1.2.1 From a24eabb1782785ad0bcf2460c3fff67106598b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Tue, 5 Mar 2019 10:38:09 +0100 Subject: Resolve conflicts in group policy --- spec/policies/group_policy_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 4c31ff30fc6..0ad50c6f91f 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -114,13 +114,13 @@ describe GroupPolicy do project.add_developer(current_user) end - it { expect_allowed(:read_label) } + it { expect_allowed(:read_label, :read_list) } context 'in subgroups', :nested_groups do let(:subgroup) { create(:group, :private, parent: group) } let(:project) { create(:project, namespace: subgroup) } - it { expect_allowed(:read_label) } + it { expect_allowed(:read_label, :read_list) } end end -- cgit v1.2.1 From 55f76ce8f6010458988ccbc99201f6b74d11e238 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 5 Mar 2019 11:02:00 +0000 Subject: Revert "Merge branch 'winh-dequarantine-labels-autocomplete' into 'master'" This reverts merge request !25542 --- spec/features/issues/gfm_autocomplete_spec.rb | 84 +++++++++++++++++++- spec/frontend/gfm_auto_complete_spec.js | 94 +---------------------- spec/javascripts/fixtures/autocomplete_sources.rb | 40 ---------- 3 files changed, 85 insertions(+), 133 deletions(-) delete mode 100644 spec/javascripts/fixtures/autocomplete_sources.rb (limited to 'spec') diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 8eb413bdd8d..986f3823275 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -278,7 +278,12 @@ describe 'GFM autocomplete', :js do end end - context 'labels' do + # This context has just one example in each contexts in order to improve spec performance. + context 'labels', :quarantine do + let!(:backend) { create(:label, project: project, title: 'backend') } + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') } + it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do create(:label, project: project, title: label_xss_title) @@ -293,6 +298,83 @@ describe 'GFM autocomplete', :js do expect(find('.atwho-view-ul').text).to have_content('alert label') end end + + context 'when no labels are assigned' do + it 'shows labels' do + note = find('#note-body') + + # It should show all the labels on "~". + type(note, '~') + wait_for_requests + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/label ~". + type(note, '/label ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/relabel ~". + type(note, '/relabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show no labels on "/unlabel ~". + type(note, '/unlabel ~') + expect_labels(not_shown: [backend, bug, feature_proposal]) + end + end + + context 'when some labels are assigned' do + before do + issue.labels << [backend] + end + + it 'shows labels' do + note = find('#note-body') + + # It should show all the labels on "~". + type(note, '~') + wait_for_requests + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show only unset labels on "/label ~". + type(note, '/label ~') + expect_labels(shown: [bug, feature_proposal], not_shown: [backend]) + + # It should show all the labels on "/relabel ~". + type(note, '/relabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show only set labels on "/unlabel ~". + type(note, '/unlabel ~') + expect_labels(shown: [backend], not_shown: [bug, feature_proposal]) + end + end + + context 'when all labels are assigned' do + before do + issue.labels << [backend, bug, feature_proposal] + end + + it 'shows labels' do + note = find('#note-body') + + # It should show all the labels on "~". + type(note, '~') + wait_for_requests + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show no labels on "/label ~". + type(note, '/label ~') + expect_labels(not_shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/relabel ~". + type(note, '/relabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/unlabel ~". + type(note, '/unlabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + end + end end shared_examples 'autocomplete suggestions' do diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index b79e6e0fe7b..c7008c780d6 100644 --- a/spec/frontend/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -6,21 +6,17 @@ import GfmAutoComplete from '~/gfm_auto_complete'; import 'vendor/jquery.caret'; import 'vendor/jquery.atwho'; -import { TEST_HOST } from 'helpers/test_constants'; -import labelsFixture from 'fixtures/autocomplete_sources/labels.json'; // eslint-disable-line import/no-unresolved - describe('GfmAutoComplete', () => { const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({ fetchData: () => {}, }); let atwhoInstance; + let items; let sorterValue; describe('DefaultOptions.sorter', () => { describe('assets loading', () => { - let items; - beforeEach(() => { jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true); @@ -65,7 +61,7 @@ describe('GfmAutoComplete', () => { atwhoInstance = { setting: {} }; const query = 'query'; - const items = []; + items = []; const searchKey = 'searchKey'; gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, query, items, searchKey); @@ -254,90 +250,4 @@ describe('GfmAutoComplete', () => { ).toBe('
  • grp/proj#5 Some Issue
  • '); }); }); - - describe('labels', () => { - const dataSources = { - labels: `${TEST_HOST}/autocomplete_sources/labels`, - }; - - const allLabels = labelsFixture; - const assignedLabels = allLabels.filter(label => label.set); - const unassignedLabels = allLabels.filter(label => !label.set); - - let autocomplete; - let $textarea; - - beforeEach(() => { - autocomplete = new GfmAutoComplete(dataSources); - $textarea = $(''); - autocomplete.setup($textarea, { labels: true }); - }); - - afterEach(() => { - autocomplete.destroy(); - }); - - const triggerDropdown = text => { - $textarea - .trigger('focus') - .val(text) - .caret('pos', -1); - $textarea.trigger('keyup'); - - return new Promise(window.requestAnimationFrame); - }; - - const getDropdownItems = () => { - const dropdown = document.getElementById('at-view-labels'); - const items = dropdown.getElementsByTagName('li'); - return [].map.call(items, item => item.textContent.trim()); - }; - - const expectLabels = ({ input, output }) => - triggerDropdown(input).then(() => { - expect(getDropdownItems()).toEqual(output.map(label => label.title)); - }); - - describe('with no labels assigned', () => { - beforeEach(() => { - autocomplete.cachedData['~'] = [...unassignedLabels]; - }); - - it.each` - input | output - ${'~'} | ${unassignedLabels} - ${'/label ~'} | ${unassignedLabels} - ${'/relabel ~'} | ${unassignedLabels} - ${'/unlabel ~'} | ${[]} - `('$input shows $output.length labels', expectLabels); - }); - - describe('with some labels assigned', () => { - beforeEach(() => { - autocomplete.cachedData['~'] = allLabels; - }); - - it.each` - input | output - ${'~'} | ${allLabels} - ${'/label ~'} | ${unassignedLabels} - ${'/relabel ~'} | ${allLabels} - ${'/unlabel ~'} | ${assignedLabels} - `('$input shows $output.length labels', expectLabels); - }); - - describe('with all labels assigned', () => { - beforeEach(() => { - autocomplete.cachedData['~'] = [...assignedLabels]; - }); - - it.each` - input | output - ${'~'} | ${assignedLabels} - ${'/label ~'} | ${[]} - ${'/relabel ~'} | ${assignedLabels} - ${'/unlabel ~'} | ${assignedLabels} - `('$input shows $output.length labels', expectLabels); - }); - }); }); diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb deleted file mode 100644 index c117fb7cd24..00000000000 --- a/spec/javascripts/fixtures/autocomplete_sources.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do - include JavaScriptFixturesHelpers - - set(:admin) { create(:admin) } - set(:group) { create(:group, name: 'frontend-fixtures') } - set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') } - set(:issue) { create(:issue, project: project) } - - before(:all) do - clean_frontend_fixtures('autocomplete_sources/') - end - - before do - sign_in(admin) - end - - it 'autocomplete_sources/labels.json' do |example| - issue.labels << create(:label, project: project, title: 'bug') - issue.labels << create(:label, project: project, title: 'critical') - - create(:label, project: project, title: 'feature') - create(:label, project: project, title: 'documentation') - - get :labels, - format: :json, - params: { - namespace_id: group.path, - project_id: project.path, - type: issue.class.name, - type_id: issue.id - } - - expect(response).to be_success - store_frontend_fixture(response, example.description) - end -end -- cgit v1.2.1 From 23d26afa2eb2c02e616b5a06c0289a8e56caf389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Tue, 5 Mar 2019 12:41:39 +0100 Subject: Make sure MR and milestone projects are same --- spec/models/ci/pipeline_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 332278ce077..d0b42d103a5 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -520,7 +520,7 @@ describe Ci::Pipeline, :mailer do end let(:assignee) { create(:user) } - let(:milestone) { create(:milestone) } + let(:milestone) { create(:milestone, project: project) } let(:labels) { create_list(:label, 2) } it 'exposes merge request pipeline variables' do -- cgit v1.2.1 From f933e6125c81877b9118ea753f3218f404bbaff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Tue, 5 Mar 2019 13:33:09 +0000 Subject: Adds Knative udpate feature specs - specs for clicking Install button - specs for clicking Save changes button --- .../projects/clusters/applications_spec.rb | 81 +++++++++++++++++++--- 1 file changed, 73 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index 2c8d014c36d..d97bf246ce3 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -17,7 +17,7 @@ describe 'Clusters Applications', :js do end context 'when cluster is being created' do - let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project])} + let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } it 'user is unable to install applications' do page.within('.js-cluster-application-row-helm') do @@ -28,7 +28,7 @@ describe 'Clusters Applications', :js do end context 'when cluster is created' do - let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project])} + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } it 'user can install applications' do page.within('.js-cluster-application-row-helm') do @@ -52,7 +52,7 @@ describe 'Clusters Applications', :js do expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') - wait_until_helm_created! + wait_until_app_created!('helm') Clusters::Cluster.last.application_helm.make_installing! @@ -76,7 +76,7 @@ describe 'Clusters Applications', :js do end context 'on an abac cluster' do - let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project])} + let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project]) } it 'should show info block and not be installable' do page.within('.js-cluster-application-row-knative') do @@ -87,7 +87,7 @@ describe 'Clusters Applications', :js do end context 'on an rbac cluster' do - let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project])} + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } it 'should not show callout block and be installable' do page.within('.js-cluster-application-row-knative') do @@ -95,6 +95,60 @@ describe 'Clusters Applications', :js do expect(page).to have_css('.js-cluster-application-install-button:not([disabled])') end end + + describe 'when user clicks install button' do + def domainname_form_value + page.find('.js-knative-domainname').value + end + + before do + allow(ClusterInstallAppWorker).to receive(:perform_async) + allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in) + allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async) + + page.within('.js-cluster-application-row-knative') do + expect(page).to have_css('.js-cluster-application-install-button:not([disabled])') + + page.find('.js-knative-domainname').set("domain.example.org") + + click_button 'Install' + + wait_until_app_created!('knative') + + expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') + + Clusters::Cluster.last.application_knative.make_installing! + Clusters::Cluster.last.application_knative.make_installed! + Clusters::Cluster.last.application_knative.update_attribute(:external_ip, '127.0.0.1') + end + end + + it 'shows status transition' do + page.within('.js-cluster-application-row-knative') do + expect(domainname_form_value).to eq('domain.example.org') + expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed') + end + + expect(page).to have_content('Knative was successfully installed on your Kubernetes cluster') + expect(page).to have_css('.js-knative-save-domain-button'), exact_text: 'Save changes' + end + + it 'can then update the domain' do + page.within('.js-cluster-application-row-knative') do + expect(ClusterUpdateAppWorker).to receive(:perform_async) + + expect(domainname_form_value).to eq('domain.example.org') + + page.find('.js-knative-domainname').set("new.domain.example.org") + + click_button 'Save changes' + + wait_until_app_updated!(cluster.application_knative) + + expect(domainname_form_value).to eq('new.domain.example.org') + end + end + end end end @@ -185,11 +239,22 @@ describe 'Clusters Applications', :js do end end - def wait_until_helm_created! + def wait_until_app_created!(app) + retries = 0 + + while Clusters::Cluster.last.send("application_#{app}").nil? + raise "Timed out waiting for #{ app } application to be created in DB" if (retries += 1) > 3 + + sleep(1) + end + end + + def wait_until_app_updated!(app) retries = 0 + updated_at = app.updated_at - while Clusters::Cluster.last.application_helm.nil? - raise "Timed out waiting for helm application to be created in DB" if (retries += 1) > 3 + while updated_at == app.reload.updated_at + raise "Timed out waiting for #{ app } application to be created in DB" if (retries += 1) > 3 sleep(1) end -- cgit v1.2.1 From 21779d00186d75349165d6c07dbe04aace68017c Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 24 Jan 2019 16:23:57 +0000 Subject: Add metadata about the GitLab server to GraphQL --- spec/graphql/resolvers/metadata_resolver_spec.rb | 11 ++++++++ spec/graphql/types/metadata_type_spec.rb | 5 ++++ spec/graphql/types/query_type_spec.rb | 15 ++++++++++- spec/policies/global_policy_spec.rb | 12 +++++++++ spec/requests/api/graphql/metadata_query_spec.rb | 32 ++++++++++++++++++++++++ spec/requests/api/version_spec.rb | 18 ++++++++++++- spec/support/helpers/graphql_helpers.rb | 3 ++- 7 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 spec/graphql/resolvers/metadata_resolver_spec.rb create mode 100644 spec/graphql/types/metadata_type_spec.rb create mode 100644 spec/requests/api/graphql/metadata_query_spec.rb (limited to 'spec') diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb new file mode 100644 index 00000000000..e662ed127a5 --- /dev/null +++ b/spec/graphql/resolvers/metadata_resolver_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Resolvers::MetadataResolver do + include GraphqlHelpers + + describe '#resolve' do + it 'returns version and revision' do + expect(resolve(described_class)).to eq(version: Gitlab::VERSION, revision: Gitlab.revision) + end + end +end diff --git a/spec/graphql/types/metadata_type_spec.rb b/spec/graphql/types/metadata_type_spec.rb new file mode 100644 index 00000000000..55205bf5b6a --- /dev/null +++ b/spec/graphql/types/metadata_type_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe GitlabSchema.types['Metadata'] do + it { expect(described_class.graphql_name).to eq('Metadata') } +end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index e1df6f9811d..fd75b8f700b 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -5,7 +5,7 @@ describe GitlabSchema.types['Query'] do expect(described_class.graphql_name).to eq('Query') end - it { is_expected.to have_graphql_fields(:project, :echo) } + it { is_expected.to have_graphql_fields(:project, :echo, :metadata) } describe 'project field' do subject { described_class.fields['project'] } @@ -20,4 +20,17 @@ describe GitlabSchema.types['Query'] do is_expected.to require_graphql_authorizations(:read_project) end end + + describe 'metadata field' do + subject { described_class.fields['metadata'] } + + it 'returns metadata' do + is_expected.to have_graphql_type(Types::MetadataType) + is_expected.to have_graphql_resolver(Resolvers::MetadataResolver) + end + + it 'authorizes with log_in' do + is_expected.to require_graphql_authorizations(:read_instance_metadata) + end + end end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index 30d68e7dc9d..12be3927e18 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -181,6 +181,18 @@ describe GlobalPolicy do end end + describe 'read instance metadata' do + context 'regular user' do + it { is_expected.to be_allowed(:read_instance_metadata) } + end + + context 'anonymous' do + let(:current_user) { nil } + + it { is_expected.not_to be_allowed(:read_instance_metadata) } + end + end + describe 'read instance statistics' do context 'regular user' do it { is_expected.to be_allowed(:read_instance_statistics) } diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb new file mode 100644 index 00000000000..4c56c559cf9 --- /dev/null +++ b/spec/requests/api/graphql/metadata_query_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting project information' do + include GraphqlHelpers + + let(:query) { graphql_query_for('metadata', {}, all_graphql_fields_for('Metadata')) } + + context 'logged in' do + it 'returns version and revision' do + post_graphql(query, current_user: create(:user)) + + expect(graphql_errors).to be_nil + expect(graphql_data).to eq( + 'metadata' => { + 'version' => Gitlab::VERSION, + 'revision' => Gitlab.revision + } + ) + end + end + + context 'anonymous user' do + it 'returns nothing' do + post_graphql(query, current_user: nil) + + expect(graphql_errors).to be_nil + expect(graphql_data).to eq('metadata' => nil) + end + end +end diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 38b618191fb..e06f8bbc095 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe API::Version do - describe 'GET /version' do + shared_examples_for 'GET /version' do context 'when unauthenticated' do it 'returns authentication error' do get api('/version') @@ -22,4 +22,20 @@ describe API::Version do end end end + + context 'with graphql enabled' do + before do + stub_feature_flags(graphql: true) + end + + include_examples 'GET /version' + end + + context 'with graphql disabled' do + before do + stub_feature_flags(graphql: false) + end + + include_examples 'GET /version' + end end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index e468ee4676d..d9529262483 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -77,8 +77,9 @@ module GraphqlHelpers def query_graphql_field(name, attributes = {}, fields = nil) fields ||= all_graphql_fields_for(name.classify) attributes = attributes_to_graphql(attributes) + attributes = "(#{attributes})" if attributes.present? <<~QUERY - #{name}(#{attributes}) { + #{name}#{attributes} { #{fields} } QUERY -- cgit v1.2.1 From 5e6e1dd54ae80f6725d75da01eb024d40aa47167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Tue, 5 Mar 2019 15:14:29 +0000 Subject: Fix rubocop offenses --- spec/features/projects/clusters/applications_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index d97bf246ce3..7eabcda286a 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -243,7 +243,7 @@ describe 'Clusters Applications', :js do retries = 0 while Clusters::Cluster.last.send("application_#{app}").nil? - raise "Timed out waiting for #{ app } application to be created in DB" if (retries += 1) > 3 + raise "Timed out waiting for #{app} application to be created in DB" if (retries += 1) > 3 sleep(1) end @@ -254,7 +254,7 @@ describe 'Clusters Applications', :js do updated_at = app.updated_at while updated_at == app.reload.updated_at - raise "Timed out waiting for #{ app } application to be created in DB" if (retries += 1) > 3 + raise "Timed out waiting for #{app} application to be created in DB" if (retries += 1) > 3 sleep(1) end -- cgit v1.2.1 From 79c421105013c3694679fefe3c1c1d4cb30446fc Mon Sep 17 00:00:00 2001 From: Igor Drozdov Date: Tue, 5 Mar 2019 15:15:22 +0000 Subject: Display the correct number of MRs a user has access to --- spec/finders/merge_requests_finder_spec.rb | 508 ++++++++++++++++++----------- 1 file changed, 309 insertions(+), 199 deletions(-) (limited to 'spec') diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 79f854cdb96..503b88fcbad 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -13,269 +13,313 @@ describe MergeRequestsFinder do end end - let(:user) { create :user } - let(:user2) { create :user } - - let(:group) { create(:group) } - let(:subgroup) { create(:group, parent: group) } - let(:project1) { create_project_without_n_plus_1(group: group) } - let(:project2) do - Gitlab::GitalyClient.allow_n_plus_1_calls do - fork_project(project1, user) + context "multiple projects with merge requests" do + let(:user) { create :user } + let(:user2) { create :user } + + let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + let(:project1) { create_project_without_n_plus_1(group: group) } + let(:project2) do + Gitlab::GitalyClient.allow_n_plus_1_calls do + fork_project(project1, user) + end end - end - let(:project3) do - Gitlab::GitalyClient.allow_n_plus_1_calls do - p = fork_project(project1, user) - p.update!(archived: true) - p + let(:project3) do + Gitlab::GitalyClient.allow_n_plus_1_calls do + p = fork_project(project1, user) + p.update!(archived: true) + p + end end - end - let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) } - let(:project5) { create_project_without_n_plus_1(group: subgroup) } - let(:project6) { create_project_without_n_plus_1(group: subgroup) } - - let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } - let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } - let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } - let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') } - let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') } - let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') } - let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') } - let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') } - let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') } - - before do - project1.add_maintainer(user) - project2.add_developer(user) - project3.add_developer(user) - project2.add_developer(user2) - project4.add_developer(user) - project5.add_developer(user) - project6.add_developer(user) - end - - describe "#execute" do - it 'filters by scope' do - params = { scope: 'authored', state: 'opened' } - merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(7) + let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) } + let(:project5) { create_project_without_n_plus_1(group: subgroup) } + let(:project6) { create_project_without_n_plus_1(group: subgroup) } + + let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } + let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } + let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } + let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') } + let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') } + let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') } + let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') } + let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') } + let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') } + + before do + project1.add_maintainer(user) + project2.add_developer(user) + project3.add_developer(user) + project2.add_developer(user2) + project4.add_developer(user) + project5.add_developer(user) + project6.add_developer(user) end - it 'filters by project' do - params = { project_id: project1.id, scope: 'authored', state: 'opened' } - merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(2) - end + describe '#execute' do + it 'filters by scope' do + params = { scope: 'authored', state: 'opened' } + merge_requests = described_class.new(user, params).execute + expect(merge_requests.size).to eq(7) + end - it 'filters by commit sha' do - merge_requests = described_class.new( - user, - commit_sha: merge_request5.merge_request_diff.last_commit_sha - ).execute + it 'filters by project' do + params = { project_id: project1.id, scope: 'authored', state: 'opened' } + merge_requests = described_class.new(user, params).execute + expect(merge_requests.size).to eq(2) + end - expect(merge_requests).to contain_exactly(merge_request5) - end + it 'filters by commit sha' do + merge_requests = described_class.new( + user, + commit_sha: merge_request5.merge_request_diff.last_commit_sha + ).execute + + expect(merge_requests).to contain_exactly(merge_request5) + end + + context 'filtering by group' do + it 'includes all merge requests when user has access' do + params = { group_id: group.id } + + merge_requests = described_class.new(user, params).execute - context 'filtering by group' do - it 'includes all merge requests when user has access' do - params = { group_id: group.id } + expect(merge_requests.size).to eq(3) + end + + it 'excludes merge requests from projects the user does not have access to' do + private_project = create_project_without_n_plus_1(:private, group: group) + private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) + params = { group_id: group.id } + + private_project.add_guest(user) + merge_requests = described_class.new(user, params).execute + + expect(merge_requests.size).to eq(3) + expect(merge_requests).not_to include(private_mr) + end + + it 'filters by group including subgroups', :nested_groups do + params = { group_id: group.id, include_subgroups: true } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests.size).to eq(6) + end + end + + it 'filters by non_archived' do + params = { non_archived: true } + merge_requests = described_class.new(user, params).execute + expect(merge_requests.size).to eq(8) + end + + it 'filters by iid' do + params = { project_id: project1.id, iids: merge_request1.iid } merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(3) + expect(merge_requests).to contain_exactly(merge_request1) end - it 'excludes merge requests from projects the user does not have access to' do - private_project = create_project_without_n_plus_1(:private, group: group) - private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) - params = { group_id: group.id } + it 'filters by source branch' do + params = { source_branch: merge_request2.source_branch } - private_project.add_guest(user) merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(3) - expect(merge_requests).not_to include(private_mr) + expect(merge_requests).to contain_exactly(merge_request2) end - it 'filters by group including subgroups', :nested_groups do - params = { group_id: group.id, include_subgroups: true } + it 'filters by target branch' do + params = { target_branch: merge_request2.target_branch } merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(6) + expect(merge_requests).to contain_exactly(merge_request2) end - end - it 'filters by non_archived' do - params = { non_archived: true } - merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(8) - end + it 'filters by state' do + params = { state: 'locked' } - it 'filters by iid' do - params = { project_id: project1.id, iids: merge_request1.iid } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request3) + end - expect(merge_requests).to contain_exactly(merge_request1) - end + it 'filters by wip' do + params = { wip: 'yes' } - it 'filters by source branch' do - params = { source_branch: merge_request2.source_branch } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) + end - expect(merge_requests).to contain_exactly(merge_request2) - end + it 'filters by not wip' do + params = { wip: 'no' } - it 'filters by target branch' do - params = { target_branch: merge_request2.target_branch } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) + end - expect(merge_requests).to contain_exactly(merge_request2) - end + it 'returns all items if no valid wip param exists' do + params = { wip: '' } - it 'filters by state' do - params = { state: 'locked' } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) + end - expect(merge_requests).to contain_exactly(merge_request3) - end + it 'adds wip to scalar params' do + scalar_params = described_class.scalar_params - it 'filters by wip' do - params = { wip: 'yes' } + expect(scalar_params).to include(:wip, :assignee_id) + end - merge_requests = described_class.new(user, params).execute + context 'filtering by group milestone' do + let!(:group) { create(:group, :public) } + let(:group_milestone) { create(:milestone, group: group) } + let!(:group_member) { create(:group_member, group: group, user: user) } + let(:params) { { milestone_title: group_milestone.title } } - expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) - end + before do + project2.update(namespace: group) + merge_request2.update(milestone: group_milestone) + merge_request3.update(milestone: group_milestone) + end - it 'filters by not wip' do - params = { wip: 'no' } + it 'returns issues assigned to that group milestone' do + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request2, merge_request3) + end + end - expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) - end + context 'filtering by created_at/updated_at' do + let(:new_project) { create(:project, forked_from_project: project1) } - it 'returns all items if no valid wip param exists' do - params = { wip: '' } + let!(:new_merge_request) do + create(:merge_request, + :simple, + author: user, + created_at: 1.week.from_now, + updated_at: 1.week.from_now, + source_project: new_project, + target_project: new_project) + end - merge_requests = described_class.new(user, params).execute + let!(:old_merge_request) do + create(:merge_request, + :simple, + author: user, + source_branch: 'feature_1', + created_at: 1.week.ago, + updated_at: 1.week.ago, + source_project: new_project, + target_project: new_project) + end - expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) - end + before do + new_project.add_maintainer(user) + end - it 'adds wip to scalar params' do - scalar_params = described_class.scalar_params + it 'filters by created_after' do + params = { project_id: new_project.id, created_after: new_merge_request.created_at } - expect(scalar_params).to include(:wip, :assignee_id) - end + merge_requests = described_class.new(user, params).execute - context 'filtering by group milestone' do - let!(:group) { create(:group, :public) } - let(:group_milestone) { create(:milestone, group: group) } - let!(:group_member) { create(:group_member, group: group, user: user) } - let(:params) { { milestone_title: group_milestone.title } } + expect(merge_requests).to contain_exactly(new_merge_request) + end - before do - project2.update(namespace: group) - merge_request2.update(milestone: group_milestone) - merge_request3.update(milestone: group_milestone) - end + it 'filters by created_before' do + params = { project_id: new_project.id, created_before: old_merge_request.created_at } - it 'returns issues assigned to that group milestone' do - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(merge_request2, merge_request3) - end - end + expect(merge_requests).to contain_exactly(old_merge_request) + end - context 'filtering by created_at/updated_at' do - let(:new_project) { create(:project, forked_from_project: project1) } - - let!(:new_merge_request) do - create(:merge_request, - :simple, - author: user, - created_at: 1.week.from_now, - updated_at: 1.week.from_now, - source_project: new_project, - target_project: new_project) - end + it 'filters by created_after and created_before' do + params = { + project_id: new_project.id, + created_after: old_merge_request.created_at, + created_before: new_merge_request.created_at + } - let!(:old_merge_request) do - create(:merge_request, - :simple, - author: user, - source_branch: 'feature_1', - created_at: 1.week.ago, - updated_at: 1.week.ago, - source_project: new_project, - target_project: new_project) - end + merge_requests = described_class.new(user, params).execute - before do - new_project.add_maintainer(user) - end + expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + end - it 'filters by created_after' do - params = { project_id: new_project.id, created_after: new_merge_request.created_at } + it 'filters by updated_after' do + params = { project_id: new_project.id, updated_after: new_merge_request.updated_at } - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(new_merge_request) - end + expect(merge_requests).to contain_exactly(new_merge_request) + end - it 'filters by created_before' do - params = { project_id: new_project.id, created_before: old_merge_request.created_at } + it 'filters by updated_before' do + params = { project_id: new_project.id, updated_before: old_merge_request.updated_at } - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(old_merge_request) - end + expect(merge_requests).to contain_exactly(old_merge_request) + end - it 'filters by created_after and created_before' do - params = { - project_id: new_project.id, - created_after: old_merge_request.created_at, - created_before: new_merge_request.created_at - } + it 'filters by updated_after and updated_before' do + params = { + project_id: new_project.id, + updated_after: old_merge_request.updated_at, + updated_before: new_merge_request.updated_at + } - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + end end + end - it 'filters by updated_after' do - params = { project_id: new_project.id, updated_after: new_merge_request.updated_at } + describe '#row_count', :request_store do + it 'returns the number of rows for the default state' do + finder = described_class.new(user) - merge_requests = described_class.new(user, params).execute + expect(finder.row_count).to eq(7) + end + + it 'returns the number of rows for a given state' do + finder = described_class.new(user, state: 'closed') - expect(merge_requests).to contain_exactly(new_merge_request) + expect(finder.row_count).to eq(1) end + end + end - it 'filters by updated_before' do - params = { project_id: new_project.id, updated_before: old_merge_request.updated_at } + context 'when projects require different access levels for merge requests' do + let(:user) { create(:user) } - merge_requests = described_class.new(user, params).execute + let(:public_project) { create(:project, :public) } + let(:internal) { create(:project, :internal) } + let(:private_project) { create(:project, :private) } + let(:public_with_private_repo) { create(:project, :public, :repository, :repository_private) } + let(:internal_with_private_repo) { create(:project, :internal, :repository, :repository_private) } - expect(merge_requests).to contain_exactly(old_merge_request) - end + let(:merge_requests) { described_class.new(user, {}).execute } - it 'filters by updated_after and updated_before' do - params = { - project_id: new_project.id, - updated_after: old_merge_request.updated_at, - updated_before: new_merge_request.updated_at - } + let!(:mr_public) { create(:merge_request, source_project: public_project) } + let!(:mr_private) { create(:merge_request, source_project: private_project) } + let!(:mr_internal) { create(:merge_request, source_project: internal) } + let!(:mr_private_repo_access) { create(:merge_request, source_project: public_with_private_repo) } + let!(:mr_internal_private_repo_access) { create(:merge_request, source_project: internal_with_private_repo) } - merge_requests = described_class.new(user, params).execute + context 'with admin user' do + let(:user) { create(:user, :admin) } - expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + it 'returns all merge requests' do + expect(merge_requests).to eq( + [mr_internal_private_repo_access, mr_private_repo_access, mr_internal, mr_private, mr_public] + ) end end @@ -293,19 +337,85 @@ describe MergeRequestsFinder do expect(merge_requests).to be_empty end end - end - describe '#row_count', :request_store do - it 'returns the number of rows for the default state' do - finder = described_class.new(user) + context 'with external user' do + let(:user) { create(:user, :external) } - expect(finder.row_count).to eq(7) + it 'returns only public merge requests' do + expect(merge_requests).to eq([mr_public]) + end end - it 'returns the number of rows for a given state' do - finder = described_class.new(user, state: 'closed') + context 'with authenticated user' do + it 'returns public and internal merge requests' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + + context 'being added to the private project' do + context 'as a guest' do + before do + private_project.add_guest(user) + end + + it 'does not return merge requests from the private project' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + end + + context 'as a developer' do + before do + private_project.add_developer(user) + end + + it 'returns merge requests from the private project' do + expect(merge_requests).to eq([mr_internal, mr_private, mr_public]) + end + end + end - expect(finder.row_count).to eq(1) + context 'being added to the public project with private repo access' do + context 'as a guest' do + before do + public_with_private_repo.add_guest(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + end + + context 'as a reporter' do + before do + public_with_private_repo.add_reporter(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_private_repo_access, mr_internal, mr_public]) + end + end + end + + context 'being added to the internal project with private repo access' do + context 'as a guest' do + before do + internal_with_private_repo.add_guest(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + end + + context 'as a reporter' do + before do + internal_with_private_repo.add_reporter(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_internal_private_repo_access, mr_internal, mr_public]) + end + end + end end end end -- cgit v1.2.1 From 3bdff7aadfc68222086518a365496fb22357cb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Tue, 5 Mar 2019 15:21:29 +0000 Subject: Rename ClusterUpdateAppWorker to ClusterPatchAppWorker - This is to avoid colision with EE ClusterUpdateAppWorker --- .../groups/clusters/applications_controller_spec.rb | 4 ++-- .../projects/clusters/applications_controller_spec.rb | 4 ++-- spec/features/projects/clusters/applications_spec.rb | 2 +- spec/services/clusters/applications/update_service_spec.rb | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb index 84da43fe2ef..16a63536ea6 100644 --- a/spec/controllers/groups/clusters/applications_controller_spec.rb +++ b/spec/controllers/groups/clusters/applications_controller_spec.rb @@ -105,7 +105,7 @@ describe Groups::Clusters::ApplicationsController do context "when cluster and app exists" do it "schedules an application update" do - expect(ClusterUpdateAppWorker).to receive(:perform_async).with(application.name, anything).once + expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once is_expected.to have_http_status(:no_content) @@ -138,7 +138,7 @@ describe Groups::Clusters::ApplicationsController do describe 'security' do before do - allow(ClusterUpdateAppWorker).to receive(:perform_async) + allow(ClusterPatchAppWorker).to receive(:perform_async) end it_behaves_like 'a secure endpoint' diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb index 247adf3f8c7..cd1a01f8acc 100644 --- a/spec/controllers/projects/clusters/applications_controller_spec.rb +++ b/spec/controllers/projects/clusters/applications_controller_spec.rb @@ -106,7 +106,7 @@ describe Projects::Clusters::ApplicationsController do context "when cluster and app exists" do it "schedules an application update" do - expect(ClusterUpdateAppWorker).to receive(:perform_async).with(application.name, anything).once + expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once is_expected.to have_http_status(:no_content) @@ -139,7 +139,7 @@ describe Projects::Clusters::ApplicationsController do describe 'security' do before do - allow(ClusterUpdateAppWorker).to receive(:perform_async) + allow(ClusterPatchAppWorker).to receive(:perform_async) end it_behaves_like 'a secure endpoint' diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index 7eabcda286a..3784203a6b4 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -135,7 +135,7 @@ describe 'Clusters Applications', :js do it 'can then update the domain' do page.within('.js-cluster-application-row-knative') do - expect(ClusterUpdateAppWorker).to receive(:perform_async) + expect(ClusterPatchAppWorker).to receive(:perform_async) expect(domainname_form_value).to eq('domain.example.org') diff --git a/spec/services/clusters/applications/update_service_spec.rb b/spec/services/clusters/applications/update_service_spec.rb index 22ad698f77d..2d299882af0 100644 --- a/spec/services/clusters/applications/update_service_spec.rb +++ b/spec/services/clusters/applications/update_service_spec.rb @@ -14,12 +14,12 @@ describe Clusters::Applications::UpdateService do describe '#execute' do before do - allow(ClusterUpdateAppWorker).to receive(:perform_async) + allow(ClusterPatchAppWorker).to receive(:perform_async) end context 'application is not installed' do it 'raises Clusters::Applications::BaseService::InvalidApplicationError' do - expect(ClusterUpdateAppWorker).not_to receive(:perform_async) + expect(ClusterPatchAppWorker).not_to receive(:perform_async) expect { subject } .to raise_exception { Clusters::Applications::BaseService::InvalidApplicationError } @@ -46,8 +46,8 @@ describe Clusters::Applications::UpdateService do expect(application.reload).to be_scheduled end - it 'schedules ClusterUpdateAppWorker' do - expect(ClusterUpdateAppWorker).to receive(:perform_async) + it 'schedules ClusterPatchAppWorker' do + expect(ClusterPatchAppWorker).to receive(:perform_async) subject end @@ -59,7 +59,7 @@ describe Clusters::Applications::UpdateService do end it 'raises StateMachines::InvalidTransition' do - expect(ClusterUpdateAppWorker).not_to receive(:perform_async) + expect(ClusterPatchAppWorker).not_to receive(:perform_async) expect { subject } .to raise_exception { StateMachines::InvalidTransition } -- cgit v1.2.1 From 01b1076bd869a5bbcbaf6df55e825cc39f705e2a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 5 Mar 2019 15:46:00 +0000 Subject: Apply suggestion to spec/graphql/types/query_type_spec.rb --- spec/graphql/types/query_type_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index fd75b8f700b..07c61ea7647 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -29,7 +29,7 @@ describe GitlabSchema.types['Query'] do is_expected.to have_graphql_resolver(Resolvers::MetadataResolver) end - it 'authorizes with log_in' do + it 'authorizes with read_instance_metadata' do is_expected.to require_graphql_authorizations(:read_instance_metadata) end end -- cgit v1.2.1 From e3eb241f5d71160db4535a4db19f0ea1944d2f0b Mon Sep 17 00:00:00 2001 From: Constance Okoghenun Date: Tue, 5 Mar 2019 18:08:39 +0000 Subject: Added tightLists option to copy_as_gfm markdown serializer This removes extraneous newlines when copying comments with an ordered list. --- spec/features/markdown/copy_as_gfm_spec.rb | 11 ----------- spec/javascripts/behaviors/copy_as_gfm_spec.js | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index 60ddb02da2c..c30ac9c4ae2 100644 --- a/spec/features/markdown/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb @@ -55,15 +55,10 @@ describe 'Copy as GFM', :js do To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). * Manage Git repositories with fine grained access controls that keep your code secure - * Perform code reviews and enhance collaboration with merge requests - * Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications - * Each project can also have an issue tracker, issue board, and a wiki - * Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises - * Completely free and open source (MIT Expat license) GFM ) @@ -116,13 +111,11 @@ describe 'Copy as GFM', :js do <<~GFM, * [ ] Unchecked task - * [x] Checked task GFM <<~GFM 1. [ ] Unchecked ordered task - 1. [x] Checked ordered task GFM ) @@ -551,7 +544,6 @@ describe 'Copy as GFM', :js do <<~GFM, * List item - * List item 2 GFM @@ -565,7 +557,6 @@ describe 'Copy as GFM', :js do # nested lists <<~GFM, * Nested - * Lists GFM @@ -578,7 +569,6 @@ describe 'Copy as GFM', :js do <<~GFM, 1. Ordered list item - 1. Ordered list item 2 GFM @@ -592,7 +582,6 @@ describe 'Copy as GFM', :js do # nested ordered list <<~GFM, 1. Nested - 1. Ordered lists GFM diff --git a/spec/javascripts/behaviors/copy_as_gfm_spec.js b/spec/javascripts/behaviors/copy_as_gfm_spec.js index ca849f75860..d653fca0988 100644 --- a/spec/javascripts/behaviors/copy_as_gfm_spec.js +++ b/spec/javascripts/behaviors/copy_as_gfm_spec.js @@ -100,7 +100,7 @@ describe('CopyAsGFM', () => { simulateCopy(); setTimeout(() => { - const expectedGFM = '* List Item1\n\n* List Item2'; + const expectedGFM = '* List Item1\n* List Item2'; expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM); done(); @@ -114,7 +114,7 @@ describe('CopyAsGFM', () => { simulateCopy(); setTimeout(() => { - const expectedGFM = '1. List Item1\n\n1. List Item2'; + const expectedGFM = '1. List Item1\n1. List Item2'; expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM); done(); -- cgit v1.2.1 From ba98e91c067444e5af8dee392921126390af78b6 Mon Sep 17 00:00:00 2001 From: Andrew Fontaine Date: Tue, 5 Mar 2019 18:33:10 +0000 Subject: Add Component to Confirm Environment Rollback Ensure a vue.js component to confirm the re-deployment/rollback of an environment pops up when rollback/re-deploy has been clicked so the user must confirm their actions before accidentally rolling back or re-deploying an environment. Only on the `environment-table` view. --- .../environments/confirm_rollback_modal_spec.js | 70 ++++++++++++++++++++++ .../environments/environment_rollback_spec.js | 32 +++++++++- .../_confirm_rollback_modal_spec.html.rb | 63 +++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 spec/javascripts/environments/confirm_rollback_modal_spec.js create mode 100644 spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb (limited to 'spec') diff --git a/spec/javascripts/environments/confirm_rollback_modal_spec.js b/spec/javascripts/environments/confirm_rollback_modal_spec.js new file mode 100644 index 00000000000..05715bce38f --- /dev/null +++ b/spec/javascripts/environments/confirm_rollback_modal_spec.js @@ -0,0 +1,70 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlModal } from '@gitlab/ui'; +import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; +import eventHub from '~/environments/event_hub'; + +describe('Confirm Rollback Modal Component', () => { + let environment; + + beforeEach(() => { + environment = { + name: 'test', + last_deployment: { + commit: { + short_id: 'abc0123', + }, + }, + modalId: 'test', + }; + }); + + it('should show "Rollback" when isLastDeployment is false', () => { + const component = shallowMount(ConfirmRollbackModal, { + propsData: { + environment: { + ...environment, + isLastDeployment: false, + }, + }, + }); + const modal = component.find(GlModal); + + expect(modal.attributes('title')).toContain('Rollback'); + expect(modal.attributes('title')).toContain('test'); + expect(modal.attributes('ok-title')).toBe('Rollback'); + expect(modal.text()).toContain('commit abc0123'); + expect(modal.text()).toContain('Are you sure you want to continue?'); + }); + + it('should show "Re-deploy" when isLastDeployment is true', () => { + const component = shallowMount(ConfirmRollbackModal, { + propsData: { + environment: { + ...environment, + isLastDeployment: true, + }, + }, + }); + const modal = component.find(GlModal); + + expect(modal.attributes('title')).toContain('Re-deploy'); + expect(modal.attributes('title')).toContain('test'); + expect(modal.attributes('ok-title')).toBe('Re-deploy'); + expect(modal.text()).toContain('commit abc0123'); + expect(modal.text()).toContain('Are you sure you want to continue?'); + }); + + it('should emit the "rollback" event when "ok" is clicked', () => { + environment = { ...environment, isLastDeployment: true }; + const component = shallowMount(ConfirmRollbackModal, { + propsData: { + environment, + }, + }); + const eventHubSpy = spyOn(eventHub, '$emit'); + const modal = component.find(GlModal); + modal.vm.$emit('ok'); + + expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', environment); + }); +}); diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/javascripts/environments/environment_rollback_spec.js index 79f33c5bc8a..8c47f6a12c0 100644 --- a/spec/javascripts/environments/environment_rollback_spec.js +++ b/spec/javascripts/environments/environment_rollback_spec.js @@ -1,8 +1,11 @@ import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import { GlButton } from '@gitlab/ui'; +import eventHub from '~/environments/event_hub'; import rollbackComp from '~/environments/components/environment_rollback.vue'; describe('Rollback Component', () => { - const retryURL = 'https://gitlab.com/retry'; + const retryUrl = 'https://gitlab.com/retry'; let RollbackComponent; beforeEach(() => { @@ -13,8 +16,9 @@ describe('Rollback Component', () => { const component = new RollbackComponent({ el: document.querySelector('.test-dom-element'), propsData: { - retryUrl: retryURL, + retryUrl, isLastDeployment: true, + environment: {}, }, }).$mount(); @@ -25,11 +29,33 @@ describe('Rollback Component', () => { const component = new RollbackComponent({ el: document.querySelector('.test-dom-element'), propsData: { - retryUrl: retryURL, + retryUrl, isLastDeployment: false, + environment: {}, }, }).$mount(); expect(component.$el).toHaveSpriteIcon('redo'); }); + + it('should emit a "rollback" event on button click', () => { + const eventHubSpy = spyOn(eventHub, '$emit'); + const component = shallowMount(RollbackComponent, { + propsData: { + retryUrl, + environment: { + name: 'test', + }, + }, + }); + const button = component.find(GlButton); + + button.vm.$emit('click'); + + expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', { + retryUrl, + isLastDeployment: true, + name: 'test', + }); + }); }); diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb new file mode 100644 index 00000000000..54ec4f32856 --- /dev/null +++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'projects/deployments/_confirm_rollback_modal' do + let(:environment) { create(:environment, :with_review_app) } + let(:deployments) { environment.deployments } + let(:project) { environment.project } + + before do + assign(:environment, environment) + assign(:deployments, deployments) + assign(:project, project) + end + + context 'when re-deploying last deployment' do + let(:deployment) { deployments.first } + + before do + allow(view).to receive(:deployment).and_return(deployment) + end + + it 'shows "re-deploy"' do + render + + expect(rendered).to have_selector('h4', text: "Re-deploy environment #{environment.name}?") + expect(rendered).to have_selector('p', text: "This action will relaunch the job for commit #{deployment.short_sha}, putting the environment in a previous version. Are you sure you want to continue?") + expect(rendered).to have_selector('a.btn-danger', text: 'Re-deploy') + end + + it 'links to re-deploying the environment' do + expected_link = retry_project_job_path(environment.project, deployment.deployable) + + render + + expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Re-deploy') + end + end + + context 'when rolling back to previous deployment' do + let(:deployment) { create(:deployment, environment: environment) } + + before do + allow(view).to receive(:deployment).and_return(deployment) + end + + it 'shows "rollback"' do + render + + expect(rendered).to have_selector('h4', text: "Rollback environment #{environment.name}?") + expect(rendered).to have_selector('p', text: "This action will run the job defined by staging for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?") + expect(rendered).to have_selector('a.btn-danger', text: 'Rollback') + end + + it 'links to re-deploying the environment' do + expected_link = retry_project_job_path(environment.project, deployment.deployable) + + render + + expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Rollback') + end + end +end -- cgit v1.2.1 From c8c0ea6c52d46ce63d838d1e739355d4deace434 Mon Sep 17 00:00:00 2001 From: Horatiu Eugen Vlad Date: Sat, 27 Oct 2018 00:39:00 +0200 Subject: Align EmailValidator to validate_email gem implementation. Renamed EmailValidator to DeviseEmailValidator to avoid 'email:' naming collision with ActiveModel::Validations::EmailValidator in 'validates' statement. Make use of the options attribute of the parent class ActiveModel::EachValidator. Add more options: regex. --- spec/validators/devise_email_validator_spec.rb | 94 ++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 spec/validators/devise_email_validator_spec.rb (limited to 'spec') diff --git a/spec/validators/devise_email_validator_spec.rb b/spec/validators/devise_email_validator_spec.rb new file mode 100644 index 00000000000..7860b659bd3 --- /dev/null +++ b/spec/validators/devise_email_validator_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DeviseEmailValidator do + let!(:user) { build(:user, public_email: 'test@example.com') } + subject { validator.validate(user) } + + describe 'validations' do + context 'by default' do + let(:validator) { described_class.new(attributes: [:public_email]) } + + it 'allows when email is valid' do + subject + + expect(user.errors).to be_empty + end + + it 'returns error when email is invalid' do + user.public_email = 'invalid' + + subject + + expect(user.errors).to be_present + expect(user.errors.first[1]).to eq 'is invalid' + end + + it 'returns error when email is nil' do + user.public_email = nil + + subject + + expect(user.errors).to be_present + end + + it 'returns error when email is blank' do + user.public_email = '' + + subject + + expect(user.errors).to be_present + expect(user.errors.first[1]).to eq 'is invalid' + end + end + end + + context 'when regexp is set as Regexp' do + let(:validator) { described_class.new(attributes: [:public_email], regexp: /[0-9]/) } + + it 'allows when value match' do + user.public_email = '1' + + subject + + expect(user.errors).to be_empty + end + + it 'returns error when value does not match' do + subject + + expect(user.errors).to be_present + end + end + + context 'when regexp is set as String' do + it 'raise argument error' do + expect { described_class.new( { regexp: 'something' } ) }.to raise_error ArgumentError + end + end + + context 'when allow_nil is set to true' do + let(:validator) { described_class.new(attributes: [:public_email], allow_nil: true) } + + it 'allows when email is nil' do + user.public_email = nil + + subject + + expect(user.errors).to be_empty + end + end + + context 'when allow_blank is set to true' do + let(:validator) { described_class.new(attributes: [:public_email], allow_blank: true) } + + it 'allows when email is blank' do + user.public_email = '' + + subject + + expect(user.errors).to be_empty + end + end +end -- cgit v1.2.1 From 90527b9f842d53595fb14a97a8f8ad19d9dc505a Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Wed, 23 Jan 2019 16:50:19 +0800 Subject: Add full option for blob diff action Returns all diff lines for frontend if full is true. Turn UnfoldForm into presenter, and move controller logic to presenter. --- spec/controllers/projects/blob_controller_spec.rb | 12 ++++ spec/presenters/blobs/unfold_presenter_spec.rb | 84 +++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 spec/presenters/blobs/unfold_presenter_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 38957e96798..250c4aad87f 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -192,6 +192,18 @@ describe Projects::BlobController do expect(match_line['type']).to be_nil end + + it 'returns all lines if "full" is true' do + commit_id = project.repository.commit('master').id + blob = project.repository.blob_at(commit_id, 'CHANGELOG') + do_get(full: true, from_merge_request: true, bottom: true) + + match_lines = JSON.parse(response.body) + + expect(match_lines.size).to eq(blob.lines.count - 1) + expect(match_lines.last['type']).to be_nil + expect(match_lines.last['text']).to include(blob.lines.last) + end end end end diff --git a/spec/presenters/blobs/unfold_presenter_spec.rb b/spec/presenters/blobs/unfold_presenter_spec.rb new file mode 100644 index 00000000000..c4d7d457705 --- /dev/null +++ b/spec/presenters/blobs/unfold_presenter_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Blobs::UnfoldPresenter do + include FakeBlobHelpers + + let(:project) { create(:project, :repository) } + let(:blob) { fake_blob(path: 'foo', data: "1\n2\n3") } + let(:subject) { described_class.new(blob, params) } + + describe '#initialize' do + context 'when full is false' do + let(:params) { { full: false, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } } + + it 'sets attributes' do + result = subject + + expect(result.full?).to eq(false) + expect(result.since).to eq(2) + expect(result.to).to eq(3) + expect(result.bottom).to eq(false) + expect(result.offset).to eq(1) + expect(result.indent).to eq(1) + end + end + + context 'when full is true' do + let(:params) { { full: true, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } } + + it 'sets other attributes' do + result = subject + + expect(result.full?).to eq(true) + expect(result.since).to eq(1) + expect(result.to).to eq(blob.lines.size) + expect(result.bottom).to eq(false) + expect(result.offset).to eq(0) + expect(result.indent).to eq(0) + end + end + end + + describe '#lines' do + context 'when scope is specified' do + let(:params) { { since: 2, to: 3 } } + + it 'returns lines cropped by params' do + expect(subject.lines.size).to eq(2) + expect(subject.lines[0]).to include('LC2') + expect(subject.lines[1]).to include('LC3') + end + end + + context 'when full is true' do + let(:params) { { full: true } } + + it 'returns all lines' do + expect(subject.lines.size).to eq(3) + expect(subject.lines[0]).to include('LC1') + expect(subject.lines[1]).to include('LC2') + expect(subject.lines[2]).to include('LC3') + end + end + end + + describe '#match_line_text' do + context 'when bottom is true' do + let(:params) { { since: 2, to: 3, bottom: true } } + + it 'returns empty string' do + expect(subject.match_line_text).to eq('') + end + end + + context 'when bottom is false' do + let(:params) { { since: 2, to: 3, bottom: false } } + + it 'returns match line string' do + expect(subject.match_line_text).to eq("@@ -2,1+2,1 @@") + end + end + end +end -- cgit v1.2.1 From 2609c2a73e8f8efc4fdd5e13c5378ffae7e4f9ef Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Wed, 6 Mar 2019 15:51:22 +0800 Subject: Fix spec description and outdated comments --- spec/controllers/projects/blob_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 250c4aad87f..27093141ed9 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -173,7 +173,7 @@ describe Projects::BlobController do expect(match_line['type']).to be_nil end - it 'adds bottom match line when "t"o is less than blob size' do + it 'adds bottom match line when "to" is less than blob size' do do_get(since: 1, to: 5, offset: 10, from_merge_request: true, bottom: true) match_line = JSON.parse(response.body).last @@ -183,7 +183,7 @@ describe Projects::BlobController do expect(match_line['meta_data']).to have_key('new_pos') end - it 'does not add bottom match line when "to" is less than blob size' do + it 'does not add bottom match line when "to" is equal to blob size' do commit_id = project.repository.commit('master').id blob = project.repository.blob_at(commit_id, 'CHANGELOG') do_get(since: 1, to: blob.lines.count, offset: 10, from_merge_request: true, bottom: true) -- cgit v1.2.1 From b14de8e1f519b9b874033f783051814129af176c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 28 Feb 2019 14:14:15 +0000 Subject: Add option to expand diff to full file The user can also toggle between the diff changes and the full file diff. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/19054 --- .../merge_request/maintainer_edits_fork_spec.rb | 2 +- .../diffs/components/diff_file_header_spec.js | 67 ++++++- .../diffs/mock_data/diff_discussions.js | 1 + spec/javascripts/diffs/store/actions_spec.js | 130 +++++++++++++ spec/javascripts/diffs/store/mutations_spec.js | 62 +++++++ spec/javascripts/diffs/store/utils_spec.js | 45 +++++ spec/lib/gitlab/diff/file_spec.rb | 201 ++++++++++++++++----- 7 files changed, 465 insertions(+), 43 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb index aead98dae23..b35f985126c 100644 --- a/spec/features/merge_request/maintainer_edits_fork_spec.rb +++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb @@ -26,7 +26,7 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js visit project_merge_request_path(target_project, merge_request) click_link 'Changes' wait_for_requests - first('.js-file-title').click_link 'Edit' + first('.js-file-title').find('.js-edit-blob').click wait_for_requests end diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 1201f066d2f..66c5b17b825 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -23,6 +23,9 @@ describe('diff_file_header', () => { }); beforeEach(() => { + gon.features = { + expandDiffFullFile: true, + }; const diffFile = diffDiscussionMock.diff_file; diffFile.added_lines = 2; @@ -382,7 +385,7 @@ describe('diff_file_header', () => { props.diffFile.edit_path = '/'; vm = mountComponentWithStore(Component, { props, store }); - expect(vm.$el.querySelector('.js-edit-blob')).toContainText('Edit'); + expect(vm.$el.querySelector('.js-edit-blob')).not.toBe(null); }); it('should not show edit button when file is deleted', () => { @@ -576,4 +579,66 @@ describe('diff_file_header', () => { }); }); }); + + describe('expand full file button', () => { + beforeEach(() => { + props.addMergeRequestButtons = true; + props.diffFile.edit_path = '/'; + }); + + it('does not render button', () => { + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.js-expand-file')).toBe(null); + }); + + it('renders button', () => { + props.diffFile.is_fully_expanded = false; + + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.js-expand-file')).not.toBe(null); + }); + + it('shows fully expanded text', () => { + props.diffFile.is_fully_expanded = false; + props.diffFile.isShowingFullFile = true; + + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show changes only'); + }); + + it('shows expand text', () => { + props.diffFile.is_fully_expanded = false; + + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show full file'); + }); + + it('renders loading icon', () => { + props.diffFile.is_fully_expanded = false; + props.diffFile.isLoadingFullFile = true; + + vm = mountComponentWithStore(Component, { props, store }); + + expect(vm.$el.querySelector('.js-expand-file .loading-container')).not.toBe(null); + }); + + it('calls toggleFullDiff on click', () => { + props.diffFile.is_fully_expanded = false; + + vm = mountComponentWithStore(Component, { props, store }); + + spyOn(vm.$store, 'dispatch').and.stub(); + + vm.$el.querySelector('.js-expand-file').click(); + + expect(vm.$store.dispatch).toHaveBeenCalledWith( + 'diffs/toggleFullDiff', + props.diffFile.file_path, + ); + }); + }); }); diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index 4a091b4580b..fd5dd611383 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -288,6 +288,7 @@ export default { external_storage: null, old_path_html: 'CHANGELOG_OLD', new_path_html: 'CHANGELOG', + is_fully_expanded: true, context_lines_path: '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff', highlighted_diff_lines: [ diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index e47c7906fcb..070bfb2ccd0 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -30,6 +30,11 @@ import actions, { setRenderTreeList, setShowWhitespace, setRenderIt, + requestFullDiff, + receiveFullDiffSucess, + receiveFullDiffError, + fetchFullDiff, + toggleFullDiff, } from '~/diffs/store/actions'; import eventHub from '~/notes/event_hub'; import * as types from '~/diffs/store/mutation_types'; @@ -847,4 +852,129 @@ describe('DiffsStoreActions', () => { testAction(setRenderIt, 'file', {}, [{ type: types.RENDER_FILE, payload: 'file' }], [], done); }); }); + + describe('requestFullDiff', () => { + it('commits REQUEST_FULL_DIFF', done => { + testAction( + requestFullDiff, + 'file', + {}, + [{ type: types.REQUEST_FULL_DIFF, payload: 'file' }], + [], + done, + ); + }); + }); + + describe('receiveFullDiffSucess', () => { + it('commits REQUEST_FULL_DIFF', done => { + testAction( + receiveFullDiffSucess, + { filePath: 'test', data: 'test' }, + {}, + [{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test', data: 'test' } }], + [], + done, + ); + }); + }); + + describe('receiveFullDiffError', () => { + it('commits REQUEST_FULL_DIFF', done => { + testAction( + receiveFullDiffError, + 'file', + {}, + [{ type: types.RECEIVE_FULL_DIFF_ERROR, payload: 'file' }], + [], + done, + ); + }); + }); + + describe('fetchFullDiff', () => { + let mock; + let scrollToElementSpy; + + beforeEach(() => { + scrollToElementSpy = spyOnDependency(actions, 'scrollToElement').and.stub(); + + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('success', () => { + beforeEach(() => { + mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(200, ['test']); + }); + + it('dispatches receiveFullDiffSucess', done => { + testAction( + fetchFullDiff, + { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' }, + null, + [], + [{ type: 'receiveFullDiffSucess', payload: { filePath: 'test', data: ['test'] } }], + done, + ); + }); + + it('scrolls to element', done => { + fetchFullDiff( + { dispatch() {} }, + { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' }, + ) + .then(() => { + expect(scrollToElementSpy).toHaveBeenCalledWith('#test'); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(500); + }); + + it('dispatches receiveFullDiffError', done => { + testAction( + fetchFullDiff, + { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' }, + null, + [], + [{ type: 'receiveFullDiffError', payload: 'test' }], + done, + ); + }); + }); + }); + + describe('toggleFullDiff', () => { + let state; + + beforeEach(() => { + state = { + diffFiles: [{ file_path: 'test', isShowingFullFile: false }], + }; + }); + + it('dispatches fetchFullDiff when file is not expanded', done => { + testAction( + toggleFullDiff, + 'test', + state, + [], + [ + { type: 'requestFullDiff', payload: 'test' }, + { type: 'fetchFullDiff', payload: state.diffFiles[0] }, + ], + done, + ); + }); + }); }); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 09ee691b602..270e7d75666 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -680,4 +680,66 @@ describe('DiffsStoreMutations', () => { expect(state.showWhitespace).toBe(false); }); }); + + describe('REQUEST_FULL_DIFF', () => { + it('sets isLoadingFullFile to true', () => { + const state = { + diffFiles: [{ file_path: 'test', isLoadingFullFile: false }], + }; + + mutations[types.REQUEST_FULL_DIFF](state, 'test'); + + expect(state.diffFiles[0].isLoadingFullFile).toBe(true); + }); + }); + + describe('RECEIVE_FULL_DIFF_ERROR', () => { + it('sets isLoadingFullFile to false', () => { + const state = { + diffFiles: [{ file_path: 'test', isLoadingFullFile: true }], + }; + + mutations[types.RECEIVE_FULL_DIFF_ERROR](state, 'test'); + + expect(state.diffFiles[0].isLoadingFullFile).toBe(false); + }); + }); + + describe('RECEIVE_FULL_DIFF_SUCCESS', () => { + it('sets isLoadingFullFile to false', () => { + const state = { + diffFiles: [ + { + file_path: 'test', + isLoadingFullFile: true, + isShowingFullFile: false, + highlighted_diff_lines: [], + parallel_diff_lines: [], + }, + ], + }; + + mutations[types.RECEIVE_FULL_DIFF_SUCCESS](state, { filePath: 'test', data: [] }); + + expect(state.diffFiles[0].isLoadingFullFile).toBe(false); + }); + + it('sets isShowingFullFile to true', () => { + const state = { + diffFiles: [ + { + file_path: 'test', + isLoadingFullFile: true, + isShowingFullFile: false, + highlighted_diff_lines: [], + parallel_diff_lines: [], + }, + ], + }; + + mutations[types.RECEIVE_FULL_DIFF_SUCCESS](state, { filePath: 'test', data: [] }); + + expect(state.diffFiles[0].isShowingFullFile).toBe(true); + }); + }); }); diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index 599ea9cd420..1f877910125 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -781,4 +781,49 @@ describe('DiffsStoreUtils', () => { ]); }); }); + + describe('convertExpandLines', () => { + it('converts expanded lines to normal lines', () => { + const diffLines = [ + { + type: 'match', + old_line: 1, + new_line: 1, + }, + { + type: '', + old_line: 2, + new_line: 2, + }, + ]; + + const lines = utils.convertExpandLines({ + diffLines, + data: [{ text: 'expanded' }], + typeKey: 'type', + oldLineKey: 'old_line', + newLineKey: 'new_line', + mapLine: ({ line, oldLine, newLine }) => ({ + ...line, + old_line: oldLine, + new_line: newLine, + }), + }); + + expect(lines).toEqual([ + { + text: 'expanded', + new_line: 1, + old_line: 1, + discussions: [], + hasForm: false, + }, + { + type: '', + old_line: 2, + new_line: 2, + }, + ]); + }); + }); }); diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 862590268ca..611c3e946ed 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -8,6 +8,47 @@ describe Gitlab::Diff::File do let(:diff) { commit.raw_diffs.first } let(:diff_file) { described_class.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } + def create_file(file_name, content) + Files::CreateService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name, + file_content: content + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + def update_file(file_name, content) + Files::UpdateService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name, + file_content: content + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + def delete_file(file_name) + Files::DeleteService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + describe '#diff_lines' do let(:diff_lines) { diff_file.diff_lines } @@ -675,47 +716,6 @@ describe Gitlab::Diff::File do end let(:branch_name) { 'master' } - def create_file(file_name, content) - Files::CreateService.new( - project, - project.owner, - commit_message: 'Update', - start_branch: branch_name, - branch_name: branch_name, - file_path: file_name, - file_content: content - ).execute - - project.commit(branch_name).diffs.diff_files.first - end - - def update_file(file_name, content) - Files::UpdateService.new( - project, - project.owner, - commit_message: 'Update', - start_branch: branch_name, - branch_name: branch_name, - file_path: file_name, - file_content: content - ).execute - - project.commit(branch_name).diffs.diff_files.first - end - - def delete_file(file_name) - Files::DeleteService.new( - project, - project.owner, - commit_message: 'Update', - start_branch: branch_name, - branch_name: branch_name, - file_path: file_name - ).execute - - project.commit(branch_name).diffs.diff_files.first - end - context 'when empty file is created' do it 'returns true' do diff_file = create_file('empty.md', '') @@ -751,4 +751,123 @@ describe Gitlab::Diff::File do end end end + + describe '#fully_expanded?' do + let(:project) do + create(:project, :custom_repo, files: {}) + end + let(:branch_name) { 'master' } + + context 'when empty file is created' do + it 'returns true' do + diff_file = create_file('empty.md', '') + + expect(diff_file.fully_expanded?).to be_truthy + end + end + + context 'when empty file is deleted' do + it 'returns true' do + create_file('empty.md', '') + diff_file = delete_file('empty.md') + + expect(diff_file.fully_expanded?).to be_truthy + end + end + + context 'when short file with last line removed' do + it 'returns true' do + create_file('with-content.md', (1..3).to_a.join("\n")) + diff_file = update_file('with-content.md', (1..2).to_a.join("\n")) + + expect(diff_file.fully_expanded?).to be_truthy + end + end + + context 'when a single line is added to empty file' do + it 'returns true' do + create_file('empty.md', '') + diff_file = update_file('empty.md', 'new content') + + expect(diff_file.fully_expanded?).to be_truthy + end + end + + context 'when single line file is changed' do + it 'returns true' do + create_file('file.md', 'foo') + diff_file = update_file('file.md', 'bar') + + expect(diff_file.fully_expanded?).to be_truthy + end + end + + context 'when long file is changed' do + before do + create_file('file.md', (1..999).to_a.join("\n")) + end + + context 'when first line is removed' do + it 'returns true' do + diff_file = update_file('file.md', (2..999).to_a.join("\n")) + + expect(diff_file.fully_expanded?).to be_falsey + end + end + + context 'when last line is removed' do + it 'returns true' do + diff_file = update_file('file.md', (1..998).to_a.join("\n")) + + expect(diff_file.fully_expanded?).to be_falsey + end + end + + context 'when first and last lines are removed' do + it 'returns false' do + diff_file = update_file('file.md', (2..998).to_a.join("\n")) + + expect(diff_file.fully_expanded?).to be_falsey + end + end + + context 'when first and last lines are changed' do + it 'returns false' do + content = (2..998).to_a + content.prepend('a') + content.append('z') + content = content.join("\n") + + diff_file = update_file('file.md', content) + + expect(diff_file.fully_expanded?).to be_falsey + end + end + + context 'when every line are changed' do + it 'returns true' do + diff_file = update_file('file.md', "hi\n" * 999) + + expect(diff_file.fully_expanded?).to be_truthy + end + end + + context 'when all contents are cleared' do + it 'returns true' do + diff_file = update_file('file.md', "") + + expect(diff_file.fully_expanded?).to be_truthy + end + end + + context 'when file is binary' do + it 'returns true' do + diff_file = update_file('file.md', (1..998).to_a.join("\n")) + allow(diff_file).to receive(:binary?).and_return(true) + + expect(diff_file.fully_expanded?).to be_truthy + end + end + end + end end -- cgit v1.2.1 From e05a86cecdf52a0ec1f0f4ce4f30287f881b8ea2 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 5 Mar 2019 16:12:27 +0000 Subject: Allow all personal snippets to be accessed by API Previously, you could only access personal snippets in the API if you had authored them. The documentation doesn't state that this is the case, and it's quite surprising. --- spec/requests/api/snippets_spec.rb | 76 ++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 7c8512f7589..d600076e9fb 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -84,10 +84,17 @@ describe API::Snippets do end describe 'GET /snippets/:id/raw' do - let(:snippet) { create(:personal_snippet, author: user) } + set(:author) { create(:user) } + set(:snippet) { create(:personal_snippet, :private, author: author) } + + it 'requires authentication' do + get api("/snippets/#{snippet.id}", nil) + + expect(response).to have_gitlab_http_status(401) + end it 'returns raw text' do - get api("/snippets/#{snippet.id}/raw", user) + get api("/snippets/#{snippet.id}/raw", author) expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq 'text/plain' @@ -95,38 +102,83 @@ describe API::Snippets do end it 'forces attachment content disposition' do - get api("/snippets/#{snippet.id}/raw", user) + get api("/snippets/#{snippet.id}/raw", author) expect(headers['Content-Disposition']).to match(/^attachment/) end it 'returns 404 for invalid snippet id' do - get api("/snippets/1234/raw", user) + snippet.destroy + + get api("/snippets/#{snippet.id}/raw", author) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end + + it 'hides private snippets from ordinary users' do + get api("/snippets/#{snippet.id}/raw", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'shows internal snippets to ordinary users' do + internal_snippet = create(:personal_snippet, :internal, author: author) + + get api("/snippets/#{internal_snippet.id}/raw", user) + + expect(response).to have_gitlab_http_status(200) + end end describe 'GET /snippets/:id' do - let(:snippet) { create(:personal_snippet, author: user) } + set(:admin) { create(:user, :admin) } + set(:author) { create(:user) } + set(:private_snippet) { create(:personal_snippet, :private, author: author) } + set(:internal_snippet) { create(:personal_snippet, :internal, author: author) } + + it 'requires authentication' do + get api("/snippets/#{private_snippet.id}", nil) + + expect(response).to have_gitlab_http_status(401) + end it 'returns snippet json' do - get api("/snippets/#{snippet.id}", user) + get api("/snippets/#{private_snippet.id}", author) expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(snippet.title) - expect(json_response['description']).to eq(snippet.description) - expect(json_response['file_name']).to eq(snippet.file_name) - expect(json_response['visibility']).to eq(snippet.visibility) + expect(json_response['title']).to eq(private_snippet.title) + expect(json_response['description']).to eq(private_snippet.description) + expect(json_response['file_name']).to eq(private_snippet.file_name) + expect(json_response['visibility']).to eq(private_snippet.visibility) + end + + it 'shows private snippets to an admin' do + get api("/snippets/#{private_snippet.id}", admin) + + expect(response).to have_gitlab_http_status(200) + end + + it 'hides private snippets from an ordinary user' do + get api("/snippets/#{private_snippet.id}", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'shows internal snippets to an ordinary user' do + get api("/snippets/#{internal_snippet.id}", user) + + expect(response).to have_gitlab_http_status(200) end it 'returns 404 for invalid snippet id' do - get api("/snippets/1234", user) + private_snippet.destroy + + get api("/snippets/#{private_snippet.id}", admin) expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Not found') + expect(json_response['message']).to eq('404 Snippet Not Found') end end -- cgit v1.2.1 From d72a61b41d1aaf6b0ed9041b32fa411e8907d4f8 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Wed, 6 Mar 2019 14:39:53 +0800 Subject: Handle transformed notes from polling response Transforms notes when the note was transformed in some other tab --- spec/javascripts/notes/stores/mutation_spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index aa08bf59a7f..4a640d589fb 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import mutations from '~/notes/stores/mutations'; +import { DISCUSSION_NOTE } from '~/notes/constants'; import { note, discussionMock, @@ -326,6 +327,18 @@ describe('Notes Store mutations', () => { expect(state.discussions[0].notes[0].note).toEqual('Foo'); }); + + it('transforms an individual note to discussion', () => { + const state = { + discussions: [individualNote], + }; + + const transformedNote = { ...individualNote.notes[0], type: DISCUSSION_NOTE }; + + mutations.UPDATE_NOTE(state, transformedNote); + + expect(state.discussions[0].individual_note).toEqual(false); + }); }); describe('CLOSE_ISSUE', () => { -- cgit v1.2.1 From 50776d2d298c3b6c98e2531f116ca43ba10dcda4 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 1 Mar 2019 19:05:04 +0900 Subject: Expose merge request entity for pipelines Add preload Fix ok Write tests test only postgresql ok add more test ; Improve wording Add changelog Fix --- spec/presenters/merge_request_presenter_spec.rb | 23 +++++++++++ .../merge_request_for_pipeline_entity_spec.rb | 29 ++++++++++++++ spec/serializers/pipeline_entity_spec.rb | 45 ++++++++++++++++++++++ spec/serializers/pipeline_serializer_spec.rb | 38 ++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 spec/serializers/merge_request_for_pipeline_entity_spec.rb (limited to 'spec') diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index bafcddebbb7..02cefcbc916 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -392,6 +392,29 @@ describe MergeRequestPresenter do end end + describe '#target_branch_path' do + subject do + described_class.new(resource, current_user: user).target_branch_path + end + + context 'when target branch exists' do + it 'returns path' do + allow(resource).to receive(:target_branch_exists?) { true } + + is_expected + .to eq("/#{resource.source_project.full_path}/branches/#{resource.target_branch}") + end + end + + context 'when target branch does not exist' do + it 'returns nil' do + allow(resource).to receive(:target_branch_exists?) { false } + + is_expected.to be_nil + end + end + end + describe '#source_branch_with_namespace_link' do subject do described_class.new(resource, current_user: user).source_branch_with_namespace_link diff --git a/spec/serializers/merge_request_for_pipeline_entity_spec.rb b/spec/serializers/merge_request_for_pipeline_entity_spec.rb new file mode 100644 index 00000000000..e49b45bc7d7 --- /dev/null +++ b/spec/serializers/merge_request_for_pipeline_entity_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe MergeRequestForPipelineEntity do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:request) { EntityRequest.new(project: project) } + let(:merge_request) { create(:merge_request, target_project: project, source_project: project) } + let(:presenter) { MergeRequestPresenter.new(merge_request, current_user: user) } + + let(:entity) do + described_class.new(presenter, request: request) + end + + before do + project.add_developer(user) + end + + context 'as json' do + subject { entity.as_json } + + it 'exposes needed attributes' do + expect(subject).to include( + :iid, :path, :title, + :source_branch, :source_branch_path, + :target_branch, :target_branch_path + ) + end + end +end diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 774486dcb6d..11040862129 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe PipelineEntity do + include Gitlab::Routing + set(:user) { create(:user) } let(:request) { double('request') } @@ -128,5 +130,48 @@ describe PipelineEntity do .to eq 'CI/CD YAML configuration error!' end end + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:project) { merge_request.target_project } + let(:pipeline) { merge_request.merge_request_pipelines.first } + + it 'makes detached flag true' do + expect(subject[:flags][:detached]).to be_truthy + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it 'has merge request information' do + expect(subject[:merge_request][:iid]).to eq(merge_request.iid) + + expect(project_merge_request_path(project, merge_request)) + .to include(subject[:merge_request][:path]) + + expect(subject[:merge_request][:title]).to eq(merge_request.title) + + expect(subject[:merge_request][:source_branch]) + .to eq(merge_request.source_branch) + + expect(project_branch_path(project, merge_request.source_branch)) + .to include(subject[:merge_request][:source_branch_path]) + + expect(subject[:merge_request][:target_branch]) + .to eq(merge_request.target_branch) + + expect(project_branch_path(project, merge_request.target_branch)) + .to include(subject[:merge_request][:target_branch_path]) + end + end + + context 'when user is an external user' do + it 'has no merge request information' do + expect(subject[:merge_request]).to be_nil + end + end + end end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 2bdcb2a45f6..a21487938a0 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -97,6 +97,44 @@ describe PipelineSerializer do end end + context 'when there are pipelines for merge requests' do + let(:resource) { Ci::Pipeline.all } + + let!(:merge_request_1) do + create(:merge_request, + :with_merge_request_pipeline, + target_project: project, + target_branch: 'master', + source_project: project, + source_branch: 'feature-1') + end + + let!(:merge_request_2) do + create(:merge_request, + :with_merge_request_pipeline, + target_project: project, + target_branch: 'master', + source_project: project, + source_branch: 'feature-2') + end + + before do + project.add_developer(user) + end + + it 'includes merge requests information' do + expect(subject.all? { |entry| entry[:merge_request].present? }).to be_truthy + end + + it 'preloads related merge requests', :postgresql do + recorded = ActiveRecord::QueryRecorder.new { subject } + + expect(recorded.log) + .to include("SELECT \"merge_requests\".* FROM \"merge_requests\" " \ + "WHERE \"merge_requests\".\"id\" IN (#{merge_request_1.id}, #{merge_request_2.id})") + end + end + describe 'number of queries when preloaded' do subject { serializer.represent(resource, preload: true) } let(:resource) { Ci::Pipeline.all } -- cgit v1.2.1 From 1263c79095c30fbdeecb26935aded531163c1b3e Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 19 Feb 2019 21:26:07 +0000 Subject: Add PersistentUserCallout factory method Use factory method to DRY safe instantiation. Add tests for PersistentUserCallout. --- spec/javascripts/persistent_user_callout_spec.js | 88 ++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 spec/javascripts/persistent_user_callout_spec.js (limited to 'spec') diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js new file mode 100644 index 00000000000..2fdfff3db03 --- /dev/null +++ b/spec/javascripts/persistent_user_callout_spec.js @@ -0,0 +1,88 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import PersistentUserCallout from '~/persistent_user_callout'; +import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; + +describe('PersistentUserCallout', () => { + const dismissEndpoint = '/dismiss'; + const featureName = 'feature'; + + function createFixture() { + const fixture = document.createElement('div'); + fixture.innerHTML = ` +
    + +
    + `; + + return fixture; + } + + describe('dismiss', () => { + let button; + let mockAxios; + let persistentUserCallout; + + beforeEach(() => { + const fixture = createFixture(); + const container = fixture.querySelector('.container'); + button = fixture.querySelector('.js-close'); + mockAxios = new MockAdapter(axios); + persistentUserCallout = new PersistentUserCallout(container); + spyOn(persistentUserCallout.container, 'remove'); + }); + + afterEach(() => { + mockAxios.restore(); + }); + + it('POSTs endpoint and removes container when clicking close', done => { + mockAxios.onPost(dismissEndpoint).replyOnce(200); + + button.click(); + + setTimeoutPromise() + .then(() => { + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + expect(mockAxios.history.post[0].data).toBe( + JSON.stringify({ feature_name: featureName }), + ); + }) + .then(done) + .catch(done.fail); + }); + + it('invokes Flash when the dismiss request fails', done => { + const Flash = spyOnDependency(PersistentUserCallout, 'Flash'); + mockAxios.onPost(dismissEndpoint).replyOnce(500); + + button.click(); + + setTimeoutPromise() + .then(() => { + expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); + expect(Flash).toHaveBeenCalledWith( + 'An error occurred while dismissing the alert. Refresh the page and try again.', + ); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('factory', () => { + it('returns an instance of PersistentUserCallout with the provided container property', () => { + const fixture = createFixture(); + + expect(PersistentUserCallout.factory(fixture) instanceof PersistentUserCallout).toBe(true); + }); + + it('returns undefined if container is falsey', () => { + expect(PersistentUserCallout.factory()).toBe(undefined); + }); + }); +}); -- cgit v1.2.1 From de5aef3bbda9217e32fc91c24280f78869c9c9c1 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Wed, 6 Mar 2019 10:44:59 +0000 Subject: Accept force option on commit via API When `force` is set to `true` and `start_branch` is set, the branch will be ovewritten with the new commit based on the `HEAD` of the `start_branch`. This commit includes changes to update the `gitaly-proto` gem. --- spec/services/files/multi_service_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'spec') diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb index 84c48d63c64..6842fa9f435 100644 --- a/spec/services/files/multi_service_spec.rb +++ b/spec/services/files/multi_service_spec.rb @@ -235,6 +235,22 @@ describe Files::MultiService do expect(blob).to be_present end end + + context 'when force is set to true and branch already exists' do + let(:commit_params) do + { + commit_message: commit_message, + branch_name: 'feature', + start_branch: 'master', + actions: actions, + force: true + } + end + + it 'is still a success' do + expect(subject.execute[:status]).to eq(:success) + end + end end def update_file(path) -- cgit v1.2.1 From ee8cb2d1b37c5c0d990a35af29be2877092eef29 Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Fri, 15 Jun 2018 16:05:24 -0600 Subject: PUT MergeRequest API endpoint - accept labels as an array --- spec/requests/api/merge_requests_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 6272bb38d59..79acb76a957 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1137,6 +1137,19 @@ describe API::MergeRequests do expect(json_response['labels']).to include '&' end + it 'also accepts labels as an array' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + title: 'new issue', + labels: ['label', 'label?', 'label&foo', '?', '&'] + + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + it 'does not update state when title is empty' do put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: 'close', title: nil } -- cgit v1.2.1 From 4db83367868a7530e43ea635d3916fb1c20dd4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20R=C3=BCttimann?= Date: Wed, 6 Mar 2019 11:06:21 +0000 Subject: API: delete job_artifacts of a single job --- spec/requests/api/jobs_spec.rb | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 3defe8bbf51..ed2ef4c730b 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -321,6 +321,49 @@ describe API::Jobs do end end + describe 'DELETE /projects/:id/jobs/:job_id/artifacts' do + let!(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) } + + before do + delete api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user) + end + + context 'when user is anonymous' do + let(:api_user) { nil } + + it 'does not delete artifacts' do + expect(job.job_artifacts.size).to eq 2 + end + + it 'returns status 401 (unauthorized)' do + expect(response).to have_http_status :unauthorized + end + end + + context 'with developer' do + it 'does not delete artifacts' do + expect(job.job_artifacts.size).to eq 2 + end + + it 'returns status 403 (forbidden)' do + expect(response).to have_http_status :forbidden + end + end + + context 'with authorized user' do + let(:maintainer) { create(:project_member, :maintainer, project: project).user } + let!(:api_user) { maintainer } + + it 'deletes artifacts' do + expect(job.job_artifacts.size).to eq 0 + end + + it 'returns status 204 (no content)' do + expect(response).to have_http_status :no_content + end + end + end + describe 'GET /projects/:id/jobs/:job_id/artifacts/:artifact_path' do context 'when job has artifacts' do let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) } -- cgit v1.2.1 From be3578d24585c7f330103cc546eb50416cd951d3 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Fri, 1 Mar 2019 16:37:22 +0200 Subject: Add array support for labels * Support label parameter as comma separated and array of strings for merge requests and issues api endpoints --- spec/requests/api/issues_spec.rb | 218 ++++++++++++++++- spec/requests/api/merge_requests_spec.rb | 257 ++++++++++++++++----- .../requests/api/merge_requests_list.rb | 31 +++ 3 files changed, 439 insertions(+), 67 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index f35dabf5d0f..a5434d3ea80 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -271,7 +271,14 @@ describe API::Issues do end it 'returns an array of labeled issues' do - get api("/issues", user), params: { labels: label.title } + get api('/issues', user), params: { labels: label.title } + + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label.title]) + end + + it 'returns an array of labeled issues with labels param as array' do + get api('/issues', user), params: { labels: [label.title] } expect_paginated_array_response(issue.id) expect(json_response.first['labels']).to eq([label.title]) @@ -284,7 +291,20 @@ describe API::Issues do create(:label_link, label: label_b, target: issue) create(:label_link, label: label_c, target: issue) - get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } + get api('/issues', user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } + + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) + end + + it 'returns an array of labeled issues when all labels matches with labels param as array' do + label_b = create(:label, title: 'foo', project: project) + label_c = create(:label, title: 'bar', project: project) + + create(:label_link, label: label_b, target: issue) + create(:label_link, label: label_c, target: issue) + + get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] } expect_paginated_array_response(issue.id) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) @@ -296,8 +316,22 @@ describe API::Issues do expect_paginated_array_response([]) end + it 'returns an empty array if no issue matches labels with labels param as array' do + get api('/issues', user), params: { labels: %w(foo bar) } + + expect_paginated_array_response([]) + end + it 'returns an array of labeled issues matching given state' do - get api("/issues", user), params: { labels: label.title, state: :opened } + get api('/issues', user), params: { labels: label.title, state: :opened } + + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label.title]) + expect(json_response.first['state']).to eq('opened') + end + + it 'returns an array of labeled issues matching given state with labels param as array' do + get api('/issues', user), params: { labels: [label.title], state: :opened } expect_paginated_array_response(issue.id) expect(json_response.first['labels']).to eq([label.title]) @@ -305,25 +339,43 @@ describe API::Issues do end it 'returns an empty array if no issue matches labels and state filters' do - get api("/issues", user), params: { labels: label.title, state: :closed } + get api('/issues', user), params: { labels: label.title, state: :closed } expect_paginated_array_response([]) end it 'returns an array of issues with any label' do - get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY } + get api('/issues', user), params: { labels: IssuesFinder::FILTER_ANY } + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of issues with any label with labels param as array' do + get api('/issues', user), params: { labels: [IssuesFinder::FILTER_ANY] } expect_paginated_array_response(issue.id) end it 'returns an array of issues with no label' do - get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE } + get api('/issues', user), params: { labels: IssuesFinder::FILTER_NONE } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of issues with no label with labels param as array' do + get api('/issues', user), params: { labels: [IssuesFinder::FILTER_NONE] } expect_paginated_array_response(closed_issue.id) end it 'returns an array of issues with no label when using the legacy No+Label filter' do - get api("/issues", user), params: { labels: "No Label" } + get api('/issues', user), params: { labels: 'No Label' } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of issues with no label when using the legacy No+Label filter with labels param as array' do + get api('/issues', user), params: { labels: ['No Label'] } expect_paginated_array_response(closed_issue.id) end @@ -588,12 +640,25 @@ describe API::Issues do expect(json_response.first['labels']).to eq([group_label.title]) end + it 'returns an array of labeled group issues with labels param as array' do + get api(base_url, user), params: { labels: [group_label.title] } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['labels']).to eq([group_label.title]) + end + it 'returns an array of labeled group issues where all labels match' do get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" } expect_paginated_array_response([]) end + it 'returns an array of labeled group issues where all labels match with labels param as array' do + get api(base_url, user), params: { labels: [group_label.title, 'foo', 'bar'] } + + expect_paginated_array_response([]) + end + it 'returns issues matching given search string for title' do get api(base_url, user), params: { search: group_issue.title } @@ -619,6 +684,19 @@ describe API::Issues do expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) end + it 'returns an array of labeled issues when all labels matches with labels param as array' do + label_b = create(:label, title: 'foo', project: group_project) + label_c = create(:label, title: 'bar', project: group_project) + + create(:label_link, label: label_b, target: group_issue) + create(:label_link, label: label_c, target: group_issue) + + get api(base_url, user), params: { labels: [group_label.title, label_b.title, label_c.title] } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) + end + it 'returns an array of issues found by iids' do get api(base_url, user), params: { iids: [group_issue.iid] } @@ -645,12 +723,25 @@ describe API::Issues do expect(json_response.first['id']).to eq(group_issue.id) end + it 'returns an array of group issues with any label with labels param as array' do + get api(base_url, user), params: { labels: [IssuesFinder::FILTER_ANY] } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['id']).to eq(group_issue.id) + end + it 'returns an array of group issues with no label' do get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE } expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id]) end + it 'returns an array of group issues with no label with labels param as array' do + get api(base_url, user), params: { labels: [IssuesFinder::FILTER_NONE] } + + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id]) + end + it 'returns an empty array if no issue matches milestone' do get api(base_url, user), params: { milestone: group_empty_milestone.title } @@ -842,6 +933,12 @@ describe API::Issues do expect_paginated_array_response(issue.id) end + it 'returns an array of labeled project issues with labels param as array' do + get api("#{base_url}/issues", user), params: { labels: [label.title] } + + expect_paginated_array_response(issue.id) + end + it 'returns an array of labeled issues when all labels matches' do label_b = create(:label, title: 'foo', project: project) label_c = create(:label, title: 'bar', project: project) @@ -854,6 +951,18 @@ describe API::Issues do expect_paginated_array_response(issue.id) end + it 'returns an array of labeled issues when all labels matches with labels param as array' do + label_b = create(:label, title: 'foo', project: project) + label_c = create(:label, title: 'bar', project: project) + + create(:label_link, label: label_b, target: issue) + create(:label_link, label: label_c, target: issue) + + get api("#{base_url}/issues", user), params: { labels: [label.title, label_b.title, label_c.title] } + + expect_paginated_array_response(issue.id) + end + it 'returns issues matching given search string for title' do get api("#{base_url}/issues?search=#{issue.title}", user) @@ -890,12 +999,24 @@ describe API::Issues do expect_paginated_array_response(issue.id) end + it 'returns an array of project issues with any label with labels param as array' do + get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_ANY] } + + expect_paginated_array_response(issue.id) + end + it 'returns an array of project issues with no label' do get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE } expect_paginated_array_response([confidential_issue.id, closed_issue.id]) end + it 'returns an array of project issues with no label with labels param as array' do + get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_NONE] } + + expect_paginated_array_response([confidential_issue.id, closed_issue.id]) + end + it 'returns an empty array if no project issue matches labels' do get api("#{base_url}/issues", user), params: { labels: 'foo,bar' } @@ -1215,6 +1336,19 @@ describe API::Issues do expect(json_response['assignees'].first['name']).to eq(user2.name) end + it 'creates a new project issue with labels param as array' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['description']).to be_nil + expect(json_response['labels']).to eq(%w(label label2)) + expect(json_response['confidential']).to be_falsy + expect(json_response['assignee']['name']).to eq(user2.name) + expect(json_response['assignees'].first['name']).to eq(user2.name) + end + it 'creates a new confidential project issue' do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', confidential: true } @@ -1269,6 +1403,20 @@ describe API::Issues do expect(json_response['labels']).to include '&' end + it 'allows special label names with labels param as array' do + post api("/projects/#{project.id}/issues", user), + params: { + title: 'new issue', + labels: ['label', 'label?', 'label&foo, ?, &'] + } + expect(response.status).to eq(201) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + it 'returns 400 if title is too long' do post api("/projects/#{project.id}/issues", user), params: { title: 'g' * 256 } @@ -1377,6 +1525,12 @@ describe API::Issues do post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: 'label, label2' } end.not_to change { project.labels.count } end + + it 'cannot create new labels with labels param as array' do + expect do + post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: %w(label label2) } + end.not_to change { project.labels.count } + end end end @@ -1444,6 +1598,21 @@ describe API::Issues do expect(json_response['labels']).to include '&' end + it 'allows special label names with labels param as array' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { + title: 'updated title', + labels: ['label', 'label?', 'label&foo, ?, &'] + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + context 'confidential issues' do it "returns 403 for non project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member), @@ -1603,6 +1772,16 @@ describe API::Issues do expect(json_response['updated_at']).to be > Time.now end + it 'removes all labels and touches the record with labels param as array' do + Timecop.travel(1.minute.from_now) do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: [''] } + end + + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to eq([]) + expect(json_response['updated_at']).to be > Time.now + end + it 'updates labels and touches the record' do Timecop.travel(1.minute.from_now) do put api("/projects/#{project.id}/issues/#{issue.iid}", user), @@ -1614,6 +1793,17 @@ describe API::Issues do expect(json_response['updated_at']).to be > Time.now end + it 'updates labels and touches the record with labels param as array' do + Timecop.travel(1.minute.from_now) do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: %w(foo bar) } + end + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to include 'foo' + expect(json_response['labels']).to include 'bar' + expect(json_response['updated_at']).to be > Time.now + end + it 'allows special label names' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' } @@ -1628,6 +1818,20 @@ describe API::Issues do expect(json_response['labels']).to include '&' end + it 'allows special label names with labels param as array' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] } + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label:foo' + expect(json_response['labels']).to include 'label-bar' + expect(json_response['labels']).to include 'label_bar' + expect(json_response['labels']).to include 'label/bar' + expect(json_response['labels']).to include 'label?bar' + expect(json_response['labels']).to include 'label&bar' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + it 'returns 400 if title is too long' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { title: 'g' * 256 } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 79acb76a957..6752a1c36bf 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -617,26 +617,115 @@ describe API::MergeRequests do end end - describe "POST /projects/:id/merge_requests" do + describe 'POST /projects/:id/merge_requests' do context 'between branches projects' do - it "returns merge_request" do - post api("/projects/#{project.id}/merge_requests", user), - params: { - title: 'Test merge_request', - source_branch: 'feature_conflict', - target_branch: 'master', - author: user, - labels: 'label, label2', - milestone_id: milestone.id, - squash: true - } + context 'different labels' do + let(:params) do + { + title: 'Test merge_request', + source_branch: 'feature_conflict', + target_branch: 'master', + author_id: user.id, + milestone_id: milestone.id, + squash: true + } + end - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('Test merge_request') - expect(json_response['labels']).to eq(%w(label label2)) - expect(json_response['milestone']['id']).to eq(milestone.id) - expect(json_response['squash']).to be_truthy - expect(json_response['force_remove_source_branch']).to be_falsy + shared_examples_for 'creates merge request with labels' do + it 'returns merge_request' do + params[:labels] = labels + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('Test merge_request') + expect(json_response['labels']).to eq(%w(label label2)) + expect(json_response['milestone']['id']).to eq(milestone.id) + expect(json_response['squash']).to be_truthy + expect(json_response['force_remove_source_branch']).to be_falsy + end + end + + it_behaves_like 'creates merge request with labels' do + let(:labels) { 'label, label2' } + end + + it_behaves_like 'creates merge request with labels' do + let(:labels) { %w(label label2) } + end + + it_behaves_like 'creates merge request with labels' do + let(:labels) { %w(label label2) } + end + + it 'creates merge request with special label names' do + params[:labels] = 'label, label?, label&foo, ?, &' + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + + it 'creates merge request with special label names as array' do + params[:labels] = ['label', 'label?', 'label&foo, ?, &', '1, 2', 3, 4] + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + expect(json_response['labels']).to include '1' + expect(json_response['labels']).to include '2' + expect(json_response['labels']).to include '3' + expect(json_response['labels']).to include '4' + end + + it 'empty label param does not add any labels' do + params[:labels] = '' + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['labels']).to eq([]) + end + + it 'empty label param as array does not add any labels, but only explicitly as json' do + params[:labels] = [] + post api("/projects/#{project.id}/merge_requests", user), + params: params.to_json, + headers: { 'Content-Type': 'application/json' } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['labels']).to eq([]) + end + + xit 'empty label param as array, does not add any labels' do + params[:labels] = [] + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['labels']).to eq([]) + end + + it 'array with one empty string element does not add labels' do + params[:labels] = [''] + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['labels']).to eq([]) + end + + it 'array with multiple empty string elements, does not add labels' do + params[:labels] = ['', '', ''] + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['labels']).to eq([]) + end end it "returns 422 when source_branch equals target_branch" do @@ -663,23 +752,6 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(400) end - it 'allows special label names' do - post api("/projects/#{project.id}/merge_requests", user), - params: { - title: 'Test merge_request', - source_branch: 'markdown', - target_branch: 'master', - author: user, - labels: 'label, label?, label&foo, ?, &' - } - expect(response).to have_gitlab_http_status(201) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - context 'with existing MR' do before do post api("/projects/#{project.id}/merge_requests", user), @@ -1122,32 +1194,97 @@ describe API::MergeRequests do expect(json_response['force_remove_source_branch']).to be_truthy end - it 'allows special label names' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), - params: { - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' - } + context 'when updating labels' do + it 'allows special label names' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + params: { + title: 'new issue', + labels: 'label, label?, label&foo, ?, &' + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'also accepts labels as an array' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), - title: 'new issue', - labels: ['label', 'label?', 'label&foo', '?', '&'] - - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' + it 'also accepts labels as an array' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + params: { + title: 'new issue', + labels: ['label', 'label?', 'label&foo, ?, &', '1, 2', 3, 4] + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + expect(json_response['labels']).to include '1' + expect(json_response['labels']).to include '2' + expect(json_response['labels']).to include '3' + expect(json_response['labels']).to include '4' + end + + it 'empty label param removes labels' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + params: { + title: 'new issue', + labels: '' + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to eq [] + end + + it 'label param as empty array, but only explicitly as json, removes labels' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + params: { + title: 'new issue', + labels: [] + }.to_json, + headers: { 'Content-Type' => 'application/json' } + + expect(response.status).to eq(200) + expect(json_response['labels']).to eq [] + end + + xit 'empty label as array, removes labels' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + params: { + title: 'new issue', + labels: [] + } + + expect(response.status).to eq(200) + # fails, as grape ommits for some reason empty array as optional param value, so nothing it passed along + expect(json_response['labels']).to eq [] + end + + it 'array with one empty string element removes labels' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + params: { + title: 'new issue', + labels: [''] + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to eq [] + end + + it 'array with multiple empty string elements, removes labels' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), + params: { + title: 'new issue', + labels: ['', '', ''] + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to eq [] + end end it 'does not update state when title is empty' do diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb index 6713ec47ace..32e3b81c3c5 100644 --- a/spec/support/shared_examples/requests/api/merge_requests_list.rb +++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb @@ -186,6 +186,37 @@ shared_examples 'merge requests list' do expect(json_response.length).to eq(0) end + it 'returns an array of labeled merge requests where all labels match' do + path = endpoint_path + "?labels[]=#{label.title}&labels[]=#{label2.title}" + + get api(path, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label2.title, label.title]) + end + + it 'returns an array of merge requests with any label when filtering by any label' do + get api(endpoint_path, user), params: { labels: [" #{label.title} ", " #{label2.title} "] } + + expect_paginated_array_response + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label2.title, label.title]) + expect(json_response.first['id']).to eq(merge_request.id) + end + + it 'returns an array of merge requests with any label when filtering by any label' do + get api(endpoint_path, user), params: { labels: ["#{label.title} , #{label2.title}"] } + + expect_paginated_array_response + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label2.title, label.title]) + expect(json_response.first['id']).to eq(merge_request.id) + end + it 'returns an array of merge requests with any label when filtering by any label' do get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY } -- cgit v1.2.1 From d5aa36f1e0ea9f8253e817ee015a5b1aaa7afb61 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Feb 2019 22:14:03 +0100 Subject: Enable reply_to_individual_notes feature flag by default --- spec/features/merge_request/user_posts_notes_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index 1bbcf455ac7..dc0862be6fc 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -67,7 +67,7 @@ describe 'Merge request > User posts notes', :js do end end - describe 'when reply_to_individual_notes feature flag is not set' do + describe 'when reply_to_individual_notes feature flag is disabled' do before do stub_feature_flags(reply_to_individual_notes: false) visit project_merge_request_path(project, merge_request) @@ -78,9 +78,8 @@ describe 'Merge request > User posts notes', :js do end end - describe 'when reply_to_individual_notes feature flag is set' do + describe 'when reply_to_individual_notes feature flag is not set' do before do - stub_feature_flags(reply_to_individual_notes: true) visit project_merge_request_path(project, merge_request) end -- cgit v1.2.1 From c5f1f7f3dbd5e7094ae3f30823d6c87b7a72121d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 6 Mar 2019 12:18:53 +0000 Subject: Use encrypted runner tokens This makes code to support encrypted runner tokens. This code also finished previously started encryption process. --- .../token_authenticatable_strategies/base_spec.rb | 32 ++-------------------- .../encrypted_spec.rb | 28 ++++++------------- 2 files changed, 12 insertions(+), 48 deletions(-) (limited to 'spec') diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb index 6605f1f5a5f..2a0182b4294 100644 --- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb +++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb @@ -15,7 +15,7 @@ describe TokenAuthenticatableStrategies::Base do context 'when encrypted strategy is specified' do it 'fabricates encrypted strategy object' do - strategy = described_class.fabricate(instance, field, encrypted: true) + strategy = described_class.fabricate(instance, field, encrypted: :required) expect(strategy).to be_a TokenAuthenticatableStrategies::Encrypted end @@ -23,7 +23,7 @@ describe TokenAuthenticatableStrategies::Base do context 'when no strategy is specified' do it 'fabricates insecure strategy object' do - strategy = described_class.fabricate(instance, field, something: true) + strategy = described_class.fabricate(instance, field, something: :required) expect(strategy).to be_a TokenAuthenticatableStrategies::Insecure end @@ -31,35 +31,9 @@ describe TokenAuthenticatableStrategies::Base do context 'when incompatible options are provided' do it 'raises an error' do - expect { described_class.fabricate(instance, field, digest: true, encrypted: true) } + expect { described_class.fabricate(instance, field, digest: true, encrypted: :required) } .to raise_error ArgumentError end end end - - describe '#fallback?' do - context 'when fallback is set' do - it 'recognizes fallback setting' do - strategy = described_class.new(instance, field, fallback: true) - - expect(strategy.fallback?).to be true - end - end - - context 'when fallback is not a valid value' do - it 'raises an error' do - strategy = described_class.new(instance, field, fallback: 'something') - - expect { strategy.fallback? }.to raise_error ArgumentError - end - end - - context 'when fallback is not set' do - it 'raises an error' do - strategy = described_class.new(instance, field, {}) - - expect(strategy.fallback?).to eq false - end - end - end end diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb index 93cab80cb1f..ca38f86c5ab 100644 --- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb +++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb @@ -12,19 +12,9 @@ describe TokenAuthenticatableStrategies::Encrypted do described_class.new(model, 'some_field', options) end - describe '.new' do - context 'when fallback and migration strategies are set' do - let(:options) { { fallback: true, migrating: true } } - - it 'raises an error' do - expect { subject }.to raise_error ArgumentError, /not compatible/ - end - end - end - describe '#find_token_authenticatable' do - context 'when using fallback strategy' do - let(:options) { { fallback: true } } + context 'when using optional strategy' do + let(:options) { { encrypted: :optional } } it 'finds the encrypted resource by cleartext' do allow(model).to receive(:find_by) @@ -50,7 +40,7 @@ describe TokenAuthenticatableStrategies::Encrypted do end context 'when using migration strategy' do - let(:options) { { migrating: true } } + let(:options) { { encrypted: :migrating } } it 'finds the cleartext resource by cleartext' do allow(model).to receive(:find_by) @@ -73,8 +63,8 @@ describe TokenAuthenticatableStrategies::Encrypted do end describe '#get_token' do - context 'when using fallback strategy' do - let(:options) { { fallback: true } } + context 'when using optional strategy' do + let(:options) { { encrypted: :optional } } it 'returns decrypted token when an encrypted token is present' do allow(instance).to receive(:read_attribute) @@ -98,7 +88,7 @@ describe TokenAuthenticatableStrategies::Encrypted do end context 'when using migration strategy' do - let(:options) { { migrating: true } } + let(:options) { { encrypted: :migrating } } it 'returns cleartext token when an encrypted token is present' do allow(instance).to receive(:read_attribute) @@ -127,8 +117,8 @@ describe TokenAuthenticatableStrategies::Encrypted do end describe '#set_token' do - context 'when using fallback strategy' do - let(:options) { { fallback: true } } + context 'when using optional strategy' do + let(:options) { { encrypted: :optional } } it 'writes encrypted token and removes plaintext token and returns it' do expect(instance).to receive(:[]=) @@ -141,7 +131,7 @@ describe TokenAuthenticatableStrategies::Encrypted do end context 'when using migration strategy' do - let(:options) { { migrating: true } } + let(:options) { { encrypted: :migrating } } it 'writes encrypted token and writes plaintext token' do expect(instance).to receive(:[]=) -- cgit v1.2.1 From e371520f465a9f92794d5820faf5c21a893dd77e Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Wed, 6 Mar 2019 12:20:27 +0000 Subject: Allow protected branch creation via web and API This commit includes changes to add `UserAccess#can_create_branch?` which will check whether the user is allowed to create a branch even if it matches a protected branch. This is used in `Gitlab::Checks::BranchCheck` when the branch name matches a protected branch. A `push_to_create_protected_branch` ability in `ProjectPolicy` has been added to allow Developers and above to create protected branches. --- spec/lib/gitlab/checks/branch_check_spec.rb | 100 ++++++++++++++++++++++++++++ spec/models/protected_branch_spec.rb | 28 ++++++++ spec/policies/project_policy_spec.rb | 3 +- 3 files changed, 129 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb index 77366e91dca..f99fc639dbd 100644 --- a/spec/lib/gitlab/checks/branch_check_spec.rb +++ b/spec/lib/gitlab/checks/branch_check_spec.rb @@ -55,6 +55,106 @@ describe Gitlab::Checks::BranchCheck do end end + context 'branch creation' do + let(:oldrev) { '0000000000000000000000000000000000000000' } + let(:ref) { 'refs/heads/feature' } + + context 'protected branch creation feature is disabled' do + before do + stub_feature_flags(protected_branch_creation: false) + end + + context 'user is not allowed to push to protected branch' do + before do + allow(user_access) + .to receive(:can_push_to_branch?) + .and_return(false) + end + + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.') + end + end + + context 'user is allowed to push to protected branch' do + before do + allow(user_access) + .to receive(:can_push_to_branch?) + .and_return(true) + end + + it 'does not raise an error' do + expect { subject.validate! }.not_to raise_error + end + end + end + + context 'protected branch creation feature is enabled' do + context 'user is not allowed to create protected branches' do + before do + allow(user_access) + .to receive(:can_merge_to_branch?) + .with('feature') + .and_return(false) + end + + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.') + end + end + + context 'user is allowed to create protected branches' do + before do + allow(user_access) + .to receive(:can_merge_to_branch?) + .with('feature') + .and_return(true) + + allow(project.repository) + .to receive(:branch_names_contains_sha) + .with(newrev) + .and_return(['branch']) + end + + context "newrev isn't in any protected branches" do + before do + allow(ProtectedBranch) + .to receive(:any_protected?) + .with(project, ['branch']) + .and_return(false) + end + + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.') + end + end + + context 'newrev is included in a protected branch' do + before do + allow(ProtectedBranch) + .to receive(:any_protected?) + .with(project, ['branch']) + .and_return(true) + end + + context 'via web interface' do + let(:protocol) { 'web' } + + it 'allows branch creation' do + expect { subject.validate! }.not_to raise_error + end + end + + context 'via SSH' do + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.') + end + end + end + end + end + end + context 'branch deletion' do let(:newrev) { '0000000000000000000000000000000000000000' } let(:ref) { 'refs/heads/feature' } diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 4c677200ae2..dafe7646366 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -190,4 +190,32 @@ describe ProtectedBranch do end end end + + describe '#any_protected?' do + context 'existing project' do + let(:project) { create(:project, :repository) } + + it 'returns true when any of the branch names match a protected branch via direct match' do + create(:protected_branch, project: project, name: 'foo') + + expect(described_class.any_protected?(project, ['foo', 'production/some-branch'])).to eq(true) + end + + it 'returns true when any of the branch matches a protected branch via wildcard match' do + create(:protected_branch, project: project, name: 'production/*') + + expect(described_class.any_protected?(project, ['foo', 'production/some-branch'])).to eq(true) + end + + it 'returns false when none of branches does not match a protected branch via direct match' do + expect(described_class.any_protected?(project, ['foo'])).to eq(false) + end + + it 'returns false when none of the branches does not match a protected branch via wildcard match' do + create(:protected_branch, project: project, name: 'production/*') + + expect(described_class.any_protected?(project, ['staging/some-branch'])).to eq(false) + end + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 47491f708e9..772d1fbee2b 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -45,8 +45,7 @@ describe ProjectPolicy do let(:base_maintainer_permissions) do %i[ push_to_delete_protected_branch update_project_snippet update_environment - update_deployment admin_project_snippet - admin_project_member admin_note admin_wiki admin_project + update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project admin_commit_status admin_build admin_container_image admin_pipeline admin_environment admin_deployment destroy_release add_cluster daily_statistics -- cgit v1.2.1 From 64abbb78cd3ac467c7a87bd43dbadefc597603e7 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Wed, 6 Mar 2019 12:28:42 +0000 Subject: Add zoom to metrics dashboard Adds the ability to change data zoom for metrics graphs --- spec/javascripts/monitoring/charts/area_spec.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index d334ef7ba4f..1b6fc456ceb 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -7,6 +7,7 @@ import MonitoringMock, { deploymentData } from '../mock_data'; describe('Area component', () => { const mockWidgets = 'mockWidgets'; + const mockSvgPathContent = 'mockSvgPathContent'; let mockGraphData; let areaChart; let spriteSpy; @@ -30,7 +31,7 @@ describe('Area component', () => { }); spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake( - () => new Promise(resolve => resolve()), + () => new Promise(resolve => resolve(mockSvgPathContent)), ); }); @@ -146,13 +147,22 @@ describe('Area component', () => { }); }); - describe('getScatterSymbol', () => { + describe('setSvg', () => { + const mockSvgName = 'mockSvgName'; + beforeEach(() => { - areaChart.vm.getScatterSymbol(); + areaChart.vm.setSvg(mockSvgName); + }); + + it('gets svg path content', () => { + expect(spriteSpy).toHaveBeenCalledWith(mockSvgName); }); - it('gets rocket svg path content for use as deployment data symbol', () => { - expect(spriteSpy).toHaveBeenCalledWith('rocket'); + it('sets svg path content', done => { + areaChart.vm.$nextTick(() => { + expect(areaChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); + done(); + }); }); }); -- cgit v1.2.1 From c08beb5051224dbee52716b0fa9c4dd9fedad5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Wed, 6 Mar 2019 12:48:45 +0000 Subject: Don't use sleep in feature specs --- .../projects/clusters/applications_spec.rb | 33 ++++++---------------- 1 file changed, 8 insertions(+), 25 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index 3784203a6b4..713e25cdcb2 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -31,6 +31,8 @@ describe 'Clusters Applications', :js do let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } it 'user can install applications' do + wait_for_requests + page.within('.js-cluster-application-row-helm') do expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install') @@ -44,6 +46,8 @@ describe 'Clusters Applications', :js do page.within('.js-cluster-application-row-helm') do page.find(:css, '.js-cluster-application-install-button').click end + + wait_for_requests end it 'they see status transition' do @@ -52,8 +56,6 @@ describe 'Clusters Applications', :js do expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') - wait_until_app_created!('helm') - Clusters::Cluster.last.application_helm.make_installing! # FE starts polling and update the buttons to "Installing" @@ -113,7 +115,7 @@ describe 'Clusters Applications', :js do click_button 'Install' - wait_until_app_created!('knative') + wait_for_requests expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') @@ -143,7 +145,7 @@ describe 'Clusters Applications', :js do click_button 'Save changes' - wait_until_app_updated!(cluster.application_knative) + wait_for_requests expect(domainname_form_value).to eq('new.domain.example.org') end @@ -202,6 +204,8 @@ describe 'Clusters Applications', :js do page.within('.js-cluster-application-row-ingress') do expect(page).to have_css('.js-cluster-application-install-button:not([disabled])') page.find(:css, '.js-cluster-application-install-button').click + + wait_for_requests end end @@ -238,25 +242,4 @@ describe 'Clusters Applications', :js do end end end - - def wait_until_app_created!(app) - retries = 0 - - while Clusters::Cluster.last.send("application_#{app}").nil? - raise "Timed out waiting for #{app} application to be created in DB" if (retries += 1) > 3 - - sleep(1) - end - end - - def wait_until_app_updated!(app) - retries = 0 - updated_at = app.updated_at - - while updated_at == app.reload.updated_at - raise "Timed out waiting for #{app} application to be created in DB" if (retries += 1) > 3 - - sleep(1) - end - end end -- cgit v1.2.1 From 8a49eeed6db026b0454c2119ab18b0b09d9661ff Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 6 Mar 2019 12:26:44 +0000 Subject: Add failing test for milestone with far-future date When looking for upcoming milestones, we compare the milestone's due date to `NOW()`. However, the due date is a date and `NOW()` is a timestamp, and in Postgres, timestamps can't extend as far in the future as dates can. This test exposes the issue. --- spec/models/milestone_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'spec') diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 77b7042424c..717d4ae4ec0 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -379,6 +379,21 @@ describe Milestone do expect(milestone_ids).to be_empty end end + + context 'when there is a milestone with a date after 294276 AD', :postgresql do + before do + past_milestone_project_1.update!(due_date: Date.new(294277, 1, 1)) + end + + it 'returns the next upcoming open milestone ID for each project and group' do + expect(milestone_ids).to contain_exactly( + current_milestone_project_1.id, + current_milestone_project_2.id, + current_milestone_group_1.id, + current_milestone_group_2.id + ) + end + end end describe '#to_reference' do -- cgit v1.2.1 From 959ad992b6b95458f084569ec6d71c8608c3e3c7 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 28 Feb 2019 13:52:25 -0300 Subject: Ref merge does not fail upon FF config enabled --- .../merge_requests/merge_to_ref_service_spec.rb | 35 +--------------------- 1 file changed, 1 insertion(+), 34 deletions(-) (limited to 'spec') diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 96f2fde7117..a7075aa97da 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -52,6 +52,7 @@ describe MergeRequests::MergeToRefService do expect(result[:status]).to eq(:success) expect(result[:commit_id]).to be_present + expect(result[:source_id]).to eq(merge_request.source_branch_sha) expect(repository.ref_exists?(target_ref)).to be(true) expect(ref_head.id).to eq(result[:commit_id]) end @@ -115,41 +116,7 @@ describe MergeRequests::MergeToRefService do end context 'merge pre-condition checks' do - before do - merge_request.project.update!(merge_method: merge_method) - end - - context 'when semi-linear merge method' do - let(:merge_method) { :rebase_merge } - - it 'return error when MR should be able to fast-forward' do - allow(merge_request).to receive(:should_be_rebased?) { true } - - error_message = 'Fast-forward merge is not possible. Please update your source branch.' - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq(error_message) - end - end - - context 'when fast-forward merge method' do - let(:merge_method) { :ff } - - it 'returns error' do - error_message = "Fast-forward to #{merge_request.merge_ref_path} is currently not supported." - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq(error_message) - end - end - context 'when MR is not mergeable to ref' do - let(:merge_method) { :merge } - it 'returns error' do allow(merge_request).to receive(:mergeable_to_ref?) { false } -- cgit v1.2.1 From ee4ba6ce38cb3edc426a6323e1ef25b5611a4d04 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Sun, 3 Mar 2019 13:46:42 +0100 Subject: Adjust GraphQL helper to query empty fields These adjustments make sure our GraphQL helpers support rendering queries for empty fields like this: { echo(text: "Hello world") } Instead of like this: { echo(text: "Hello world") { } } The latter would be an invalid query, causing parsing errors. --- spec/support/helpers/graphql_helpers.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index d9529262483..6cdc19ac2e5 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -79,12 +79,21 @@ module GraphqlHelpers attributes = attributes_to_graphql(attributes) attributes = "(#{attributes})" if attributes.present? <<~QUERY - #{name}#{attributes} { - #{fields} - } + #{name}#{attributes} + #{wrap_fields(fields)} QUERY end + def wrap_fields(fields) + return unless fields.strip.present? + + <<~FIELDS + { + #{fields} + } + FIELDS + end + def all_graphql_fields_for(class_name, parent_types = Set.new) type = GitlabSchema.types[class_name.to_s] return "" unless type @@ -116,8 +125,8 @@ module GraphqlHelpers end.join(", ") end - def post_graphql(query, current_user: nil, variables: nil) - post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables } + def post_graphql(query, current_user: nil, variables: nil, headers: {}) + post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables }, headers: headers end def post_graphql_mutation(mutation, current_user: nil) -- cgit v1.2.1 From b623932eb303921a721244c707f145e1baf29da0 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Sun, 3 Mar 2019 13:53:03 +0100 Subject: Allow GraphQL requests without CSRF token With this we allow authentication using a session or using personal access token. Authentication using a session, and CSRF token makes it easy to play with GraphQL from the Graphiql endpoint we expose. But we cannot enforce CSRF validity, otherwise authentication for regular API clients would fail when they use personal access tokens to authenticate. --- spec/controllers/graphql_controller_spec.rb | 112 ---------------------------- spec/requests/api/graphql_spec.rb | 86 +++++++++++++++++++++ 2 files changed, 86 insertions(+), 112 deletions(-) delete mode 100644 spec/controllers/graphql_controller_spec.rb create mode 100644 spec/requests/api/graphql_spec.rb (limited to 'spec') diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb deleted file mode 100644 index a0f40874db1..00000000000 --- a/spec/controllers/graphql_controller_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'spec_helper' - -describe GraphqlController do - describe 'execute' do - let(:user) { nil } - - before do - sign_in(user) if user - - run_test_query! - end - - subject { query_response } - - context 'graphql is disabled by feature flag' do - let(:user) { nil } - - before do - stub_feature_flags(graphql: false) - end - - it 'returns 404' do - run_test_query! - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'signed out' do - let(:user) { nil } - - it 'runs the query with current_user: nil' do - is_expected.to eq('echo' => 'nil says: test success') - end - end - - context 'signed in' do - let(:user) { create(:user, username: 'Simon') } - - it 'runs the query with current_user set' do - is_expected.to eq('echo' => '"Simon" says: test success') - end - end - - context 'invalid variables' do - it 'returns an error' do - run_test_query!(variables: "This is not JSON") - - expect(response).to have_gitlab_http_status(422) - expect(json_response['errors'].first['message']).not_to be_nil - end - end - end - - context 'token authentication' do - before do - stub_authentication_activity_metrics(debug: false) - end - - let(:user) { create(:user, username: 'Simon') } - let(:personal_access_token) { create(:personal_access_token, user: user) } - - context "when the 'personal_access_token' param is populated with the personal access token" do - it 'logs the user in' do - expect(authentication_metrics) - .to increment(:user_authenticated_counter) - .and increment(:user_session_override_counter) - .and increment(:user_sessionless_authentication_counter) - - run_test_query!(private_token: personal_access_token.token) - - expect(response).to have_gitlab_http_status(200) - expect(query_response).to eq('echo' => '"Simon" says: test success') - end - end - - context 'when the personal access token has no api scope' do - it 'does not log the user in' do - personal_access_token.update(scopes: [:read_user]) - - run_test_query!(private_token: personal_access_token.token) - - expect(response).to have_gitlab_http_status(200) - - expect(query_response).to eq('echo' => 'nil says: test success') - end - end - - context 'without token' do - it 'shows public data' do - run_test_query! - - expect(query_response).to eq('echo' => 'nil says: test success') - end - end - end - - # Chosen to exercise all the moving parts in GraphqlController#execute - def run_test_query!(variables: { 'text' => 'test success' }, private_token: nil) - query = <<~QUERY - query Echo($text: String) { - echo(text: $text) - } - QUERY - - post :execute, params: { query: query, operationName: 'Echo', variables: variables, private_token: private_token } - end - - def query_response - json_response['data'] - end -end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb new file mode 100644 index 00000000000..cca87c16f27 --- /dev/null +++ b/spec/requests/api/graphql_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'GraphQL' do + include GraphqlHelpers + + let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) } + + context 'graphql is disabled by feature flag' do + before do + stub_feature_flags(graphql: false) + end + + it 'does not generate a route for GraphQL' do + expect { post_graphql(query) }.to raise_error(ActionController::RoutingError) + end + end + + context 'invalid variables' do + it 'returns an error' do + post_graphql(query, variables: "This is not JSON") + + expect(response).to have_gitlab_http_status(422) + expect(json_response['errors'].first['message']).not_to be_nil + end + end + + context 'authentication', :allow_forgery_protection do + let(:user) { create(:user) } + + it 'allows access to public data without authentication' do + post_graphql(query) + + expect(graphql_data['echo']).to eq('nil says: Hello world') + end + + it 'does not authenticate a user with an invalid CSRF' do + login_as(user) + + post_graphql(query, headers: { 'X-CSRF-Token' => 'invalid' }) + + expect(graphql_data['echo']).to eq('nil says: Hello world') + end + + it 'authenticates a user with a valid session token' do + # Create a session to get a CSRF token from + login_as(user) + get('/') + + post '/api/graphql', params: { query: query }, headers: { 'X-CSRF-Token' => response.session['_csrf_token'] } + + expect(graphql_data['echo']).to eq("\"#{user.username}\" says: Hello world") + end + + context 'token authentication' do + let(:token) { create(:personal_access_token) } + + before do + stub_authentication_activity_metrics(debug: false) + end + + it 'Authenticates users with a PAT' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + .and increment(:user_session_override_counter) + .and increment(:user_sessionless_authentication_counter) + + post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token }) + + expect(graphql_data['echo']).to eq("\"#{token.user.username}\" says: Hello world") + end + + context 'when the personal access token has no api scope' do + it 'does not log the user in' do + token.update(scopes: [:read_user]) + + post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token }) + + expect(response).to have_gitlab_http_status(200) + + expect(graphql_data['echo']).to eq('nil says: Hello world') + end + end + end + end +end -- cgit v1.2.1 From 062efe4f7a83fb2b6d951b314692cca9ee8731cd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 2 Mar 2019 09:31:36 -0800 Subject: Significantly reduce N+1 queries in /api/v4/todos endpoint By preloading associations and batching issuable metadata lookups, we can significantly cut the number of SQL queries needed to load the Todos API endpoint. On GitLab.com, my own tests showed my user's SQL queries went from 365 to under 60 SQL queries. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/40378 --- spec/requests/api/todos_spec.rb | 52 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index f121a1d3b78..9f0d5ad5d12 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -8,10 +8,14 @@ describe API::Todos do let(:author_2) { create(:user) } let(:john_doe) { create(:user, username: 'john_doe') } let(:merge_request) { create(:merge_request, source_project: project_1) } + let!(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) } let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } let!(:pending_3) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe) } let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } + let!(:award_emoji_1) { create(:award_emoji, awardable: merge_request, user: author_1, name: 'thumbsup') } + let!(:award_emoji_2) { create(:award_emoji, awardable: pending_1.target, user: author_1, name: 'thumbsup') } + let!(:award_emoji_3) { create(:award_emoji, awardable: pending_2.target, user: author_2, name: 'thumbsdown') } before do project_1.add_developer(john_doe) @@ -34,7 +38,7 @@ describe API::Todos do expect(response.status).to eq(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect(json_response.length).to eq(4) expect(json_response[0]['id']).to eq(pending_3.id) expect(json_response[0]['project']).to be_a Hash expect(json_response[0]['author']).to be_a Hash @@ -45,6 +49,23 @@ describe API::Todos do expect(json_response[0]['state']).to eq('pending') expect(json_response[0]['action_name']).to eq('assigned') expect(json_response[0]['created_at']).to be_present + expect(json_response[0]['target_type']).to eq('Commit') + + expect(json_response[1]['target_type']).to eq('Issue') + expect(json_response[1]['target']['upvotes']).to eq(0) + expect(json_response[1]['target']['downvotes']).to eq(1) + expect(json_response[1]['target']['merge_requests_count']).to eq(0) + + expect(json_response[2]['target_type']).to eq('Issue') + expect(json_response[2]['target']['upvotes']).to eq(1) + expect(json_response[2]['target']['downvotes']).to eq(0) + expect(json_response[2]['target']['merge_requests_count']).to eq(0) + + expect(json_response[3]['target_type']).to eq('MergeRequest') + # Only issues get a merge request count at the moment + expect(json_response[3]['target']['merge_requests_count']).to be_nil + expect(json_response[3]['target']['upvotes']).to eq(1) + expect(json_response[3]['target']['downvotes']).to eq(0) end context 'and using the author filter' do @@ -54,7 +75,7 @@ describe API::Todos do expect(response.status).to eq(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect(json_response.length).to eq(3) end end @@ -67,7 +88,7 @@ describe API::Todos do expect(response.status).to eq(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect(json_response.length).to eq(2) end end @@ -100,7 +121,7 @@ describe API::Todos do expect(response.status).to eq(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect(json_response.length).to eq(3) end end @@ -115,6 +136,27 @@ describe API::Todos do end end end + + it 'avoids N+1 queries', :request_store do + create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) + + get api('/todos', john_doe) + + control = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) } + + merge_request_2 = create(:merge_request, source_project: project_2) + create(:todo, project: project_2, author: author_2, user: john_doe, target: merge_request_2) + + project_3 = create(:project, :repository) + project_3.add_developer(john_doe) + merge_request_3 = create(:merge_request, source_project: project_3) + create(:todo, project: project_3, author: author_2, user: john_doe, target: merge_request_3) + create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) + create(:on_commit_todo, project: project_3, author: author_1, user: john_doe) + + expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control) + expect(response.status).to eq(200) + end end describe 'POST /todos/:id/mark_as_done' do @@ -230,7 +272,7 @@ describe API::Todos do context 'for a merge request' do it_behaves_like 'an issuable', 'merge_requests' do - let(:issuable) { merge_request } + let(:issuable) { create(:merge_request, :simple, source_project: project_1) } end end end -- cgit v1.2.1 From 3288e1a874ec0184f9f27f932748e51c57babf17 Mon Sep 17 00:00:00 2001 From: Andrew Newdigate Date: Fri, 8 Feb 2019 14:19:53 +0200 Subject: Adds the Rubocop ReturnNil cop This style change enforces `return if ...` instead of `return nil if ...` to save maintainers a few minor review points --- spec/support/import_export/export_file_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index ac320934f5a..388b88f0331 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -91,7 +91,7 @@ module ExportFileHelper loop do object_with_parent = deep_find_with_parent(sensitive_word, project_hash) - return nil unless object_with_parent && object_with_parent.object + return unless object_with_parent && object_with_parent.object if is_safe_hash?(object_with_parent.parent, sensitive_word) # It's in the safe list, remove hash and keep looking -- cgit v1.2.1 From dc8ab052cf3139f80b0e4b6c26621574d2c7e2dc Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 20 Feb 2019 04:18:06 +0000 Subject: Update project settings section titles and info Improve wording of project settings section headers. Improve order of sections. Adds section toggling by section title. Translates section titles and descriptions. --- .../projects/settings/project_settings_spec.rb | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/features/projects/settings/project_settings_spec.rb (limited to 'spec') diff --git a/spec/features/projects/settings/project_settings_spec.rb b/spec/features/projects/settings/project_settings_spec.rb new file mode 100644 index 00000000000..7afddc0e712 --- /dev/null +++ b/spec/features/projects/settings/project_settings_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Projects settings' do + set(:project) { create(:project) } + let(:user) { project.owner } + let(:panel) { find('.general-settings', match: :first) } + let(:button) { panel.find('.btn.js-settings-toggle') } + let(:title) { panel.find('.settings-title') } + + before do + sign_in(user) + visit edit_project_path(project) + end + + it 'can toggle sections by clicking the title or button', :js do + expect_toggle_state(:expanded) + + button.click + + expect_toggle_state(:collapsed) + + button.click + + expect_toggle_state(:expanded) + + title.click + + expect_toggle_state(:collapsed) + + title.click + + expect_toggle_state(:expanded) + end + + def expect_toggle_state(state) + is_collapsed = state == :collapsed + + expect(button).to have_content(is_collapsed ? 'Expand' : 'Collapse') + expect(panel[:class]).send(is_collapsed ? 'not_to' : 'to', include('expanded')) + end +end -- cgit v1.2.1 From ce23d6e64d59a8fadcc86bb25a157607322b09e3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 Mar 2019 13:05:55 -0500 Subject: Revert "Merge branch '54924-clean-up-data' into 'master'" This reverts commit c9ecc71ab91b0b55f9aba632f9e7b305191a458c, reversing changes made to c3c8dbf8fa4090bb090071d320a31857eb709d3d. --- ...ean_up_noteable_id_for_notes_on_commits_spec.rb | 34 ---------------------- 1 file changed, 34 deletions(-) delete mode 100644 spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb (limited to 'spec') diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb deleted file mode 100644 index bb3038ada6e..00000000000 --- a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20190228092516_clean_up_noteable_id_for_notes_on_commits.rb') - -describe CleanUpNoteableIdForNotesOnCommits, :migration do - let(:notes) { table(:notes) } - - before do - notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') - notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') - notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') - - notes.create!(noteable_type: 'Issue', noteable_id: 1, note: 'Test') - notes.create!(noteable_type: 'MergeRequest', noteable_id: 1, note: 'Test') - notes.create!(noteable_type: 'Snippet', noteable_id: 1, note: 'Test') - end - - it 'clears noteable_id for notes on commits' do - expect { migrate! }.to change { dirty_notes_on_commits.count }.from(3).to(0) - end - - it 'does not clear noteable_id for other notes' do - expect { migrate! }.not_to change { other_notes.count } - end - - def dirty_notes_on_commits - notes.where(noteable_type: 'Commit').where('noteable_id IS NOT NULL') - end - - def other_notes - notes.where("noteable_type != 'Commit' AND noteable_id IS NOT NULL") - end -end -- cgit v1.2.1 From 2cb45dd0d56156ace389239088822c428d87585c Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 1 Mar 2019 09:20:11 -0300 Subject: Provide reliable source and target IDs Returns the source and target IDs used to create the merge commit on Gitaly. --- spec/requests/api/merge_requests_spec.rb | 12 --- .../merge_requests/merge_to_ref_service_spec.rb | 93 ++++++++++++++-------- 2 files changed, 60 insertions(+), 45 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 6272bb38d59..05b904ab59c 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1060,18 +1060,6 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(404) end - - it "returns 400 when merge method is not supported" do - merge_request.project.update!(merge_method: 'ff') - - put api(url, user) - - expected_error = - 'Fast-forward to refs/merge-requests/1/merge is currently not supported.' - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq(expected_error) - end end describe "PUT /projects/:id/merge_requests/:merge_request_iid" do diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index a7075aa97da..fabca8f6b4a 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -19,27 +19,7 @@ describe MergeRequests::MergeToRefService do end end - set(:user) { create(:user) } - let(:merge_request) { create(:merge_request, :simple) } - let(:project) { merge_request.project } - - before do - project.add_maintainer(user) - end - - describe '#execute' do - let(:service) do - described_class.new(project, user, - commit_message: 'Awesome message', - 'should_remove_source_branch' => true) - end - - def process_merge_to_ref - perform_enqueued_jobs do - service.execute(merge_request) - end - end - + shared_examples_for 'successfully merges to ref with merge method' do it 'writes commit to merge ref' do repository = project.repository target_ref = merge_request.merge_ref_path @@ -53,9 +33,30 @@ describe MergeRequests::MergeToRefService do expect(result[:status]).to eq(:success) expect(result[:commit_id]).to be_present expect(result[:source_id]).to eq(merge_request.source_branch_sha) + expect(result[:target_id]).to eq(merge_request.target_branch_sha) expect(repository.ref_exists?(target_ref)).to be(true) expect(ref_head.id).to eq(result[:commit_id]) end + end + + shared_examples_for 'successfully evaluates pre-condition checks' do + it 'returns error when feature is disabled' do + stub_feature_flags(merge_to_tmp_merge_ref_path: false) + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Feature is not enabled') + end + + it 'returns an error when the failing to process the merge' do + allow(project.repository).to receive(:merge_to_ref).and_return(nil) + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Conflicts detected during merge') + end it 'does not send any mail' do expect { process_merge_to_ref }.not_to change { ActionMailer::Base.deliveries.count } @@ -74,25 +75,31 @@ describe MergeRequests::MergeToRefService do process_merge_to_ref end + end - it 'returns error when feature is disabled' do - stub_feature_flags(merge_to_tmp_merge_ref_path: false) + set(:user) { create(:user) } + let(:merge_request) { create(:merge_request, :simple) } + let(:project) { merge_request.project } - result = service.execute(merge_request) + before do + project.add_maintainer(user) + end - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('Feature is not enabled') + describe '#execute' do + let(:service) do + described_class.new(project, user, commit_message: 'Awesome message', + should_remove_source_branch: true) end - it 'returns an error when the failing to process the merge' do - allow(project.repository).to receive(:merge_to_ref).and_return(nil) - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('Conflicts detected during merge') + def process_merge_to_ref + perform_enqueued_jobs do + service.execute(merge_request) + end end + it_behaves_like 'successfully merges to ref with merge method' + it_behaves_like 'successfully evaluates pre-condition checks' + context 'commit history comparison with regular MergeService' do let(:merge_ref_service) do described_class.new(project, user, {}) @@ -116,7 +123,27 @@ describe MergeRequests::MergeToRefService do end context 'merge pre-condition checks' do + before do + merge_request.project.update!(merge_method: merge_method) + end + + context 'when semi-linear merge method' do + let(:merge_method) { :rebase_merge } + + it_behaves_like 'successfully merges to ref with merge method' + it_behaves_like 'successfully evaluates pre-condition checks' + end + + context 'when fast-forward merge method' do + let(:merge_method) { :ff } + + it_behaves_like 'successfully merges to ref with merge method' + it_behaves_like 'successfully evaluates pre-condition checks' + end + context 'when MR is not mergeable to ref' do + let(:merge_method) { :merge } + it 'returns error' do allow(merge_request).to receive(:mergeable_to_ref?) { false } -- cgit v1.2.1 From 86be6374a46b112c0b2899310b35dc67ce50e988 Mon Sep 17 00:00:00 2001 From: Lucas Charles Date: Thu, 7 Mar 2019 00:34:45 +0000 Subject: Backport security-reports fixtures to CE --- .../gl-dependency-scanning-report.json | 104 ++++++++++++ .../remediations/remediation.patch | 180 +++++++++++++++++++++ .../security-reports/remediations/yarn.lock | 104 ++++++++++++ 3 files changed, 388 insertions(+) create mode 100644 spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json create mode 100644 spec/fixtures/security-reports/remediations/remediation.patch create mode 100644 spec/fixtures/security-reports/remediations/yarn.lock (limited to 'spec') diff --git a/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json new file mode 100644 index 00000000000..c96e831b027 --- /dev/null +++ b/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json @@ -0,0 +1,104 @@ +{ + "version": "2.0", + "vulnerabilities": [ + { + "category": "dependency_scanning", + "name": "Regular Expression Denial of Service", + "message": "Regular Expression Denial of Service in debug", + "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.", + "cve": "yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a", + "severity": "Unknown", + "solution": "Upgrade to latest versions.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "yarn.lock", + "dependency": { + "package": { + "name": "debug" + }, + "version": "1.0.5" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a", + "value": "37283ed4-0380-40d7-ada7-2d994afcc62a", + "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories" + } + ], + "links": [ + { + "url": "https://nodesecurity.io/advisories/534" + }, + { + "url": "https://github.com/visionmedia/debug/issues/501" + }, + { + "url": "https://github.com/visionmedia/debug/pull/504" + } + ] + }, + { + "category": "dependency_scanning", + "name": "Authentication bypass via incorrect DOM traversal and canonicalization", + "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js", + "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.", + "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98", + "severity": "Unknown", + "solution": "Upgrade to fixed version.\r\n", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "yarn.lock", + "dependency": { + "package": { + "name": "saml2-js" + }, + "version": "1.5.0" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98", + "value": "9952e574-7b5b-46fa-a270-aeb694198a98", + "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories" + }, + { + "type": "cve", + "name": "CVE-2017-11429", + "value": "CVE-2017-11429", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429" + } + ], + "links": [ + { + "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279" + }, + { + "url": "https://github.com/Clever/saml2/issues/127" + }, + { + "url": "https://www.kb.cert.org/vuls/id/475445" + } + ] + } + ], + "remediations": [ + { + "fixes": [ + { + "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98" + } + ], + "summary": "Upgrade saml2-js", + "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95YXJuLmxvY2sKKysrIGIveWFybi5sb2NrCkBAIC0yLDEwMyArMiwxMjQgQEAKICMgeWFybiBsb2NrZmlsZSB2MQogCiAKLWFzeW5jQH4wLjIuNzoKLSAgdmVyc2lvbiAiMC4yLjEwIgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy9hc3luYy8tL2FzeW5jLTAuMi4xMC50Z3ojYjZiYmUwYjA2NzRiOWQ3MTk3MDhjYTM4ZGU4YzIzN2NiNTI2YzNkMSIKLQotYXN5bmNAfjEuNS4yOgotICB2ZXJzaW9uICIxLjUuMiIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcvYXN5bmMvLS9hc3luYy0xLjUuMi50Z3ojZWM2YTYxYWU1NjQ4MGMwYzNjYjI0MWM5NTYxOGUyMDg5MmY5NjcyYSIKK2FzeW5jQF4yLjEuNSwgYXN5bmNAXjIuNS4wOgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vYXN5bmMvLS9hc3luYy0yLjYuMS50Z3ojYjI0NWEyM2NhNzE5MzAwNDRlYzUzZmE0NmFhMDBhM2U4N2M2YTYxMCIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mTkVpTDIrQVp0NkFsQXcvMjlDcjBVRGU0c1JBSENwRUhoNTRXTXorQmI3UWZOY0Z3NGgzbG9vZnlKcExlUXM0WXg3eXVxdS8yZExnTTVoS09zNkhsUT09CisgIGRlcGVuZGVuY2llczoKKyAgICBsb2Rhc2ggIl40LjE3LjEwIgogCi1kZWJ1Z0BeMS4wLjQ6Ci0gIHZlcnNpb24gIjEuMC41IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9kZWJ1Zy8tL2RlYnVnLTEuMC41LnRneiNmNzI0MTIxNzQzMGY5OWRlYzRjMmI0NzNlYWI5MjIyOGU4NzRjMmFjIgorZGVidWdAXjIuNi4wOgorICB2ZXJzaW9uICIyLjYuOSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZGVidWcvLS9kZWJ1Zy0yLjYuOS50Z3ojNWQxMjg1MTVkZjEzNGZmMzI3ZTkwYTRjOTNmNGUwNzdhNTM2MzQxZiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1iQzdFbHJkSmFKblBiQVArMUVvdFl2cVpzYjNlY2w1d2k2QmZpNkJKVFVjTm93cDZjdnNwZzBqWHpuUlRLRGptL0U3QWRnRkJWZUFQVk1OY0tHc0hNQT09CiAgIGRlcGVuZGVuY2llczoKICAgICBtcyAiMi4wLjAiCiAKLWVqc0B+MC44LjM6Ci0gIHZlcnNpb24gIjAuOC44IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9lanMvLS9lanMtMC44LjgudGd6I2ZmZGM1NmRjYzM1ZDAyOTI2ZGQ1MGFkMTM0MzliYmM1NDA2MWQ1OTgiCitlanNAXjIuNS42OgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZWpzLy0vZWpzLTIuNi4xLnRneiM0OThlYzBkNDk1NjU1YWJjNmYyM2NkNjE4NjhkOTI2NDY0MDcxYWEwIgorICBpbnRlZ3JpdHkgc2hhNTEyLTB4eTRBL3R3ZnJSQ25raGZrOEVyRGk1RHFkQXNBcWVHeGh0NHhrQ1Vyc3ZoaGJRTnM3RSs0alYwQ043K05LSVkwYUhFNzIrWHZxdEJJWHpEMzFaYlhRPT0KKworbG9kYXNoLW5vZGVAfjIuNC4xOgorICB2ZXJzaW9uICIyLjQuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbG9kYXNoLW5vZGUvLS9sb2Rhc2gtbm9kZS0yLjQuMS50Z3ojZWE4MmY3YjEwMGM3MzNkMWE0MmFmNzY4MDFlNTA2MTA1ZTJhODBlYyIKKyAgaW50ZWdyaXR5IHNoYTEtNm9MM3NRREhNOUdrS3Zkb0FlVUdFRjRxZ093PQorCitsb2Rhc2hAXjQuMTcuMTA6CisgIHZlcnNpb24gIjQuMTcuMTEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL2xvZGFzaC8tL2xvZGFzaC00LjE3LjExLnRneiNiMzllYTYyMjllZjYwN2VjZDg5ZTJjOGRmMTI1MzY4OTFjYWM5YjhkIgorICBpbnRlZ3JpdHkgc2hhNTEyLWNRS2g4aWdvNVFVaFo3bGczOERZV0F4TXZqU0FLRzBBOHdHU1ZpbVAwN1NJVUVLMlVPK2FyU1JLYlJaV3RlbE10TjVWMEhrd2g1cnlPdG8vU3NoWUlnPT0KIAogbXNAMi4wLjA6CiAgIHZlcnNpb24gIjIuMC4wIgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9tcy8tL21zLTIuMC4wLnRneiM1NjA4YWVhZGZjMDBiZTZjMjkwMWRmNWY5ODYxNzg4ZGUwZDU5N2M4IgorICBpbnRlZ3JpdHkgc2hhMS1WZ2l1cmZ3QXZtd3BBZDlmbUdGNGplRFZsOGc9CiAKLW5vZGUtZm9yZ2VAMC4yLjI0OgotICB2ZXJzaW9uICIwLjIuMjQiCi0gIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL25vZGUtZm9yZ2UvLS9ub2RlLWZvcmdlLTAuMi4yNC50Z3ojZmE2Zjg0NmY0MmZhOTNmNjNhMGEzMGM5ZmJmZjdiNGUxMzBlMDg1OCIKK25vZGUtZm9yZ2VAXjAuNy4wOgorICB2ZXJzaW9uICIwLjcuNiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbm9kZS1mb3JnZS8tL25vZGUtZm9yZ2UtMC43LjYudGd6I2ZkZjNiNDE4YWVlMWY5NGYwZWY2NDJjZDYzNDg2Yzc3Y2E5NzI0YWMiCisgIGludGVncml0eSBzaGE1MTItc29sMzBMVXB6MWpRRkJqT0t3Ymp4aWppRTNiNnBqZDc0WXdmRDBmSk9LUGpGK2ZPTktiMllnOHJZZ1M2K2JLNlZEbCsvd2ZyNElZcEM3akR6TFVJZnc9PQogCiBzYW1sMi1qc0BeMS41LjA6Ci0gIHZlcnNpb24gIjEuNS4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYW1sMi1qcy8tL3NhbWwyLWpzLTEuNS4wLnRneiNjMGQyMjY4YTE3OWU3MzI5ZDI5ZWIyNWFhODJkZjU1MDM3NzRiMGQ5IgorICB2ZXJzaW9uICIxLjEyLjQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3NhbWwyLWpzLy0vc2FtbDItanMtMS4xMi40LnRneiNjMjg4ZjIwYmRhNmQyYjkxMDczYjE2Yzk0ZWE3MmYyMjM0OWFjM2IzIgorICBpbnRlZ3JpdHkgc2hhMS13b2p5QzlwdEs1RUhPeGJKVHFjdklqU2F3N009CiAgIGRlcGVuZGVuY2llczoKLSAgICBhc3luYyAifjEuNS4yIgotICAgIGRlYnVnICJeMS4wLjQiCi0gICAgdW5kZXJzY29yZSAifjEuNi4wIgotICAgIHhtbC1jcnlwdG8gIl4wLjguMSIKLSAgICB4bWwtZW5jcnlwdGlvbiAifjAuNy40IgotICAgIHhtbDJqcyAifjAuNC4xIgotICAgIHhtbGJ1aWxkZXIgIn4yLjEuMCIKLSAgICB4bWxkb20gIn4wLjEuMTkiCisgICAgYXN5bmMgIl4yLjUuMCIKKyAgICBkZWJ1ZyAiXjIuNi4wIgorICAgIHVuZGVyc2NvcmUgIl4xLjguMCIKKyAgICB4bWwtY3J5cHRvICJeMC4xMC4wIgorICAgIHhtbC1lbmNyeXB0aW9uICJeMC4xMS4wIgorICAgIHhtbDJqcyAiXjAuNC4wIgorICAgIHhtbGJ1aWxkZXIgIn4yLjIuMCIKKyAgICB4bWxkb20gIl4wLjEuMCIKIAogc2F4QD49MC42LjA6CiAgIHZlcnNpb24gIjEuMi40IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYXgvLS9zYXgtMS4yLjQudGd6IzI4MTYyMzRlMjM3OGJkZGM0ZTUzNTRmYWI1Y2FhODk1ZGY3MTAwZDkiCisgIGludGVncml0eSBzaGE1MTItTnFWRHY5VHBBTlVqRm0wTjh1TTVHeEwzNlVnS2k5L2F0WncreDdZRm5ROGNrd0ZHS3JsNHhYNHlXdHJleTNVSm01blAxa1VibllnTG9wcVdOU1JoV3c9PQogCi11bmRlcnNjb3JlQD49MS41Lng6Cit1bmRlcnNjb3JlQF4xLjguMDoKICAgdmVyc2lvbiAiMS45LjEiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3VuZGVyc2NvcmUvLS91bmRlcnNjb3JlLTEuOS4xLnRneiMwNmRjZTM0YTBlNjhhN2JhYmMyOWIzNjViOGU3NGI4OTI1MjAzOTYxIgorICBpbnRlZ3JpdHkgc2hhNTEyLTUvNGV0bkNrZDljOGd3Z293aTUvb20vbVlPNWFqQ2FPZ2R6ai9vVyswZVFWOVd4S0JEWnc1K3ljbUttZWFUWGpJblMvVzBCenBHTG8yeFIyYUJ3WmRnPT0KIAotdW5kZXJzY29yZUB+MS42LjA6Ci0gIHZlcnNpb24gIjEuNi4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS91bmRlcnNjb3JlLy0vdW5kZXJzY29yZS0xLjYuMC50Z3ojOGIzOGIxMGNhY2RlZjYzMzM3YjhiMjRlNGZmODZkNDVhZWE1MjlhOCIKLQoteG1sLWNyeXB0b0BeMC44LjE6Ci0gIHZlcnNpb24gIjAuOC41IgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjguNS50Z3ojMmJiY2ZiM2ViMzNmM2E4MmEyMThiODIyYmY2NzJiNmIxYzIwZTUzOCIKK3htbC1jcnlwdG9AXjAuMTAuMDoKKyAgdmVyc2lvbiAiMC4xMC4xIgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjEwLjEudGd6I2Y4MzJmNzRjY2Y1NmYyNGFmY2FlMTE2M2ExZmNhYjQ0ZDk2Nzc0YTgiCisgIGludGVncml0eSBzaGExLStETDNUTTlXOGtyOHJoRmpvZnlyUk5sbmRLZz0KICAgZGVwZW5kZW5jaWVzOgogICAgIHhtbGRvbSAiPTAuMS4xOSIKICAgICB4cGF0aC5qcyAiPj0wLjAuMyIKIAoteG1sLWVuY3J5cHRpb25AfjAuNy40OgotICB2ZXJzaW9uICIwLjcuNCIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjcuNC50Z3ojNDI3OTFlYzY0ZDU1NmQyNDU1ZGNiOWRhMGE1NDEyMzY2NWFjNjVjNyIKK3htbC1lbmNyeXB0aW9uQF4wLjExLjA6CisgIHZlcnNpb24gIjAuMTEuMiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjExLjIudGd6I2MyMTdmNTUwOTU0N2UzNGI1MDBiODI5ZjJjMGJjYTg1Y2NhNzNhMjEiCisgIGludGVncml0eSBzaGE1MTItalZ2RVM3aTVvdmRPN04rTmpnbmNBMzI2eFlLamhxZUFubnZJZ1JuWTdST0xDZkZxRURMd1AwU3hwLzMwU0hHMEFYUVYxMDQ4VDV5aW5PRnl2d0dGemc9PQogICBkZXBlbmRlbmNpZXM6Ci0gICAgYXN5bmMgIn4wLjIuNyIKLSAgICBlanMgIn4wLjguMyIKLSAgICBub2RlLWZvcmdlICIwLjIuMjQiCisgICAgYXN5bmMgIl4yLjEuNSIKKyAgICBlanMgIl4yLjUuNiIKKyAgICBub2RlLWZvcmdlICJeMC43LjAiCiAgICAgeG1sZG9tICJ+MC4xLjE1IgotICAgIHhwYXRoICIwLjAuNSIKKyAgICB4cGF0aCAiMC4wLjI3IgogCi14bWwyanNAfjAuNC4xOgoreG1sMmpzQF4wLjQuMDoKICAgdmVyc2lvbiAiMC40LjE5IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwyanMvLS94bWwyanMtMC40LjE5LnRneiM2ODZjMjBmMjEzMjA5ZTk0YWJmMGQxYmNmMWVmYWEyOTFjNzgyN2E3IgorICBpbnRlZ3JpdHkgc2hhNTEyLWVzWm5KWkpPaUpSOXdXS015dXZTRTF5NkRxNUxDdUphbnFoeHNsSDJieE02ZHVhaE5aK0hNcENMaEJRR1prYlg2eFJmOHgxWTJlSmxndDJxM3FvNDlRPT0KICAgZGVwZW5kZW5jaWVzOgogICAgIHNheCAiPj0wLjYuMCIKICAgICB4bWxidWlsZGVyICJ+OS4wLjEiCiAKLXhtbGJ1aWxkZXJAfjIuMS4wOgotICB2ZXJzaW9uICIyLjEuMCIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItMi4xLjAudGd6IzZkZGFlMzE2ODNiNmRmMTIxMDBiMjlmYzhhMGQ0ZjQ2MzQ5YWJiZWQiCit4bWxidWlsZGVyQH4yLjIuMDoKKyAgdmVyc2lvbiAiMi4yLjEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTIuMi4xLnRneiM5MzI2NDMwZjEzMGQ4NzQzNWQ0YzQwODY2NDNhYTI5MjZlMTA1YTMyIgorICBpbnRlZ3JpdHkgc2hhMS1reVpERHhNTmgwTmRURUNHWkRxaWttNFFXakk9CiAgIGRlcGVuZGVuY2llczoKLSAgICB1bmRlcnNjb3JlICI+PTEuNS54IgorICAgIGxvZGFzaC1ub2RlICJ+Mi40LjEiCiAKIHhtbGJ1aWxkZXJAfjkuMC4xOgogICB2ZXJzaW9uICI5LjAuNyIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItOS4wLjcudGd6IzEzMmVlNjNkMmVjNTU2NWM1NTdlMjBmNGMyMmRmOWFjYTY4NmIxMGQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTkuMC43LnRneiMxMzJlZTYzZDJlYzU1NjVjNTU3ZTIwZjRjMjJkZjlhY2E2ODZiMTBkIgorICBpbnRlZ3JpdHkgc2hhMS1FeTdtUFM3RlZseFZmaUQwd2kzNXJLYUdzUTA9CiAKIHhtbGRvbUA9MC4xLjE5OgogICB2ZXJzaW9uICIwLjEuMTkiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGRvbS8tL3htbGRvbS0wLjEuMTkudGd6IzYzMWZjMDc3NzZlZmQ4NDExOGJmMjUxNzFiMzdlZDRkMDc1YTBhYmMiCisgIGludGVncml0eSBzaGExLVl4L0FkM2J2MkVFWXZ5VVhHemZ0VFFkYUNydz0KIAoteG1sZG9tQH4wLjEuMTUsIHhtbGRvbUB+MC4xLjE5OgoreG1sZG9tQF4wLjEuMCwgeG1sZG9tQH4wLjEuMTU6CiAgIHZlcnNpb24gIjAuMS4yNyIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sZG9tLy0veG1sZG9tLTAuMS4yNy50Z3ojZDUwMWY5N2IzYmRiNDAzYWY4ZWY5ZWNjMjA1NzMxODdhYWRhYzBlOSIKKyAgaW50ZWdyaXR5IHNoYTEtMVFINWV6dmJRRHI0NzU3TUlGY3hoNnJhd09rPQogCiB4cGF0aC5qc0A+PTAuMC4zOgogICB2ZXJzaW9uICIxLjEuMCIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGguanMvLS94cGF0aC5qcy0xLjEuMC50Z3ojMzgxNmE0NGVkNGJiMzUyMDkxMDgzZDAwMmEzODNkZDUxMDRhNWZmMSIKKyAgaW50ZWdyaXR5IHNoYTUxMi1qZytxa2ZTNEs4RTc5NjVzcWFVbDhtUm5nWGlLYjNXWkdmT05nRTE4cHIwM0ZVUWl1U1Y2RytFajR0UzU1QitySVFTRkVJdzNwaGRWQVE0cFBxTldmUT09CiAKLXhwYXRoQDAuMC41OgotICB2ZXJzaW9uICIwLjAuNSIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGgvLS94cGF0aC0wLjAuNS50Z3ojNDU0MDM2ZjZlZjBmM2RmNWFmNWQ0YmE0YTExOWZiNzU2NzRiM2U2YyIKK3hwYXRoQDAuMC4yNzoKKyAgdmVyc2lvbiAiMC4wLjI3IgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94cGF0aC8tL3hwYXRoLTAuMC4yNy50Z3ojZGQzNDIxZmJkY2M1NjQ2YWMzMmM0ODUzMWI0ZDdlOWQwYzJjZmE5MiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mZzAzV1J4dGtDVjZvaENsZVBOQUVDWXNtcEtLVHY1TDh5L1gzRG4xaFFyZWMzUE94MmpIWi8wUDJxUTZIdnNyVTFCbWVxWGNvZjNOR0d1ZUc2THh3UT09Cg==" + } + ] +} diff --git a/spec/fixtures/security-reports/remediations/remediation.patch b/spec/fixtures/security-reports/remediations/remediation.patch new file mode 100644 index 00000000000..bbfb6874627 --- /dev/null +++ b/spec/fixtures/security-reports/remediations/remediation.patch @@ -0,0 +1,180 @@ +diff --git a/yarn.lock b/yarn.lock +index 0ecc92f..7fa4554 100644 +--- a/yarn.lock ++++ b/yarn.lock +@@ -2,103 +2,124 @@ + # yarn lockfile v1 + + +-async@~0.2.7: +- version "0.2.10" +- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" +- +-async@~1.5.2: +- version "1.5.2" +- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" ++async@^2.1.5, async@^2.5.0: ++ version "2.6.1" ++ resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" ++ integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== ++ dependencies: ++ lodash "^4.17.10" + +-debug@^1.0.4: +- version "1.0.5" +- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac" ++debug@^2.6.0: ++ version "2.6.9" ++ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" ++ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +-ejs@~0.8.3: +- version "0.8.8" +- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598" ++ejs@^2.5.6: ++ version "2.6.1" ++ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" ++ integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== ++ ++lodash-node@~2.4.1: ++ version "2.4.1" ++ resolved "https://registry.yarnpkg.com/lodash-node/-/lodash-node-2.4.1.tgz#ea82f7b100c733d1a42af76801e506105e2a80ec" ++ integrity sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw= ++ ++lodash@^4.17.10: ++ version "4.17.11" ++ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" ++ integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + + ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" ++ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +-node-forge@0.2.24: +- version "0.2.24" +- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858" ++node-forge@^0.7.0: ++ version "0.7.6" ++ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" ++ integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + + saml2-js@^1.5.0: +- version "1.5.0" +- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9" ++ version "1.12.4" ++ resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.12.4.tgz#c288f20bda6d2b91073b16c94ea72f22349ac3b3" ++ integrity sha1-wojyC9ptK5EHOxbJTqcvIjSaw7M= + dependencies: +- async "~1.5.2" +- debug "^1.0.4" +- underscore "~1.6.0" +- xml-crypto "^0.8.1" +- xml-encryption "~0.7.4" +- xml2js "~0.4.1" +- xmlbuilder "~2.1.0" +- xmldom "~0.1.19" ++ async "^2.5.0" ++ debug "^2.6.0" ++ underscore "^1.8.0" ++ xml-crypto "^0.10.0" ++ xml-encryption "^0.11.0" ++ xml2js "^0.4.0" ++ xmlbuilder "~2.2.0" ++ xmldom "^0.1.0" + + sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" ++ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +-underscore@>=1.5.x: ++underscore@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" ++ integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== + +-underscore@~1.6.0: +- version "1.6.0" +- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" +- +-xml-crypto@^0.8.1: +- version "0.8.5" +- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538" ++xml-crypto@^0.10.0: ++ version "0.10.1" ++ resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-0.10.1.tgz#f832f74ccf56f24afcae1163a1fcab44d96774a8" ++ integrity sha1-+DL3TM9W8kr8rhFjofyrRNlndKg= + dependencies: + xmldom "=0.1.19" + xpath.js ">=0.0.3" + +-xml-encryption@~0.7.4: +- version "0.7.4" +- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7" ++xml-encryption@^0.11.0: ++ version "0.11.2" ++ resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.11.2.tgz#c217f5509547e34b500b829f2c0bca85cca73a21" ++ integrity sha512-jVvES7i5ovdO7N+NjgncA326xYKjhqeAnnvIgRnY7ROLCfFqEDLwP0Sxp/30SHG0AXQV1048T5yinOFyvwGFzg== + dependencies: +- async "~0.2.7" +- ejs "~0.8.3" +- node-forge "0.2.24" ++ async "^2.1.5" ++ ejs "^2.5.6" ++ node-forge "^0.7.0" + xmldom "~0.1.15" +- xpath "0.0.5" ++ xpath "0.0.27" + +-xml2js@~0.4.1: ++xml2js@^0.4.0: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" ++ integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +-xmlbuilder@~2.1.0: +- version "2.1.0" +- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed" ++xmlbuilder@~2.2.0: ++ version "2.2.1" ++ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.2.1.tgz#9326430f130d87435d4c4086643aa2926e105a32" ++ integrity sha1-kyZDDxMNh0NdTECGZDqikm4QWjI= + dependencies: +- underscore ">=1.5.x" ++ lodash-node "~2.4.1" + + xmlbuilder@~9.0.1: + version "9.0.7" +- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" ++ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" ++ integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + + xmldom@=0.1.19: + version "0.1.19" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc" ++ integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw= + +-xmldom@~0.1.15, xmldom@~0.1.19: ++xmldom@^0.1.0, xmldom@~0.1.15: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" ++ integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= + + xpath.js@>=0.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1" ++ integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ== + +-xpath@0.0.5: +- version "0.0.5" +- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c" ++xpath@0.0.27: ++ version "0.0.27" ++ resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" ++ integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== diff --git a/spec/fixtures/security-reports/remediations/yarn.lock b/spec/fixtures/security-reports/remediations/yarn.lock new file mode 100644 index 00000000000..0ecc92fb711 --- /dev/null +++ b/spec/fixtures/security-reports/remediations/yarn.lock @@ -0,0 +1,104 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +async@~0.2.7: + version "0.2.10" + resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + +async@~1.5.2: + version "1.5.2" + resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +debug@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac" + dependencies: + ms "2.0.0" + +ejs@~0.8.3: + version "0.8.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +node-forge@0.2.24: + version "0.2.24" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858" + +saml2-js@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9" + dependencies: + async "~1.5.2" + debug "^1.0.4" + underscore "~1.6.0" + xml-crypto "^0.8.1" + xml-encryption "~0.7.4" + xml2js "~0.4.1" + xmlbuilder "~2.1.0" + xmldom "~0.1.19" + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +underscore@>=1.5.x: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + +underscore@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" + +xml-crypto@^0.8.1: + version "0.8.5" + resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538" + dependencies: + xmldom "=0.1.19" + xpath.js ">=0.0.3" + +xml-encryption@~0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7" + dependencies: + async "~0.2.7" + ejs "~0.8.3" + node-forge "0.2.24" + xmldom "~0.1.15" + xpath "0.0.5" + +xml2js@~0.4.1: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~2.1.0: + version "2.1.0" + resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed" + dependencies: + underscore ">=1.5.x" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + +xmldom@=0.1.19: + version "0.1.19" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc" + +xmldom@~0.1.15, xmldom@~0.1.19: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + +xpath.js@>=0.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1" + +xpath@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c" -- cgit v1.2.1 From 333097d7652a863f1d328e4c6b86be5e3b570bbf Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Thu, 7 Mar 2019 09:09:37 +0800 Subject: Allow protected branch creation for empty project --- spec/lib/gitlab/checks/branch_check_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb index f99fc639dbd..1a672f9d80a 100644 --- a/spec/lib/gitlab/checks/branch_check_spec.rb +++ b/spec/lib/gitlab/checks/branch_check_spec.rb @@ -116,6 +116,21 @@ describe Gitlab::Checks::BranchCheck do .and_return(['branch']) end + context "when repo is empty" do + let(:project) { create(:project, :empty_repo) } + let(:ref) { 'refs/heads/master' } + + before do + allow(user_access) + .to receive(:can_push_to_branch?) + .and_return(true) + end + + it 'allows branch creation' do + expect { subject.validate! }.not_to raise_error + end + end + context "newrev isn't in any protected branches" do before do allow(ProtectedBranch) -- cgit v1.2.1 From 572e3f2254fb9582d304cc4023b83b87dbf0e389 Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 7 Mar 2019 07:06:54 +0000 Subject: Provide EE backports for filtering by approver feature Adds custom validator for ArrayNoneAny param Extracts some logic in js into separate files --- .../filtered_search_visual_tokens_spec.js | 335 +------------------ .../filtered_search/visual_token_value_spec.js | 361 +++++++++++++++++++++ spec/lib/api/helpers/custom_validators_spec.rb | 23 ++ 3 files changed, 385 insertions(+), 334 deletions(-) create mode 100644 spec/javascripts/filtered_search/visual_token_value_spec.js (limited to 'spec') diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 6230da77f49..f3dc35552d5 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -1,9 +1,4 @@ -import _ from 'underscore'; -import AjaxCache from '~/lib/utils/ajax_cache'; -import UsersCache from '~/lib/utils/users_cache'; - import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens'; -import DropdownUtils from '~/filtered_search//dropdown_utils'; import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; describe('Filtered Search Visual Tokens', () => { @@ -685,349 +680,21 @@ describe('Filtered Search Visual Tokens', () => { }); describe('renderVisualTokenValue', () => { - const keywordToken = FilteredSearchSpecHelper.createFilterVisualToken('search'); - const milestoneToken = FilteredSearchSpecHelper.createFilterVisualToken( - 'milestone', - 'upcoming', - ); - - let updateLabelTokenColorSpy; - let updateUserTokenAppearanceSpy; - beforeEach(() => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` ${authorToken.outerHTML} ${bugLabelToken.outerHTML} - ${keywordToken.outerHTML} - ${milestoneToken.outerHTML} `); - - spyOn(subject, 'updateLabelTokenColor'); - updateLabelTokenColorSpy = subject.updateLabelTokenColor; - - spyOn(subject, 'updateUserTokenAppearance'); - updateUserTokenAppearanceSpy = subject.updateUserTokenAppearance; }); it('renders a author token value element', () => { - const { tokenNameElement, tokenValueContainer, tokenValueElement } = findElements( - authorToken, - ); + const { tokenNameElement, tokenValueElement } = findElements(authorToken); const tokenName = tokenNameElement.innerText; const tokenValue = 'new value'; subject.renderVisualTokenValue(authorToken, tokenName, tokenValue); expect(tokenValueElement.innerText).toBe(tokenValue); - expect(updateUserTokenAppearanceSpy.calls.count()).toBe(1); - const expectedArgs = [tokenValueContainer, tokenValueElement, tokenValue]; - - expect(updateUserTokenAppearanceSpy.calls.argsFor(0)).toEqual(expectedArgs); - expect(updateLabelTokenColorSpy.calls.count()).toBe(0); - }); - - it('renders a label token value element', () => { - const { tokenNameElement, tokenValueContainer, tokenValueElement } = findElements( - bugLabelToken, - ); - const tokenName = tokenNameElement.innerText; - const tokenValue = 'new value'; - - subject.renderVisualTokenValue(bugLabelToken, tokenName, tokenValue); - - expect(tokenValueElement.innerText).toBe(tokenValue); - expect(updateLabelTokenColorSpy.calls.count()).toBe(1); - const expectedArgs = [tokenValueContainer, tokenValue]; - - expect(updateLabelTokenColorSpy.calls.argsFor(0)).toEqual(expectedArgs); - expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0); - }); - - it('renders a milestone token value element', () => { - const { tokenNameElement, tokenValueElement } = findElements(milestoneToken); - const tokenName = tokenNameElement.innerText; - const tokenValue = 'new value'; - - subject.renderVisualTokenValue(milestoneToken, tokenName, tokenValue); - - expect(tokenValueElement.innerText).toBe(tokenValue); - expect(updateLabelTokenColorSpy.calls.count()).toBe(0); - expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0); - }); - - it('does not update user token appearance for `None` filter', () => { - const { tokenNameElement } = findElements(authorToken); - - const tokenName = tokenNameElement.innerText; - const tokenValue = 'None'; - - subject.renderVisualTokenValue(authorToken, tokenName, tokenValue); - - expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0); - }); - - it('does not update user token appearance for `none` filter', () => { - const { tokenNameElement } = findElements(authorToken); - - const tokenName = tokenNameElement.innerText; - const tokenValue = 'none'; - - subject.renderVisualTokenValue(authorToken, tokenName, tokenValue); - - expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0); - }); - - it('does not update user token appearance for `any` filter', () => { - const { tokenNameElement } = findElements(authorToken); - - const tokenName = tokenNameElement.innerText; - const tokenValue = 'any'; - - subject.renderVisualTokenValue(authorToken, tokenName, tokenValue); - - expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0); - }); - - it('does not update label token color for `none` filter', () => { - const { tokenNameElement } = findElements(bugLabelToken); - - const tokenName = tokenNameElement.innerText; - const tokenValue = 'none'; - - subject.renderVisualTokenValue(bugLabelToken, tokenName, tokenValue); - - expect(updateLabelTokenColorSpy.calls.count()).toBe(0); - }); - - it('does not update label token color for `any` filter', () => { - const { tokenNameElement } = findElements(bugLabelToken); - - const tokenName = tokenNameElement.innerText; - const tokenValue = 'any'; - - subject.renderVisualTokenValue(bugLabelToken, tokenName, tokenValue); - - expect(updateLabelTokenColorSpy.calls.count()).toBe(0); - }); - }); - - describe('updateUserTokenAppearance', () => { - let usersCacheSpy; - - beforeEach(() => { - spyOn(UsersCache, 'retrieve').and.callFake(username => usersCacheSpy(username)); - }); - - it('ignores error if UsersCache throws', done => { - spyOn(window, 'Flash'); - const dummyError = new Error('Earth rotated backwards'); - const { tokenValueContainer, tokenValueElement } = findElements(authorToken); - const tokenValue = tokenValueElement.innerText; - usersCacheSpy = username => { - expect(`@${username}`).toBe(tokenValue); - return Promise.reject(dummyError); - }; - - subject - .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) - .then(() => { - expect(window.Flash.calls.count()).toBe(0); - }) - .then(done) - .catch(done.fail); - }); - - it('does nothing if user cannot be found', done => { - const { tokenValueContainer, tokenValueElement } = findElements(authorToken); - const tokenValue = tokenValueElement.innerText; - usersCacheSpy = username => { - expect(`@${username}`).toBe(tokenValue); - return Promise.resolve(undefined); - }; - - subject - .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) - .then(() => { - expect(tokenValueElement.innerText).toBe(tokenValue); - }) - .then(done) - .catch(done.fail); - }); - - it('replaces author token with avatar and display name', done => { - const dummyUser = { - name: 'Important Person', - avatar_url: 'https://host.invalid/mypics/avatar.png', - }; - const { tokenValueContainer, tokenValueElement } = findElements(authorToken); - const tokenValue = tokenValueElement.innerText; - usersCacheSpy = username => { - expect(`@${username}`).toBe(tokenValue); - return Promise.resolve(dummyUser); - }; - - subject - .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) - .then(() => { - expect(tokenValueContainer.dataset.originalValue).toBe(tokenValue); - expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name); - const avatar = tokenValueElement.querySelector('img.avatar'); - - expect(avatar.src).toBe(dummyUser.avatar_url); - expect(avatar.alt).toBe(''); - }) - .then(done) - .catch(done.fail); - }); - - it('escapes user name when creating token', done => { - const dummyUser = { - name: '", project: project) } + + it 'does not include the malicious script' do + is_expected.not_to include("") + end + end + end + + context 'when ref exists in the repository' do + before do + allow(pipeline).to receive(:ref_exists?) { false } + end + + it 'returns a correct ref text' do + is_expected.to eq("for #{pipeline.ref}") + end + + context 'when ref contains malicious script' do + let(:pipeline) { create(:ci_pipeline, ref: "", project: project) } + + it 'does not include the malicious script' do + is_expected.not_to include("") + end + end + end + end + end + + describe '#link_to_merge_request' do + subject { presenter.link_to_merge_request } + + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.last } + + it 'returns a correct link' do + is_expected + .to include(project_merge_request_path(merge_request.project, merge_request)) + end + + context 'when pipeline is branch pipeline' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + it 'returns nothing' do + is_expected.to be_nil + end + end + end + + describe '#link_to_merge_request_source_branch' do + subject { presenter.link_to_merge_request_source_branch } + + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.last } + + it 'returns a correct link' do + is_expected + .to include(project_commits_path(merge_request.source_project, + merge_request.source_branch)) + end + + context 'when pipeline is branch pipeline' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + it 'returns nothing' do + is_expected.to be_nil + end + end + end + + describe '#link_to_merge_request_target_branch' do + subject { presenter.link_to_merge_request_target_branch } + + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.last } + + it 'returns a correct link' do + is_expected + .to include(project_commits_path(merge_request.target_project, merge_request.target_branch)) + end + + context 'when pipeline is branch pipeline' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + it 'returns nothing' do + is_expected.to be_nil + end + end + end end diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 02cefcbc916..fd03f594c35 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -345,6 +345,30 @@ describe MergeRequestPresenter do end end + describe '#source_branch_commits_path' do + subject do + described_class.new(resource, current_user: user) + .source_branch_commits_path + end + + context 'when source branch exists' do + it 'returns path' do + allow(resource).to receive(:source_branch_exists?) { true } + + is_expected + .to eq("/#{resource.source_project.full_path}/commits/#{resource.source_branch}") + end + end + + context 'when source branch does not exist' do + it 'returns nil' do + allow(resource).to receive(:source_branch_exists?) { false } + + is_expected.to be_nil + end + end + end + describe '#target_branch_tree_path' do subject do described_class.new(resource, current_user: user) diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index c8308a0ae85..1d992e8a483 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -159,13 +159,13 @@ describe PipelineEntity do expect(subject[:merge_request][:source_branch]) .to eq(merge_request.source_branch) - expect(project_branch_path(project, merge_request.source_branch)) + expect(project_commits_path(project, merge_request.source_branch)) .to include(subject[:merge_request][:source_branch_path]) expect(subject[:merge_request][:target_branch]) .to eq(merge_request.target_branch) - expect(project_branch_path(project, merge_request.target_branch)) + expect(project_commits_path(project, merge_request.target_branch)) .to include(subject[:merge_request][:target_branch_path]) end end -- cgit v1.2.1 From 1e6ea914a1affef8490ca15338c885c11d54e17f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 19 Mar 2019 10:07:05 -0700 Subject: GitHub import: Create new branches as project owner This avoids permission errors when importing merge requests whose authors don't have direct access to push to the main repository. --- spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index 15e59718dce..37c3fae7cb7 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -273,6 +273,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi mr.state = 'opened' mr.save + # Ensure the project owner is creating the branches because the + # merge request author may not have access to push to this + # repository. + allow(project.repository).to receive(:add_branch).with(project.owner, anything, anything).and_call_original + importer.insert_git_data(mr, exists) expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy -- cgit v1.2.1 From d165754400cd68f116babc1b0f50cf6109e85009 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 19 Mar 2019 10:02:17 -0700 Subject: Reject HEAD requests to info/refs endpoint In production, we see high error rates due to clients attempting to use the dumb Git HTTP protocol with HEAD /foo/bar.git/info/refs endpoint. This isn't supported and causes Error 500s because Workhorse doesn't send along its secret because it's not proxying this request. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/54579 --- spec/controllers/projects/git_http_controller_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 spec/controllers/projects/git_http_controller_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/git_http_controller_spec.rb b/spec/controllers/projects/git_http_controller_spec.rb new file mode 100644 index 00000000000..bf099e8deeb --- /dev/null +++ b/spec/controllers/projects/git_http_controller_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::GitHttpController do + describe 'HEAD #info_refs' do + it 'returns 403' do + project = create(:project, :public, :repository) + + head :info_refs, params: { namespace_id: project.namespace.to_param, project_id: project.path + '.git' } + + expect(response.status).to eq(403) + end + end +end -- cgit v1.2.1 From 42ca9c6f0de34dfa7ae09cc0e9672ea5857afd38 Mon Sep 17 00:00:00 2001 From: Tiger Date: Wed, 27 Feb 2019 13:13:06 +1100 Subject: Add :preparing status to HasStatus Introduces a new status for builds between :created and :pending that will be used when builds require one or more prerequisite actions to be completed before being picked up by a runner (such as creating Kubernetes resources before deploying). The existing :created > :pending transition is unchanged, so only builds that require preparation will use the :preparing status. --- spec/factories/ci/builds.rb | 4 ++ spec/factories/ci/pipelines.rb | 8 +++ spec/factories/commit_statuses.rb | 4 ++ .../projects/badges/pipeline_badge_spec.rb | 18 ++++++ spec/features/projects/pipelines/pipeline_spec.rb | 23 ++++++++ spec/features/projects/pipelines/pipelines_spec.rb | 24 ++++++++ spec/lib/gitlab/badge/pipeline/template_spec.rb | 10 ++++ spec/lib/gitlab/ci/status/build/preparing_spec.rb | 33 +++++++++++ spec/lib/gitlab/ci/status/preparing_spec.rb | 29 ++++++++++ spec/models/ci/pipeline_spec.rb | 64 ++++++++++++++++++---- spec/models/commit_status_spec.rb | 17 ++++++ spec/models/concerns/has_status_spec.rb | 22 +++++++- 12 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 spec/lib/gitlab/ci/status/build/preparing_spec.rb create mode 100644 spec/lib/gitlab/ci/status/preparing_spec.rb (limited to 'spec') diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 0b3e67b4987..067391c1179 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -75,6 +75,10 @@ FactoryBot.define do status 'created' end + trait :preparing do + status 'preparing' + end + trait :scheduled do schedulable status 'scheduled' diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index ee5d27355f1..aa5ccbda6cd 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -50,6 +50,14 @@ FactoryBot.define do failure_reason :config_error end + trait :created do + status :created + end + + trait :preparing do + status :preparing + end + trait :blocked do status :manual end diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index 381bf07f6a0..848a31e96c1 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -33,6 +33,10 @@ FactoryBot.define do status 'pending' end + trait :preparing do + status 'preparing' + end + trait :created do status 'created' end diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb index dee81898928..96efa06d843 100644 --- a/spec/features/projects/badges/pipeline_badge_spec.rb +++ b/spec/features/projects/badges/pipeline_badge_spec.rb @@ -41,6 +41,24 @@ describe 'Pipeline Badge' do end end + context 'when the pipeline is preparing' do + let!(:job) { create(:ci_build, status: 'created', pipeline: pipeline) } + + before do + # Prevent skipping directly to 'pending' + allow(Ci::BuildPrepareWorker).to receive(:perform_async) + end + + it 'displays the preparing badge' do + job.prepare + + visit pipeline_project_badges_path(project, ref: ref, format: :svg) + + expect(page.status_code).to eq(200) + expect_badge('preparing') + end + end + context 'when the pipeline is running' do it 'shows displays so on the badge' do create(:ci_build, pipeline: pipeline, name: 'second build', status_event: 'run') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 36b8c15b8b6..84d4312010f 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -21,6 +21,11 @@ describe 'Pipeline', :js do pipeline: pipeline, stage: 'test', name: 'test') end + let!(:build_preparing) do + create(:ci_build, :preparing, + pipeline: pipeline, stage: 'deploy', name: 'prepare') + end + let!(:build_running) do create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') @@ -97,6 +102,24 @@ describe 'Pipeline', :js do end end + context 'when pipeline has preparing builds' do + it 'shows a preparing icon and a cancel action' do + page.within('#ci-badge-prepare') do + expect(page).to have_selector('.js-ci-status-icon-preparing') + expect(page).to have_selector('.js-icon-cancel') + expect(page).to have_content('prepare') + end + end + + it 'cancels the preparing build and shows retry button' do + find('#ci-badge-deploy .ci-action-icon-container').click + + page.within('#ci-badge-deploy') do + expect(page).to have_css('.js-icon-retry') + end + end + end + context 'when pipeline has successful builds' do it 'shows the success icon and a retry action for the successful build' do page.within('#ci-badge-build') do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 88d7c9ef8bd..9dc505573d4 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -282,6 +282,30 @@ describe 'Pipelines', :js do end context 'for generic statuses' do + context 'when preparing' do + let!(:pipeline) do + create(:ci_empty_pipeline, + status: 'preparing', project: project) + end + + let!(:status) do + create(:generic_commit_status, + :preparing, pipeline: pipeline) + end + + before do + visit_project_pipelines + end + + it 'is cancelable' do + expect(page).to have_selector('.js-pipelines-cancel-button') + end + + it 'shows the pipeline as preparing' do + expect(page).to have_selector('.ci-preparing') + end + end + context 'when running' do let!(:running) do create(:generic_commit_status, diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb index 20fa4f879c3..bcef0b7e120 100644 --- a/spec/lib/gitlab/badge/pipeline/template_spec.rb +++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb @@ -59,6 +59,16 @@ describe Gitlab::Badge::Pipeline::Template do end end + context 'when status is preparing' do + before do + allow(badge).to receive(:status).and_return('preparing') + end + + it 'has expected color' do + expect(template.value_color).to eq '#dfb317' + end + end + context 'when status is unknown' do before do allow(badge).to receive(:status).and_return('unknown') diff --git a/spec/lib/gitlab/ci/status/build/preparing_spec.rb b/spec/lib/gitlab/ci/status/build/preparing_spec.rb new file mode 100644 index 00000000000..4d8945845ba --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/preparing_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Preparing do + subject do + described_class.new(double('subject')) + end + + describe '#illustration' do + it { expect(subject.illustration).to include(:image, :size, :title, :content) } + end + + describe '.matches?' do + subject { described_class.matches?(build, nil) } + + context 'when build is preparing' do + let(:build) { create(:ci_build, :preparing) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not preparing' do + let(:build) { create(:ci_build, :success) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb new file mode 100644 index 00000000000..7211c0e506d --- /dev/null +++ b/spec/lib/gitlab/ci/status/preparing_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Status::Preparing do + subject do + described_class.new(double('subject'), nil) + end + + describe '#text' do + it { expect(subject.text).to eq 'preparing' } + end + + describe '#label' do + it { expect(subject.label).to eq 'preparing' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'status_created' } + end + + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_created' } + end + + describe '#group' do + it { expect(subject.group).to eq 'preparing' } + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7eeaa7a18ef..d35caac33dc 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1201,16 +1201,28 @@ describe Ci::Pipeline, :mailer do end describe '#started_at' do - it 'updates on transitioning to running' do - build.run + let(:pipeline) { create(:ci_empty_pipeline, status: from_status) } + + %i[created preparing pending].each do |status| + context "from #{status}" do + let(:from_status) { status } - expect(pipeline.reload.started_at).not_to be_nil + it 'updates on transitioning to running' do + pipeline.run + + expect(pipeline.started_at).not_to be_nil + end + end end - it 'does not update on transitioning to success' do - build.success + context 'from created' do + let(:from_status) { :created } + + it 'does not update on transitioning to success' do + pipeline.succeed - expect(pipeline.reload.started_at).to be_nil + expect(pipeline.started_at).to be_nil + end end end @@ -1229,27 +1241,49 @@ describe Ci::Pipeline, :mailer do end describe 'merge request metrics' do - let(:project) { create(:project, :repository) } - let(:pipeline) { FactoryBot.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } - let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } + let(:pipeline) { create(:ci_empty_pipeline, status: from_status) } before do expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id) end context 'when transitioning to running' do - it 'schedules metrics workers' do - pipeline.run + %i[created preparing pending].each do |status| + context "from #{status}" do + let(:from_status) { status } + + it 'schedules metrics workers' do + pipeline.run + end + end end end context 'when transitioning to success' do + let(:from_status) { 'created' } + it 'schedules metrics workers' do pipeline.succeed end end end + describe 'merge on success' do + let(:pipeline) { create(:ci_empty_pipeline, status: from_status) } + + %i[created preparing pending running].each do |status| + context "from #{status}" do + let(:from_status) { status } + + it 'schedules pipeline success worker' do + expect(PipelineSuccessWorker).to receive(:perform_async).with(pipeline.id) + + pipeline.succeed + end + end + end + end + describe 'pipeline caching' do it 'performs ExpirePipelinesCacheWorker' do expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id) @@ -1768,6 +1802,14 @@ describe Ci::Pipeline, :mailer do subject { pipeline.reload.status } + context 'on prepare' do + before do + build.prepare + end + + it { is_expected.to eq('preparing') } + end + context 'on queuing' do before do build.enqueue diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 8b7c88805c1..1d241bf6000 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -49,6 +49,16 @@ describe CommitStatus do commit_status.success! end + + describe 'transitioning to running' do + let(:commit_status) { create(:commit_status, :pending, started_at: nil) } + + it 'records the started at time' do + commit_status.run! + + expect(commit_status.started_at).to be_present + end + end end describe '#started?' do @@ -555,6 +565,7 @@ describe CommitStatus do before do allow(Time).to receive(:now).and_return(current_time) + expect(commit_status.any_unmet_prerequisites?).to eq false end shared_examples 'commit status enqueued' do @@ -569,6 +580,12 @@ describe CommitStatus do it_behaves_like 'commit status enqueued' end + context 'when initial state is :preparing' do + let(:commit_status) { create(:commit_status, :preparing) } + + it_behaves_like 'commit status enqueued' + end + context 'when initial state is :skipped' do let(:commit_status) { create(:commit_status, :skipped) } diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 6b1038cb8fd..e8b1eba67cc 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -34,6 +34,22 @@ describe HasStatus do it { is_expected.to eq 'running' } end + context 'all preparing' do + let!(:statuses) do + [create(type, status: :preparing), create(type, status: :preparing)] + end + + it { is_expected.to eq 'preparing' } + end + + context 'at least one preparing' do + let!(:statuses) do + [create(type, status: :success), create(type, status: :preparing)] + end + + it { is_expected.to eq 'preparing' } + end + context 'success and failed but allowed to fail' do let!(:statuses) do [create(type, status: :success), @@ -188,7 +204,7 @@ describe HasStatus do end end - %i[created running pending success + %i[created preparing running pending success failed canceled skipped].each do |status| it_behaves_like 'having a job', status end @@ -234,7 +250,7 @@ describe HasStatus do describe '.alive' do subject { CommitStatus.alive } - %i[running pending created].each do |status| + %i[running pending preparing created].each do |status| it_behaves_like 'containing the job', status end @@ -270,7 +286,7 @@ describe HasStatus do describe '.cancelable' do subject { CommitStatus.cancelable } - %i[running pending created scheduled].each do |status| + %i[running pending preparing created scheduled].each do |status| it_behaves_like 'containing the job', status end -- cgit v1.2.1 From 00f0d356b71fa87f8190810b01add0cc4586e90a Mon Sep 17 00:00:00 2001 From: Tiger Date: Mon, 4 Mar 2019 10:56:20 +1100 Subject: Create framework for build prerequisites Introduces the concept of Prerequisites for a CI build. If a build has unmet prerequisites it will go through the :preparing state before being made available to a runner. There are no actual prerequisites yet, so current behaviour is unchanged. --- .../projects/badges/pipeline_badge_spec.rb | 3 +- .../gitlab/ci/build/prerequisite/factory_spec.rb | 34 +++++++++++++ spec/models/ci/build_spec.rb | 57 ++++++++++++++++++++++ spec/models/ci/pipeline_spec.rb | 6 ++- spec/models/commit_status_spec.rb | 6 +++ spec/requests/api/runner_spec.rb | 9 ++++ spec/services/ci/prepare_build_service_spec.rb | 54 ++++++++++++++++++++ spec/workers/ci/build_prepare_worker_spec.rb | 30 ++++++++++++ 8 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb create mode 100644 spec/services/ci/prepare_build_service_spec.rb create mode 100644 spec/workers/ci/build_prepare_worker_spec.rb (limited to 'spec') diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb index 96efa06d843..4ac4e8f0fcb 100644 --- a/spec/features/projects/badges/pipeline_badge_spec.rb +++ b/spec/features/projects/badges/pipeline_badge_spec.rb @@ -47,10 +47,11 @@ describe 'Pipeline Badge' do before do # Prevent skipping directly to 'pending' allow(Ci::BuildPrepareWorker).to receive(:perform_async) + allow(job).to receive(:prerequisites).and_return([double]) end it 'displays the preparing badge' do - job.prepare + job.enqueue visit pipeline_project_badges_path(project, ref: ref, format: :svg) diff --git a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb new file mode 100644 index 00000000000..5187f99a441 --- /dev/null +++ b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Build::Prerequisite::Factory do + let(:build) { create(:ci_build) } + + describe '.for_build' do + let(:kubernetes_namespace) do + instance_double( + Gitlab::Ci::Build::Prerequisite::KubernetesNamespace, + unmet?: unmet) + end + + subject { described_class.new(build).unmet } + + before do + expect(Gitlab::Ci::Build::Prerequisite::KubernetesNamespace) + .to receive(:new).with(build).and_return(kubernetes_namespace) + end + + context 'prerequisite is unmet' do + let(:unmet) { true } + + it { is_expected.to eq [kubernetes_namespace] } + end + + context 'prerequisite is met' do + let(:unmet) { false } + + it { is_expected.to be_empty } + end + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 9ca4241d7d8..b31c4fcceb3 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -186,6 +186,37 @@ describe Ci::Build do end end + describe '#enqueue' do + let(:build) { create(:ci_build, :created) } + + subject { build.enqueue } + + before do + allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites) + allow(Ci::PrepareBuildService).to receive(:perform_async) + end + + context 'build has unmet prerequisites' do + let(:has_prerequisites) { true } + + it 'transitions to preparing' do + subject + + expect(build).to be_preparing + end + end + + context 'build has no prerequisites' do + let(:has_prerequisites) { false } + + it 'transitions to pending' do + subject + + expect(build).to be_pending + end + end + end + describe '#actionize' do context 'when build is a created' do before do @@ -344,6 +375,18 @@ describe Ci::Build do expect(build).to be_pending end + + context 'build has unmet prerequisites' do + before do + allow(build).to receive(:prerequisites).and_return([double]) + end + + it 'transits to preparing' do + subject + + expect(build).to be_preparing + end + end end end @@ -2928,6 +2971,20 @@ describe Ci::Build do end end + describe 'state transition: any => [:preparing]' do + let(:build) { create(:ci_build, :created) } + + before do + allow(build).to receive(:prerequisites).and_return([double]) + end + + it 'queues BuildPrepareWorker' do + expect(Ci::BuildPrepareWorker).to receive(:perform_async).with(build.id) + + build.enqueue + end + end + describe 'state transition: any => [:pending]' do let(:build) { create(:ci_build, :created) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index d35caac33dc..2ac056f63b2 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1804,7 +1804,11 @@ describe Ci::Pipeline, :mailer do context 'on prepare' do before do - build.prepare + # Prevent skipping directly to 'pending' + allow(build).to receive(:prerequisites).and_return([double]) + allow(Ci::BuildPrepareWorker).to receive(:perform_async) + + build.enqueue end it { is_expected.to eq('preparing') } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 1d241bf6000..e2b7f5c6ee2 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -489,6 +489,12 @@ describe CommitStatus do it { is_expected.to be_script_failure } end + + context 'when failure_reason is unmet_prerequisites' do + let(:reason) { :unmet_prerequisites } + + it { is_expected.to be_unmet_prerequisites } + end end describe 'ensure stage assignment' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 9087cccb759..3ccedd8dd06 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -918,6 +918,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do it { expect(job).to be_job_execution_timeout } end + + context 'when failure_reason is unmet_prerequisites' do + before do + update_job(state: 'failed', failure_reason: 'unmet_prerequisites') + job.reload + end + + it { expect(job).to be_unmet_prerequisites } + end end context 'when trace is given' do diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb new file mode 100644 index 00000000000..1797f8f964f --- /dev/null +++ b/spec/services/ci/prepare_build_service_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::PrepareBuildService do + describe '#execute' do + let(:build) { create(:ci_build, :preparing) } + + subject { described_class.new(build).execute } + + before do + allow(build).to receive(:prerequisites).and_return(prerequisites) + end + + shared_examples 'build enqueueing' do + it 'enqueues the build' do + expect(build).to receive(:enqueue).once + + subject + end + end + + context 'build has unmet prerequisites' do + let(:prerequisite) { double(complete!: true) } + let(:prerequisites) { [prerequisite] } + + it 'completes each prerequisite' do + expect(prerequisites).to all(receive(:complete!)) + + subject + end + + include_examples 'build enqueueing' + + context 'prerequisites fail to complete' do + before do + allow(build).to receive(:enqueue).and_return(false) + end + + it 'drops the build' do + expect(build).to receive(:drop!).with(:unmet_prerequisites).once + + subject + end + end + end + + context 'build has no prerequisites' do + let(:prerequisites) { [] } + + include_examples 'build enqueueing' + end + end +end diff --git a/spec/workers/ci/build_prepare_worker_spec.rb b/spec/workers/ci/build_prepare_worker_spec.rb new file mode 100644 index 00000000000..9f76696ee66 --- /dev/null +++ b/spec/workers/ci/build_prepare_worker_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::BuildPrepareWorker do + subject { described_class.new.perform(build_id) } + + context 'build exists' do + let(:build) { create(:ci_build) } + let(:build_id) { build.id } + let(:service) { double(execute: true) } + + it 'calls the prepare build service' do + expect(Ci::PrepareBuildService).to receive(:new).with(build).and_return(service) + expect(service).to receive(:execute).once + + subject + end + end + + context 'build does not exist' do + let(:build_id) { -1 } + + it 'does not attempt to prepare the build' do + expect(Ci::PrepareBuildService).not_to receive(:new) + + subject + end + end +end -- cgit v1.2.1 From 98a14a498dc3ffe6ea8bcd7db62e8bada5d2eb45 Mon Sep 17 00:00:00 2001 From: Tiger Date: Mon, 4 Mar 2019 11:00:40 +1100 Subject: Add build prerequisite for Kubernetes namespaces Builds that have deployments require Kubernetes resources to be created before the build can be deployed. These resources are no longer created when the cluster is created, which allows us to only create the resources required by each specific build. --- .../prerequisite/kubernetes_namespace_spec.rb | 72 ++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb new file mode 100644 index 00000000000..6f6e4abc0c8 --- /dev/null +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do + let(:build) { create(:ci_build) } + + describe '#unmet?' do + subject { described_class.new(build).unmet? } + + context 'build has no deployment' do + before do + expect(build.deployment).to be_nil + end + + it { is_expected.to be_falsey } + end + + context 'build has a deployment, and no existing kubernetes namespace' do + let!(:deployment) { create(:deployment, deployable: build) } + let!(:cluster) { create(:cluster, projects: [build.project]) } + + before do + expect(build.project.kubernetes_namespaces).to be_empty + end + + it { is_expected.to be_truthy } + end + + context 'build has a deployment and kubernetes namespaces' do + let!(:deployment) { create(:deployment, deployable: build) } + let!(:cluster) { create(:cluster, projects: [build.project]) } + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } + + it { is_expected.to be_falsey } + end + end + + describe '#complete!' do + let(:cluster) { create(:cluster, projects: [build.project]) } + let(:service) { double(execute: true) } + + subject { described_class.new(build).complete! } + + context 'completion is required' do + let!(:deployment) { create(:deployment, deployable: build) } + + it 'creates a kubernetes namespace' do + expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) + .to receive(:new) + .with(cluster: cluster, kubernetes_namespace: instance_of(Clusters::KubernetesNamespace)) + .and_return(service) + + expect(service).to receive(:execute).once + + subject + end + end + + context 'completion is not required' do + before do + expect(build.deployment).to be_nil + end + + it 'does not create a namespace' do + expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new) + + subject + end + end + end +end -- cgit v1.2.1 From 42c6ccd2098ec98e5244e743a0c39634f076f66f Mon Sep 17 00:00:00 2001 From: Tiger Date: Mon, 4 Mar 2019 12:14:47 +1100 Subject: Tweak FactoryBot definition for clusters Only create an associated project or group if there were none already specified. --- spec/factories/clusters/clusters.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb index a2e5f4862db..1cc3c0e03d8 100644 --- a/spec/factories/clusters/clusters.rb +++ b/spec/factories/clusters/clusters.rb @@ -12,7 +12,7 @@ FactoryBot.define do cluster_type { Clusters::Cluster.cluster_types[:project_type] } before(:create) do |cluster, evaluator| - cluster.projects << create(:project, :repository) + cluster.projects << create(:project, :repository) unless cluster.projects.present? end end @@ -20,7 +20,7 @@ FactoryBot.define do cluster_type { Clusters::Cluster.cluster_types[:group_type] } before(:create) do |cluster, evalutor| - cluster.groups << create(:group) + cluster.groups << create(:group) unless cluster.groups.present? end end -- cgit v1.2.1 From 759dab5b69f53a861045ebbc84836f83c7502af2 Mon Sep 17 00:00:00 2001 From: Tiger Date: Tue, 12 Mar 2019 17:37:37 +1100 Subject: Add feature flag for build preparing state The flag is on by default, but allows us to revert back to the old behaviour if we encounter any problems. --- spec/models/ci/build_spec.rb | 30 ++++++++++++++++++++++ spec/requests/api/project_clusters_spec.rb | 1 - spec/services/projects/create_service_spec.rb | 1 + spec/services/projects/transfer_service_spec.rb | 1 + spec/workers/cluster_configure_worker_spec.rb | 16 ++++++++++++ .../cluster_project_configure_worker_spec.rb | 21 +++++++++++++++ 6 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 spec/workers/cluster_project_configure_worker_spec.rb (limited to 'spec') diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index b31c4fcceb3..7500e6ae5b1 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2919,6 +2919,36 @@ describe Ci::Build do end end + describe '#any_unmet_prerequisites?' do + let(:build) { create(:ci_build, :created) } + + subject { build.any_unmet_prerequisites? } + + context 'build has prerequisites' do + before do + allow(build).to receive(:prerequisites).and_return([double]) + end + + it { is_expected.to be_truthy } + + context 'and the ci_preparing_state feature is disabled' do + before do + stub_feature_flags(ci_preparing_state: false) + end + + it { is_expected.to be_falsey } + end + end + + context 'build does not have prerequisites' do + before do + allow(build).to receive(:prerequisites).and_return([]) + end + + it { is_expected.to be_falsey } + end + end + describe '#yaml_variables' do let(:build) { create(:ci_build, pipeline: pipeline, yaml_variables: variables) } diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 9bab1f95150..4e42e233b4c 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -331,7 +331,6 @@ describe API::ProjectClusters do it 'should update cluster attributes' do expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') - expect(cluster.kubernetes_namespace.namespace).to eq('new-namespace') end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index d1b110b9806..e8418b09dc2 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -276,6 +276,7 @@ describe Projects::CreateService, '#execute' do before do group.add_owner(user) + stub_feature_flags(ci_preparing_state: false) expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator) expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher) end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index aae50d5307f..4efd360cb30 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -83,6 +83,7 @@ describe Projects::TransferService do subject { transfer_project(project, user, group) } before do + stub_feature_flags(ci_preparing_state: false) expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator) expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher) end diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb index 6918ee3d7d8..83f76809435 100644 --- a/spec/workers/cluster_configure_worker_spec.rb +++ b/spec/workers/cluster_configure_worker_spec.rb @@ -4,6 +4,11 @@ require 'spec_helper' describe ClusterConfigureWorker, '#perform' do let(:worker) { described_class.new } + let(:ci_preparing_state_enabled) { false } + + before do + stub_feature_flags(ci_preparing_state: ci_preparing_state_enabled) + end context 'when group cluster' do let(:cluster) { create(:cluster, :group, :provided_by_gcp) } @@ -66,4 +71,15 @@ describe ClusterConfigureWorker, '#perform' do described_class.new.perform(123) end end + + context 'ci_preparing_state feature is enabled' do + let(:cluster) { create(:cluster) } + let(:ci_preparing_state_enabled) { true } + + it 'does not configure the cluster' do + expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster) + + described_class.new.perform(cluster.id) + end + end end diff --git a/spec/workers/cluster_project_configure_worker_spec.rb b/spec/workers/cluster_project_configure_worker_spec.rb new file mode 100644 index 00000000000..afdea55adf4 --- /dev/null +++ b/spec/workers/cluster_project_configure_worker_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ClusterProjectConfigureWorker, '#perform' do + let(:worker) { described_class.new } + + context 'ci_preparing_state feature is enabled' do + let(:cluster) { create(:cluster) } + + before do + stub_feature_flags(ci_preparing_state: true) + end + + it 'does not configure the cluster' do + expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_project) + + described_class.new.perform(cluster.id) + end + end +end -- cgit v1.2.1 From 89b0bc04b9927abc85ce5fc3735438f956a8d5a2 Mon Sep 17 00:00:00 2001 From: Tiger Date: Wed, 13 Mar 2019 14:06:54 +1100 Subject: Create one Kubernetes namespace for a deployment Instead of creating a Kubernetes namespace on every cluster related to a project, only create one on the cluster the project is about to be deployed to. --- .../prerequisite/kubernetes_namespace_spec.rb | 37 +++++++++++++--------- spec/models/deployment_spec.rb | 28 ++++++++++++++++ 2 files changed, 50 insertions(+), 15 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index 6f6e4abc0c8..ba87863c978 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -16,34 +16,41 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do it { is_expected.to be_falsey } end - context 'build has a deployment, and no existing kubernetes namespace' do + context 'build has a deployment' do let!(:deployment) { create(:deployment, deployable: build) } - let!(:cluster) { create(:cluster, projects: [build.project]) } - before do - expect(build.project.kubernetes_namespaces).to be_empty - end + context 'and a cluster to deploy to' do + let(:cluster) { create(:cluster, projects: [build.project]) } - it { is_expected.to be_truthy } - end + before do + allow(build.deployment).to receive(:cluster).and_return(cluster) + end - context 'build has a deployment and kubernetes namespaces' do - let!(:deployment) { create(:deployment, deployable: build) } - let!(:cluster) { create(:cluster, projects: [build.project]) } - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } + it { is_expected.to be_truthy } + end - it { is_expected.to be_falsey } + context 'and no cluster to deploy to' do + before do + expect(deployment.cluster).to be_nil + end + + it { is_expected.to be_falsey } + end end end describe '#complete!' do - let(:cluster) { create(:cluster, projects: [build.project]) } + let!(:deployment) { create(:deployment, deployable: build) } let(:service) { double(execute: true) } subject { described_class.new(build).complete! } context 'completion is required' do - let!(:deployment) { create(:deployment, deployable: build) } + let(:cluster) { create(:cluster, projects: [build.project]) } + + before do + allow(build.deployment).to receive(:cluster).and_return(cluster) + end it 'creates a kubernetes namespace' do expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) @@ -59,7 +66,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do context 'completion is not required' do before do - expect(build.deployment).to be_nil + expect(deployment.cluster).to be_nil end it 'does not create a namespace' do diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index a8d53cfcd7d..5fce9504334 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -356,4 +356,32 @@ describe Deployment do end end end + + describe '#cluster' do + let(:deployment) { create(:deployment) } + let(:project) { deployment.project } + let(:environment) { deployment.environment } + + subject { deployment.cluster } + + before do + expect(project).to receive(:deployment_platform) + .with(environment: environment.name).and_call_original + end + + context 'project has no deployment platform' do + before do + expect(project.clusters).to be_empty + end + + it { is_expected.to be_nil } + end + + context 'project has a deployment platform' do + let!(:cluster) { create(:cluster, projects: [project]) } + let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) } + + it { is_expected.to eq cluster } + end + end end -- cgit v1.2.1 From 325d504c3c9697c73130aef67e8b32c99544b453 Mon Sep 17 00:00:00 2001 From: Tiger Date: Mon, 18 Mar 2019 13:02:39 +1100 Subject: Don't recreate Kubernetes namespaces if they exist Instead of attempting to create or update a Kubernetes namespace on every deploy, only do so when we know it doesn't exist yet. --- spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index ba87863c978..62dcd80fad7 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -27,6 +27,12 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end it { is_expected.to be_truthy } + + context 'and a namespace is already created for this project' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: build.project) } + + it { is_expected.to be_falsey } + end end context 'and no cluster to deploy to' do -- cgit v1.2.1 From 983dcba3d4ca0e3dae5a04271adeaa6ecedea488 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Wed, 20 Mar 2019 08:07:22 +0800 Subject: Handle cases when authorized_keys doesn't exist Modify Gitlab::AuthorizedKeys#rm_key and #list_key_ids to handle cases when an `Errno::ENOENT` is raised. Also added test cases in spec to ensure each method will still work when authorized_keys doesn't exist. --- spec/lib/gitlab/authorized_keys_spec.rb | 152 ++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 46 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb index b0fbe959ff8..ffe78967c2a 100644 --- a/spec/lib/gitlab/authorized_keys_spec.rb +++ b/spec/lib/gitlab/authorized_keys_spec.rb @@ -7,15 +7,31 @@ describe Gitlab::AuthorizedKeys do subject { described_class.new(logger) } + after do + delete_authorized_keys_file + end + describe '#add_key' do - it "adds a line at the end of the file and strips trailing garbage" do - create_authorized_keys_fixture - auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E" - - expect(logger).to receive(:info).with('Adding key (key-741): ssh-rsa AAAAB3NzaDAxx2E') - expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage')) - .to be_truthy - expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line}\n") + context 'authorized_keys file exists' do + before do + create_authorized_keys_fixture + end + + it "adds a line at the end of the file and strips trailing garbage" do + auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E" + + expect(logger).to receive(:info).with('Adding key (key-741): ssh-rsa AAAAB3NzaDAxx2E') + expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage')) + .to be_truthy + expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line}\n") + end + end + + context 'authorized_keys file does not exist' do + it 'creates the file' do + expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to be_truthy + expect(File.exist?(tmp_authorized_keys_path)).to be_truthy + end end end @@ -27,63 +43,103 @@ describe Gitlab::AuthorizedKeys do ] end - before do - create_authorized_keys_fixture - end + context 'authorized_keys file exists' do + before do + create_authorized_keys_fixture + end - it "adds lines at the end of the file" do - auth_line1 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG" - auth_line2 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG" + it "adds lines at the end of the file" do + auth_line1 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG" + auth_line2 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG" - expect(logger).to receive(:info).with('Adding key (key-12): ssh-dsa ASDFASGADG') - expect(logger).to receive(:info).with('Adding key (key-123): ssh-rsa GFDGDFSGSDFG') - expect(subject.batch_add_keys(keys)).to be_truthy - expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line1}\n#{auth_line2}\n") - end + expect(logger).to receive(:info).with('Adding key (key-12): ssh-dsa ASDFASGADG') + expect(logger).to receive(:info).with('Adding key (key-123): ssh-rsa GFDGDFSGSDFG') + expect(subject.batch_add_keys(keys)).to be_truthy + expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line1}\n#{auth_line2}\n") + end - context "invalid key" do - let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] } + context "invalid key" do + let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] } - it "doesn't add keys" do - expect(subject.batch_add_keys(keys)).to be_falsey - expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n") + it "doesn't add keys" do + expect(subject.batch_add_keys(keys)).to be_falsey + expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n") + end + end + end + + context 'authorized_keys file does not exist' do + it 'creates the file' do + expect(subject.batch_add_keys(keys)).to be_truthy + expect(File.exist?(tmp_authorized_keys_path)).to be_truthy end end end describe '#rm_key' do - it "removes the right line" do - create_authorized_keys_fixture - other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" - delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" - erased_line = delete_line.gsub(/./, '#') - File.open(tmp_authorized_keys_path, 'a') do |auth_file| - auth_file.puts delete_line - auth_file.puts other_line - end - - expect(logger).to receive(:info).with('Removing key (key-741)') - expect(subject.rm_key('key-741')).to be_truthy - expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{erased_line}\n#{other_line}\n") + context 'authorized_keys file exists' do + before do + create_authorized_keys_fixture + end + + it "removes the right line" do + other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" + delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" + erased_line = delete_line.gsub(/./, '#') + File.open(tmp_authorized_keys_path, 'a') do |auth_file| + auth_file.puts delete_line + auth_file.puts other_line + end + + expect(logger).to receive(:info).with('Removing key (key-741)') + expect(subject.rm_key('key-741')).to be_truthy + expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{erased_line}\n#{other_line}\n") + end + end + + context 'authorized_keys file does not exist' do + it 'returns false' do + expect(subject.rm_key('key-741')).to be_falsey + end end end describe '#clear' do - it "should return true" do - expect(subject.clear).to be_truthy + context 'authorized_keys file exists' do + before do + create_authorized_keys_fixture + end + + it "returns true" do + expect(subject.clear).to be_truthy + end + end + + context 'authorized_keys file does not exist' do + it "still returns true" do + expect(subject.clear).to be_truthy + end end end describe '#list_key_ids' do - before do - create_authorized_keys_fixture( - existing_content: - "key-1\tssh-dsa AAA\nkey-2\tssh-rsa BBB\nkey-3\tssh-rsa CCC\nkey-9000\tssh-rsa DDD\n" - ) + context 'authorized_keys file exists' do + before do + create_authorized_keys_fixture( + existing_content: + "key-1\tssh-dsa AAA\nkey-2\tssh-rsa BBB\nkey-3\tssh-rsa CCC\nkey-9000\tssh-rsa DDD\n" + ) + end + + it 'returns array of key IDs' do + expect(subject.list_key_ids).to eq([1, 2, 3, 9000]) + end end - it 'returns array of key IDs' do - expect(subject.list_key_ids).to eq([1, 2, 3, 9000]) + context 'authorized_keys file does not exist' do + it 'returns an empty array' do + expect(subject.list_key_ids).to be_empty + end end end @@ -92,6 +148,10 @@ describe Gitlab::AuthorizedKeys do File.open(tmp_authorized_keys_path, 'w') { |file| file.puts(existing_content) } end + def delete_authorized_keys_file + File.delete(tmp_authorized_keys_path) if File.exist?(tmp_authorized_keys_path) + end + def tmp_authorized_keys_path Gitlab.config.gitlab_shell.authorized_keys_file end -- cgit v1.2.1 From 66d3354f367bd294792b199ab60fdca513e0ee92 Mon Sep 17 00:00:00 2001 From: Gosia Ksionek Date: Wed, 20 Mar 2019 08:17:11 +0000 Subject: Add additional link to 2fa page --- spec/features/users/login_spec.rb | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index ad856bd062e..368a814874f 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -434,16 +434,22 @@ describe 'Login' do context 'within the grace period' do it 'redirects to two-factor configuration page' do - expect(authentication_metrics) - .to increment(:user_authenticated_counter) - - gitlab_sign_in(user) - - expect(current_path).to eq profile_two_factor_auth_path - expect(page).to have_content( - 'The group settings for Group 1 and Group 2 require you to enable ' \ - 'Two-Factor Authentication for your account. You need to do this ' \ - 'before ') + Timecop.freeze do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + + gitlab_sign_in(user) + + expect(current_path).to eq profile_two_factor_auth_path + expect(page).to have_content( + 'The group settings for Group 1 and Group 2 require you to enable '\ + 'Two-Factor Authentication for your account. '\ + 'You can leave Group 1 and leave Group 2. '\ + 'You need to do this '\ + 'before '\ + "#{(Time.zone.now + 2.days).strftime("%a, %-d %b %Y %H:%M:%S %z")}" + ) + end end it 'allows skipping two-factor configuration', :js do @@ -500,7 +506,8 @@ describe 'Login' do expect(current_path).to eq profile_two_factor_auth_path expect(page).to have_content( 'The group settings for Group 1 and Group 2 require you to enable ' \ - 'Two-Factor Authentication for your account.' + 'Two-Factor Authentication for your account. '\ + 'You can leave Group 1 and leave Group 2.' ) end end -- cgit v1.2.1 From f17875eb645425fbbc2c4b45cb7c9ac8850e5066 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Wed, 20 Mar 2019 17:46:58 +0800 Subject: CE-port of 6571-single-codebase Adds conditional to close assignee menu when visible in EE --- spec/features/issues_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'spec') diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 9bc340ed4bb..51508b78649 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -497,12 +497,21 @@ describe 'Issues' do it 'allows user to unselect themselves', :js do issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) + def close_dropdown_menu_if_visible + find('.dropdown-menu-toggle', visible: :all).tap do |toggle| + toggle.click if toggle.visible? + end + end + page.within '.assignee' do click_link 'Edit' click_link user.name + close_dropdown_menu_if_visible + page.within '.value .author' do expect(page).to have_content user.name end @@ -510,6 +519,8 @@ describe 'Issues' do click_link 'Edit' click_link user.name + close_dropdown_menu_if_visible + page.within '.value .assign-yourself' do expect(page).to have_content "No assignee" end -- cgit v1.2.1 From 0b324d3463d5fdd887b46d9ad043d2d45e70360b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Wed, 20 Mar 2019 14:39:07 +0100 Subject: Disallow changing namespace of a project in update method --- spec/controllers/projects_controller_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index a1662658ade..41380e79305 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -369,6 +369,23 @@ describe ProjectsController do end end + it 'does not update namespace' do + controller.instance_variable_set(:@project, project) + + params = { + namespace_id: 'test' + } + + expect do + put :update, + params: { + namespace_id: project.namespace, + id: project.id, + project: params + } + end.not_to change {project.namespace} + end + def update_project(**parameters) put :update, params: { -- cgit v1.2.1 From 6fe0739bce73b5fb7fc85ced85544413c54375c0 Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Wed, 20 Mar 2019 11:02:36 -0300 Subject: Update job detail sidebar for post-merge pipelines This commit updates the job detail view sidebar to accommodate new states introduced as part of the post-merge pipeline feature. --- spec/features/projects/jobs_spec.rb | 109 ++++++++++++ .../jobs/components/stages_dropdown_spec.js | 188 ++++++++++++++++----- 2 files changed, 257 insertions(+), 40 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 65ce872363f..224375daf71 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -2,6 +2,9 @@ require 'spec_helper' require 'tempfile' describe 'Jobs', :clean_gitlab_redis_shared_state do + include Gitlab::Routing + include ProjectForksHelper + let(:user) { create(:user) } let(:user_access_level) { :developer } let(:project) { create(:project, :repository) } @@ -121,6 +124,112 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end + context 'pipeline info block', :js do + it 'shows pipeline id and source branch' do + visit project_job_path(project, job) + + within '.js-pipeline-info' do + expect(page).to have_content("Pipeline ##{pipeline.id} for #{pipeline.ref}") + end + end + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) do + create(:merge_request, + :with_detached_merge_request_pipeline, + target_project: target_project, + source_project: source_project) + end + + let(:source_project) { project } + let(:target_project) { project } + let(:pipeline) { merge_request.all_pipelines.last } + let(:job) { create(:ci_build, pipeline: pipeline) } + + it 'shows merge request iid and source branch' do + visit project_job_path(project, job) + + within '.js-pipeline-info' do + expect(page).to have_content("for !#{pipeline.merge_request.iid} " \ + "with #{pipeline.merge_request.source_branch}") + expect(page).to have_link("!#{pipeline.merge_request.iid}", + href: project_merge_request_path(project, merge_request)) + expect(page).to have_link(pipeline.merge_request.source_branch, + href: project_commits_path(project, merge_request.source_branch)) + end + end + + context 'when source project is a forked project' do + let(:source_project) { fork_project(project, user, repository: true) } + let(:target_project) { project } + + it 'shows merge request iid and source branch' do + visit project_job_path(source_project, job) + + within '.js-pipeline-info' do + expect(page).to have_content("for !#{pipeline.merge_request.iid} " \ + "with #{pipeline.merge_request.source_branch}") + expect(page).to have_link("!#{pipeline.merge_request.iid}", + href: project_merge_request_path(project, merge_request)) + expect(page).to have_link(pipeline.merge_request.source_branch, + href: project_commits_path(source_project, merge_request.source_branch)) + end + end + end + end + + context 'when pipeline is merge request pipeline' do + let(:merge_request) do + create(:merge_request, + :with_merge_request_pipeline, + target_project: target_project, + source_project: source_project) + end + + let(:source_project) { project } + let(:target_project) { project } + let(:pipeline) { merge_request.all_pipelines.last } + let(:job) { create(:ci_build, pipeline: pipeline) } + + it 'shows merge request iid and source branch' do + visit project_job_path(project, job) + + within '.js-pipeline-info' do + expect(page).to have_content("for !#{pipeline.merge_request.iid} " \ + "with #{pipeline.merge_request.source_branch} " \ + "into #{pipeline.merge_request.target_branch}") + expect(page).to have_link("!#{pipeline.merge_request.iid}", + href: project_merge_request_path(project, merge_request)) + expect(page).to have_link(pipeline.merge_request.source_branch, + href: project_commits_path(project, merge_request.source_branch)) + expect(page).to have_link(pipeline.merge_request.target_branch, + href: project_commits_path(project, merge_request.target_branch)) + end + end + + context 'when source project is a forked project' do + let(:source_project) { fork_project(project, user, repository: true) } + let(:target_project) { project } + + it 'shows merge request iid and source branch' do + visit project_job_path(source_project, job) + + within '.js-pipeline-info' do + expect(page).to have_content("for !#{pipeline.merge_request.iid} " \ + "with #{pipeline.merge_request.source_branch} " \ + "into #{pipeline.merge_request.target_branch}") + expect(page).to have_link("!#{pipeline.merge_request.iid}", + href: project_merge_request_path(project, merge_request)) + expect(page).to have_link(pipeline.merge_request.source_branch, + href: project_commits_path(source_project, merge_request.source_branch)) + expect(page).to have_link(pipeline.merge_request.target_branch, + href: project_commits_path(project, merge_request.target_branch)) + end + end + end + end + end + context 'sidebar', :js do let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '') } diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index 9c731ae2f68..eccb4e13d67 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -1,59 +1,167 @@ import Vue from 'vue'; import component from '~/jobs/components/stages_dropdown.vue'; +import { trimText } from 'spec/helpers/vue_component_helper'; import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Stages Dropdown', () => { const Component = Vue.extend(component); let vm; - beforeEach(() => { - vm = mountComponent(Component, { - pipeline: { - id: 28029444, - details: { - status: { - details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - }, - path: 'pipeline/28029444', + const mockPipelineData = { + id: 28029444, + details: { + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', }, - stages: [ - { - name: 'build', - }, - { - name: 'test', - }, - ], - selectedStage: 'deploy', + }, + path: 'pipeline/28029444', + flags: { + merge_request_pipeline: true, + detached_merge_request_pipeline: false, + }, + merge_request: { + iid: 1234, + path: '/root/detached-merge-request-pipelines/merge_requests/1', + title: 'Update README.md', + source_branch: 'feature-1234', + source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1234', + target_branch: 'master', + target_branch_path: '/root/detached-merge-request-pipelines/branches/master', + }, + ref: { + name: 'test-branch', + }, + }; + + describe('without a merge request pipeline', () => { + let pipeline; + + beforeEach(() => { + pipeline = JSON.parse(JSON.stringify(mockPipelineData)); + delete pipeline.merge_request; + delete pipeline.flags.merge_request_pipeline; + delete pipeline.flags.detached_merge_request_pipeline; + + vm = mountComponent(Component, { + pipeline, + stages: [{ name: 'build' }, { name: 'test' }], + selectedStage: 'deploy', + }); }); - }); - afterEach(() => { - vm.$destroy(); - }); + afterEach(() => { + vm.$destroy(); + }); - it('renders pipeline status', () => { - expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull(); - }); + it('renders pipeline status', () => { + expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull(); + }); + + it('renders pipeline link', () => { + expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual( + 'pipeline/28029444', + ); + }); + + it('renders dropdown with stages', () => { + expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build'); + }); + + it('rendes selected stage', () => { + expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy'); + }); - it('renders pipeline link', () => { - expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual( - 'pipeline/28029444', - ); + it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`; + const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); + + expect(actual).toBe(expected); + }); }); - it('renders dropdown with stages', () => { - expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build'); + describe('with an "attached" merge request pipeline', () => { + let pipeline; + + beforeEach(() => { + pipeline = JSON.parse(JSON.stringify(mockPipelineData)); + pipeline.flags.merge_request_pipeline = true; + pipeline.flags.detached_merge_request_pipeline = false; + + vm = mountComponent(Component, { + pipeline, + stages: [], + selectedStage: 'deploy', + }); + }); + + it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + } into ${pipeline.merge_request.target_branch}`; + const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); + + expect(actual).toBe(expected); + }); + + it(`renders the correct merge request link`, () => { + const actual = vm.$el.querySelector('.js-mr-link').href; + + expect(actual).toContain(pipeline.merge_request.path); + }); + + it(`renders the correct source branch link`, () => { + const actual = vm.$el.querySelector('.js-source-branch-link').href; + + expect(actual).toContain(pipeline.merge_request.source_branch_path); + }); + + it(`renders the correct target branch link`, () => { + const actual = vm.$el.querySelector('.js-target-branch-link').href; + + expect(actual).toContain(pipeline.merge_request.target_branch_path); + }); }); - it('rendes selected stage', () => { - expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy'); + describe('with a detached merge request pipeline', () => { + let pipeline; + + beforeEach(() => { + pipeline = JSON.parse(JSON.stringify(mockPipelineData)); + pipeline.flags.merge_request_pipeline = false; + pipeline.flags.detached_merge_request_pipeline = true; + + vm = mountComponent(Component, { + pipeline, + stages: [], + selectedStage: 'deploy', + }); + }); + + it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + }`; + const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); + + expect(actual).toBe(expected); + }); + + it(`renders the correct merge request link`, () => { + const actual = vm.$el.querySelector('.js-mr-link').href; + + expect(actual).toContain(pipeline.merge_request.path); + }); + + it(`renders the correct source branch link`, () => { + const actual = vm.$el.querySelector('.js-source-branch-link').href; + + expect(actual).toContain(pipeline.merge_request.source_branch_path); + }); }); }); -- cgit v1.2.1 From 53a59604964b2cff06b4e25401acae50b1f82d3e Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 13 Mar 2019 10:57:05 -0300 Subject: Implement multi-line suggestions filtering Implements the filtering logic for `suggestion:-x+y` syntax. --- spec/lib/banzai/filter/output_safety_spec.rb | 29 +++++++++++++++++++ spec/lib/banzai/filter/suggestion_filter_spec.rb | 33 +++++++++++++++++++++- .../banzai/filter/syntax_highlight_filter_spec.rb | 32 ++++++++++++++++++++- spec/models/diff_note_spec.rb | 10 +++++++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 spec/lib/banzai/filter/output_safety_spec.rb (limited to 'spec') diff --git a/spec/lib/banzai/filter/output_safety_spec.rb b/spec/lib/banzai/filter/output_safety_spec.rb new file mode 100644 index 00000000000..5ffe591c9a4 --- /dev/null +++ b/spec/lib/banzai/filter/output_safety_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::OutputSafety do + subject do + Class.new do + include Banzai::Filter::OutputSafety + end.new + end + + let(:content) { '
    foo
    ' } + + context 'when given HTML is safe' do + let(:html) { content.html_safe } + + it 'returns safe HTML' do + expect(subject.escape_once(html)).to eq(html) + end + end + + context 'when given HTML is not safe' do + let(:html) { content } + + it 'returns escaped HTML' do + expect(subject.escape_once(html)).to eq(ERB::Util.html_escape_once(html)) + end + end +end diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb index b13c90b54bd..af6f002fa30 100644 --- a/spec/lib/banzai/filter/suggestion_filter_spec.rb +++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Banzai::Filter::SuggestionFilter do include FilterSpecHelper - let(:input) { "
    foo\n
    " } + let(:input) { %(
    foo\n
    ) } let(:default_context) do { suggestions_filter_enabled: true } end @@ -23,4 +23,35 @@ describe Banzai::Filter::SuggestionFilter do expect(result[:class]).to be_nil end + + context 'multi-line suggestions' do + let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR } + let(:input) { %(
    foo\n
    ) } + + context 'feature disabled' do + before do + stub_feature_flags(multi_line_suggestions: false) + end + + it 'removes data-lang-params if it matches a multi-line suggestion param' do + doc = filter(input, default_context) + pre = doc.css('pre').first + + expect(pre[data_attr]).to be_nil + end + end + + context 'feature enabled' do + before do + stub_feature_flags(multi_line_suggestions: true) + end + + it 'keeps data-lang-params' do + doc = filter(input, default_context) + pre = doc.css('pre').first + + expect(pre[data_attr]).to eq('-3+2') + end + end + end end diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index ef52c572898..05057789cc1 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -45,7 +45,10 @@ describe Banzai::Filter::SyntaxHighlightFilter do end context "languages that should be passed through" do - %w(math mermaid plantuml).each do |lang| + let(:delimiter) { described_class::PARAMS_DELIMITER } + let(:data_attr) { described_class::LANG_PARAMS_ATTR } + + %w(math mermaid plantuml suggestion).each do |lang| context "when #{lang} is specified" do it "highlights as plaintext but with the correct language attribute and class" do result = filter(%{
    This is a test
    }) @@ -55,6 +58,33 @@ describe Banzai::Filter::SyntaxHighlightFilter do include_examples "XSS prevention", lang end + + context "when #{lang} has extra params" do + let(:lang_params) { 'foo-bar-kux' } + + it "includes data-lang-params tag with extra information" do + result = filter(%{
    This is a test
    }) + + expect(result.to_html).to eq(%{
    This is a test
    }) + end + + include_examples "XSS prevention", lang + include_examples "XSS prevention", + "#{lang}#{described_class::PARAMS_DELIMITER}<script>alert(1)</script>" + include_examples "XSS prevention", + "#{lang}#{described_class::PARAMS_DELIMITER}" + end + end + + context 'when multiple param delimiters are used' do + let(:lang) { 'suggestion' } + let(:lang_params) { '-1+10' } + + it "delimits on the first appearence" do + result = filter(%{
    This is a test
    }) + + expect(result.to_html).to eq(%{
    This is a test
    }) + end end end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index fda00a693f0..67e5f4f7e41 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -336,6 +336,16 @@ describe DiffNote do end end + describe '#banzai_render_context' do + let(:note) { create(:diff_note_on_merge_request) } + + it 'includes expected context' do + context = note.banzai_render_context(:note) + + expect(context).to include(suggestions_filter_enabled: true, noteable: note.noteable, project: note.project) + end + end + describe "image diff notes" do subject { build(:image_diff_note_on_merge_request, project: project, noteable: merge_request) } -- cgit v1.2.1 From a24cb6de02a57215db9f6c582f4244c8bd190211 Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Wed, 20 Mar 2019 12:39:27 -0300 Subject: Update pipeline list view This commit updates the pipeline page and related components to include new pipeline information added by the post-merge pipeline feature. --- .../user_sees_merge_request_pipelines_spec.rb | 78 +++++++++--------- spec/features/projects/pipelines/pipelines_spec.rb | 95 ++++++++++++++++++++++ .../environments/environment_item_spec.js | 2 +- spec/javascripts/pipelines/pipeline_url_spec.js | 12 +-- .../vue_shared/components/commit_spec.js | 90 +++++++++++++++++++- 5 files changed, 231 insertions(+), 46 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb index 97b2aa82fce..28f88718ec1 100644 --- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe 'Merge request > User sees merge request pipelines', :js do +describe 'Merge request > User sees pipelines triggered by merge request', :js do include ProjectForksHelper include TestReportsHelper @@ -47,7 +47,7 @@ describe 'Merge request > User sees merge request pipelines', :js do .execute(:push) end - let!(:merge_request_pipeline) do + let!(:detached_merge_request_pipeline) do Ci::CreatePipelineService.new(project, user, ref: 'feature') .execute(:merge_request_event, merge_request: merge_request) end @@ -60,16 +60,16 @@ describe 'Merge request > User sees merge request pipelines', :js do end end - it 'sees branch pipelines and merge request pipelines in correct order' do + it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do expect(page).to have_selector('.ci-pending', count: 2) - expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}") + expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}") end end - it 'sees the latest merge request pipeline as the head pipeline' do + it 'sees the latest detached merge request pipeline as the head pipeline' do page.within('.ci-widget-content') do - expect(page).to have_content("##{merge_request_pipeline.id}") + expect(page).to have_content("##{detached_merge_request_pipeline.id}") end end @@ -79,7 +79,7 @@ describe 'Merge request > User sees merge request pipelines', :js do .execute(:push) end - let!(:merge_request_pipeline_2) do + let!(:detached_merge_request_pipeline_2) do Ci::CreatePipelineService.new(project, user, ref: 'feature') .execute(:merge_request_event, merge_request: merge_request) end @@ -92,15 +92,15 @@ describe 'Merge request > User sees merge request pipelines', :js do end end - it 'sees branch pipelines and merge request pipelines in correct order' do + it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do expect(page).to have_selector('.ci-pending', count: 4) expect(all('.js-pipeline-url-link')[0]) - .to have_content("##{merge_request_pipeline_2.id}") + .to have_content("##{detached_merge_request_pipeline_2.id}") expect(all('.js-pipeline-url-link')[1]) - .to have_content("##{merge_request_pipeline.id}") + .to have_content("##{detached_merge_request_pipeline.id}") expect(all('.js-pipeline-url-link')[2]) .to have_content("##{push_pipeline_2.id}") @@ -110,25 +110,25 @@ describe 'Merge request > User sees merge request pipelines', :js do end end - it 'sees merge request tag for merge request pipelines' do + it 'sees detached tag for detached merge request pipelines' do page.within('.ci-table') do expect(all('.pipeline-tags')[0]) - .to have_content("merge request") + .to have_content("detached") expect(all('.pipeline-tags')[1]) - .to have_content("merge request") + .to have_content("detached") expect(all('.pipeline-tags')[2]) - .not_to have_content("merge request") + .not_to have_content("detached") expect(all('.pipeline-tags')[3]) - .not_to have_content("merge request") + .not_to have_content("detached") end end - it 'sees the latest merge request pipeline as the head pipeline' do + it 'sees the latest detached merge request pipeline as the head pipeline' do page.within('.ci-widget-content') do - expect(page).to have_content("##{merge_request_pipeline_2.id}") + expect(page).to have_content("##{detached_merge_request_pipeline_2.id}") end end end @@ -140,16 +140,16 @@ describe 'Merge request > User sees merge request pipelines', :js do wait_for_requests end - context 'when merge request pipeline is pending' do + context 'when detached merge request pipeline is pending' do it 'waits the head pipeline' do expect(page).to have_content('to be merged automatically when the pipeline succeeds') expect(page).to have_link('Cancel automatic merge') end end - context 'when merge request pipeline succeeds' do + context 'when detached merge request pipeline succeeds' do before do - merge_request_pipeline.succeed! + detached_merge_request_pipeline.succeed! wait_for_requests end @@ -218,7 +218,7 @@ describe 'Merge request > User sees merge request pipelines', :js do .execute(:push) end - let!(:merge_request_pipeline) do + let!(:detached_merge_request_pipeline) do Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature') .execute(:merge_request_event, merge_request: merge_request) end @@ -236,16 +236,16 @@ describe 'Merge request > User sees merge request pipelines', :js do end end - it 'sees branch pipelines and merge request pipelines in correct order' do + it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do expect(page).to have_selector('.ci-pending', count: 2) - expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}") + expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}") end end - it 'sees the latest merge request pipeline as the head pipeline' do + it 'sees the latest detached merge request pipeline as the head pipeline' do page.within('.ci-widget-content') do - expect(page).to have_content("##{merge_request_pipeline.id}") + expect(page).to have_content("##{detached_merge_request_pipeline.id}") end end @@ -261,7 +261,7 @@ describe 'Merge request > User sees merge request pipelines', :js do .execute(:push) end - let!(:merge_request_pipeline_2) do + let!(:detached_merge_request_pipeline_2) do Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature') .execute(:merge_request_event, merge_request: merge_request) end @@ -274,15 +274,15 @@ describe 'Merge request > User sees merge request pipelines', :js do end end - it 'sees branch pipelines and merge request pipelines in correct order' do + it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do expect(page).to have_selector('.ci-pending', count: 4) expect(all('.js-pipeline-url-link')[0]) - .to have_content("##{merge_request_pipeline_2.id}") + .to have_content("##{detached_merge_request_pipeline_2.id}") expect(all('.js-pipeline-url-link')[1]) - .to have_content("##{merge_request_pipeline.id}") + .to have_content("##{detached_merge_request_pipeline.id}") expect(all('.js-pipeline-url-link')[2]) .to have_content("##{push_pipeline_2.id}") @@ -292,25 +292,25 @@ describe 'Merge request > User sees merge request pipelines', :js do end end - it 'sees merge request tag for merge request pipelines' do + it 'sees detached tag for detached merge request pipelines' do page.within('.ci-table') do expect(all('.pipeline-tags')[0]) - .to have_content("merge request") + .to have_content("detached") expect(all('.pipeline-tags')[1]) - .to have_content("merge request") + .to have_content("detached") expect(all('.pipeline-tags')[2]) - .not_to have_content("merge request") + .not_to have_content("detached") expect(all('.pipeline-tags')[3]) - .not_to have_content("merge request") + .not_to have_content("detached") end end - it 'sees the latest merge request pipeline as the head pipeline' do + it 'sees the latest detached merge request pipeline as the head pipeline' do page.within('.ci-widget-content') do - expect(page).to have_content("##{merge_request_pipeline_2.id}") + expect(page).to have_content("##{detached_merge_request_pipeline_2.id}") end end @@ -328,16 +328,16 @@ describe 'Merge request > User sees merge request pipelines', :js do wait_for_requests end - context 'when merge request pipeline is pending' do + context 'when detached merge request pipeline is pending' do it 'waits the head pipeline' do expect(page).to have_content('to be merged automatically when the pipeline succeeds') expect(page).to have_link('Cancel automatic merge') end end - context 'when merge request pipeline succeeds' do + context 'when detached merge request pipeline succeeds' do before do - merge_request_pipeline.succeed! + detached_merge_request_pipeline.succeed! wait_for_requests end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 88d7c9ef8bd..0e151efa9df 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'Pipelines', :js do + include ProjectForksHelper + let(:project) { create(:project) } context 'when user is logged in' do @@ -165,6 +167,99 @@ describe 'Pipelines', :js do end end + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) do + create(:merge_request, + :with_detached_merge_request_pipeline, + source_project: source_project, + target_project: target_project) + end + + let!(:pipeline) { merge_request.all_pipelines.first } + let(:source_project) { project } + let(:target_project) { project } + + before do + visit project_pipelines_path(source_project) + end + + shared_examples_for 'showing detached merge request pipeline information' do + it 'shows detached tag for the pipeline' do + within '.pipeline-tags' do + expect(page).to have_content('detached') + end + end + + it 'shows the link of the merge request' do + within '.branch-commit' do + expect(page).to have_link(merge_request.iid, + href: project_merge_request_path(project, merge_request)) + end + end + + it 'does not show the ref of the pipeline' do + within '.branch-commit' do + expect(page).not_to have_link(pipeline.ref) + end + end + end + + it_behaves_like 'showing detached merge request pipeline information' + + context 'when source project is a forked project' do + let(:source_project) { fork_project(project, user, repository: true) } + + it_behaves_like 'showing detached merge request pipeline information' + end + end + + context 'when pipeline is merge request pipeline' do + let(:merge_request) do + create(:merge_request, + :with_merge_request_pipeline, + source_project: source_project, + target_project: target_project, + merge_sha: target_project.commit.sha) + end + + let!(:pipeline) { merge_request.all_pipelines.first } + let(:source_project) { project } + let(:target_project) { project } + + before do + visit project_pipelines_path(source_project) + end + + shared_examples_for 'Correct merge request pipeline information' do + it 'does not show detached tag for the pipeline' do + within '.pipeline-tags' do + expect(page).not_to have_content('detached') + end + end + + it 'shows the link of the merge request' do + within '.branch-commit' do + expect(page).to have_link(merge_request.iid, + href: project_merge_request_path(project, merge_request)) + end + end + + it 'does not show the ref of the pipeline' do + within '.branch-commit' do + expect(page).not_to have_link(pipeline.ref) + end + end + end + + it_behaves_like 'Correct merge request pipeline information' + + context 'when source project is a forked project' do + let(:source_project) { fork_project(project, user, repository: true) } + + it_behaves_like 'Correct merge request pipeline information' + end + end + context 'when pipeline has configuration errors' do let(:pipeline) do create(:ci_pipeline, :invalid, project: project) diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js index 8b877994515..388d7063d13 100644 --- a/spec/javascripts/environments/environment_item_spec.js +++ b/spec/javascripts/environments/environment_item_spec.js @@ -60,7 +60,7 @@ describe('Environment item', () => { sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', ref: { name: 'master', - ref_path: 'root/ci-folders/tree/master', + ref_url: 'root/ci-folders/tree/master', }, tag: true, 'last?': true, diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index ea917b36526..faad49a78b0 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -100,7 +100,8 @@ describe('Pipeline Url Component', () => { latest: true, yaml_errors: true, stuck: true, - merge_request: true, + merge_request_pipeline: true, + detached_merge_request_pipeline: true, }, }, autoDevopsHelpPath: 'foo', @@ -108,15 +109,16 @@ describe('Pipeline Url Component', () => { }).$mount(); expect(component.$el.querySelector('.js-pipeline-url-latest').textContent).toContain('latest'); + expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain( 'yaml invalid', ); - expect(component.$el.querySelector('.js-pipeline-url-mergerequest').textContent).toContain( - 'merge request', - ); - expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck'); + + expect(component.$el.querySelector('.js-pipeline-url-detached').textContent).toContain( + 'detached', + ); }); it('should render a badge for autodevops', () => { diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js index 18fcdf7ede1..f2e20f626b5 100644 --- a/spec/javascripts/vue_shared/components/commit_spec.js +++ b/spec/javascripts/vue_shared/components/commit_spec.js @@ -61,7 +61,7 @@ describe('Commit component', () => { }); it('should render a tag icon if it represents a tag', () => { - expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-tag'); + expect(component.$el.querySelector('.icon-container svg.ic-tag')).not.toBeNull(); }); it('should render a link to the ref url', () => { @@ -143,4 +143,92 @@ describe('Commit component', () => { ); }); }); + + describe('When commit ref is provided, but merge ref is not', () => { + it('should render the commit ref', () => { + props = { + tag: false, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: + 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + shortSha: 'b7836edd', + title: null, + author: {}, + }; + + component = mountComponent(CommitComponent, props); + const refEl = component.$el.querySelector('.ref-name'); + + expect(refEl.textContent).toContain('master'); + + expect(refEl.href).toBe(props.commitRef.ref_url); + + expect(refEl.getAttribute('data-original-title')).toBe(props.commitRef.name); + + expect(component.$el.querySelector('.icon-container .ic-branch')).not.toBeNull(); + }); + }); + + describe('When both commit and merge ref are provided', () => { + it('should render the merge ref', () => { + props = { + tag: false, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: + 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + mergeRequestRef: { + iid: 1234, + path: 'https://example.com/path/to/mr', + title: 'Test MR', + }, + shortSha: 'b7836edd', + title: null, + author: {}, + }; + + component = mountComponent(CommitComponent, props); + const refEl = component.$el.querySelector('.ref-name'); + + expect(refEl.textContent).toContain('1234'); + + expect(refEl.href).toBe(props.mergeRequestRef.path); + + expect(refEl.getAttribute('data-original-title')).toBe(props.mergeRequestRef.title); + + expect(component.$el.querySelector('.icon-container .ic-git-merge')).not.toBeNull(); + }); + }); + + describe('When showRefInfo === false', () => { + it('should not render any ref info', () => { + props = { + tag: false, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: + 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + mergeRequestRef: { + iid: 1234, + path: '/path/to/mr', + title: 'Test MR', + }, + shortSha: 'b7836edd', + title: null, + author: {}, + showRefInfo: false, + }; + + component = mountComponent(CommitComponent, props); + + expect(component.$el.querySelector('.ref-name')).toBeNull(); + }); + }); }); -- cgit v1.2.1 From 7028b3761ebc71db18a230b65c31de96736607bb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 20 Mar 2019 13:21:29 -0300 Subject: Gracefully handles excluded fields from attributes during serialization --- spec/lib/gitlab/json_cache_spec.rb | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb index 2cae8ec031a..b7dc8234bdf 100644 --- a/spec/lib/gitlab/json_cache_spec.rb +++ b/spec/lib/gitlab/json_cache_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::JsonCache do let(:namespace) { 'geo' } let(:key) { 'foo' } let(:expanded_key) { "#{namespace}:#{key}:#{Rails.version}" } - let(:broadcast_message) { create(:broadcast_message) } + set(:broadcast_message) { create(:broadcast_message) } subject(:cache) { described_class.new(namespace: namespace, backend: backend) } @@ -321,6 +321,42 @@ describe Gitlab::JsonCache do expect(result).to be_new_record end + + it 'gracefully handles bad cached entry' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return('{') + + expect(cache.read(key, BroadcastMessage)).to be_nil + end + + it 'gracefully handles an empty hash' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return('{}') + + expect(cache.read(key, BroadcastMessage)).to be_a(BroadcastMessage) + end + + it 'gracefully handles unknown attributes' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(broadcast_message.attributes.merge(unknown_attribute: 1).to_json) + + expect(cache.read(key, BroadcastMessage)).to be_nil + end + + it 'gracefully handles excluded fields from attributes during serialization' do + backend.write(expanded_key, broadcast_message.to_json) + + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + excluded_fields = BroadcastMessage.cached_markdown_fields.html_fields + + (excluded_fields + ['cached_markdown_version']).each do |field| + expect(result.public_send(field)).to be_nil + end + end end it "returns the result of the block when 'as' option is nil" do -- cgit v1.2.1 From 732f892db308863f2a2db736949c94ae9d613678 Mon Sep 17 00:00:00 2001 From: Igor Drozdov Date: Wed, 20 Mar 2019 20:23:23 +0300 Subject: Return cached languages if they've been detected before --- .../controllers/projects/graphs_controller_spec.rb | 1 + spec/features/projects/graph_spec.rb | 2 + spec/requests/api/projects_spec.rb | 16 +++++--- .../detect_repository_languages_service_spec.rb | 10 +++++ .../projects/repository_languages_service_spec.rb | 48 ++++++++++++++++++++++ 5 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 spec/services/projects/repository_languages_service_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 8decd8f1382..df6a6e00f73 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -27,6 +27,7 @@ describe Projects::GraphsController do describe 'charts' do context 'when languages were previously detected' do + let(:project) { create(:project, :repository, detected_repository_languages: true) } let!(:repository_language) { create(:repository_language, project: project) } it 'sets the languages properly' do diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb index 9665f1755d6..e1bc18519a2 100644 --- a/spec/features/projects/graph_spec.rb +++ b/spec/features/projects/graph_spec.rb @@ -6,6 +6,8 @@ describe 'Project Graph', :js do let(:branch_name) { 'master' } before do + ::Projects::DetectRepositoryLanguagesService.new(project, user).execute + project.add_maintainer(user) sign_in(user) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 60d9d7fed13..fdbb78b8829 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -13,12 +13,18 @@ shared_examples 'languages and percentages JSON response' do ) end - it 'returns expected language values' do - get api("/projects/#{project.id}/languages", user) + context "when the languages haven't been detected yet" do + it 'returns expected language values' do + get api("/projects/#{project.id}/languages", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq({}) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to eq(expected_languages) - expect(json_response.count).to be > 1 + get api("/projects/#{project.id}/languages", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(JSON.parse(response.body)).to eq(expected_languages) + end end context 'when the languages were detected before' do diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb index deea1189cdf..b38bd62c9f0 100644 --- a/spec/services/projects/detect_repository_languages_service_spec.rb +++ b/spec/services/projects/detect_repository_languages_service_spec.rb @@ -19,6 +19,10 @@ describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_ expect(names).to eq(%w[Ruby JavaScript HTML CoffeeScript]) end + + it 'updates detected_repository_languages flag' do + expect { subject.execute }.to change(project, :detected_repository_languages).to(true) + end end context 'with a previous detection' do @@ -36,6 +40,12 @@ describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_ expect(repository_languages).to eq(%w[Ruby D]) end + + it "doesn't touch detected_repository_languages flag" do + expect(project).not_to receive(:update_column).with(:detected_repository_languages, true) + + subject.execute + end end context 'when no repository exists' do diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb new file mode 100644 index 00000000000..61c1b8c5ec1 --- /dev/null +++ b/spec/services/projects/repository_languages_service_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Projects::RepositoryLanguagesService do + let(:service) { described_class.new(project, project.owner) } + + context 'when detected_repository_languages flag is set' do + let(:project) { create(:project) } + + context 'when a project is without detected programming languages' do + it 'schedules a worker and returns the empty result' do + expect(::DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id, project.owner.id) + expect(service.execute).to eq([]) + end + end + + context 'when a project is with detected programming languages' do + let!(:repository_language) { create(:repository_language, project: project) } + + it 'does not schedule a worker and returns the detected languages' do + expect(::DetectRepositoryLanguagesWorker).not_to receive(:perform_async).with(project.id, project.owner.id) + + languages = service.execute + + expect(languages.size).to eq(1) + expect(languages.last.attributes.values).to eq( + [project.id, repository_language.programming_language_id, repository_language.share] + ) + end + + it 'sets detected_repository_languages flag' do + expect { service.execute }.to change(project, :detected_repository_languages).from(nil).to(true) + end + end + end + + context 'when detected_repository_languages flag is not set' do + let!(:repository_language) { create(:repository_language, project: project) } + let(:project) { create(:project, detected_repository_languages: true) } + let(:languages) { service.execute } + + it 'returns repository languages' do + expect(languages.size).to eq(1) + expect(languages.last.attributes.values).to eq( + [project.id, repository_language.programming_language_id, repository_language.share] + ) + end + end +end -- cgit v1.2.1 From 3fd8e612719dbeb9d4790aef6875b005d9dd6aff Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 20 Mar 2019 15:34:30 -0300 Subject: Add option to not exclude _html fields from attributes --- spec/models/concerns/cache_markdown_field_spec.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 447279f19a8..a2e19f7d687 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -23,6 +23,7 @@ describe CacheMarkdownField do include CacheMarkdownField cache_markdown_field :foo cache_markdown_field :baz, pipeline: :single_line + cache_markdown_field :zoo, hidden: false def self.add_attr(name) self.attribute_names += [name] @@ -35,7 +36,7 @@ describe CacheMarkdownField do add_attr :cached_markdown_version - [:foo, :foo_html, :bar, :baz, :baz_html].each do |name| + [:foo, :foo_html, :bar, :baz, :baz_html, :zoo, :zoo_html].each do |name| add_attr(name) end @@ -84,8 +85,8 @@ describe CacheMarkdownField do end describe '.attributes' do - it 'excludes cache attributes' do - expect(thing.attributes.keys.sort).to eq(%w[bar baz foo]) + it 'excludes cache attributes that is hidden by default' do + expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html]) end end @@ -297,7 +298,12 @@ describe CacheMarkdownField do it 'saves the changes using #update_columns' do expect(thing).to receive(:persisted?).and_return(true) expect(thing).to receive(:update_columns) - .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version) + .with( + "foo_html" => updated_html, + "baz_html" => "", + "zoo_html" => "", + "cached_markdown_version" => cache_version + ) thing.refresh_markdown_cache! end -- cgit v1.2.1 From bcc988a6c6c9896450b7c40d2718a1aa9dbd689f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 20 Mar 2019 15:43:40 -0300 Subject: Does not exclude message_html from attributes --- spec/models/broadcast_message_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 89839709131..30ca07d5d2c 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -95,6 +95,12 @@ describe BroadcastMessage do end end + describe '#attributes' do + it 'includes message_html field' do + expect(subject.attributes.keys).to include("cached_markdown_version", "message_html") + end + end + describe '#active?' do it 'is truthy when started and not ended' do message = build(:broadcast_message) -- cgit v1.2.1 From 69dc893da3f19d465c12c6c183270daf38df14b3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 20 Mar 2019 15:44:05 -0300 Subject: Fix spec for Gitlab::JsonCache --- spec/lib/gitlab/json_cache_spec.rb | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb index b7dc8234bdf..b82c09af306 100644 --- a/spec/lib/gitlab/json_cache_spec.rb +++ b/spec/lib/gitlab/json_cache_spec.rb @@ -146,6 +146,18 @@ describe Gitlab::JsonCache do expect(cache.read(key, BroadcastMessage)).to be_nil end + + it 'gracefully handles excluded fields from attributes during serialization' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(broadcast_message.attributes.except("message_html").to_json) + + result = cache.read(key, BroadcastMessage) + + BroadcastMessage.cached_markdown_fields.html_fields.each do |field| + expect(result.public_send(field)).to be_nil + end + end end context 'when the cached value is an array' do @@ -327,7 +339,9 @@ describe Gitlab::JsonCache do .with(expanded_key) .and_return('{') - expect(cache.read(key, BroadcastMessage)).to be_nil + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result).to eq 'block result' end it 'gracefully handles an empty hash' do @@ -335,7 +349,7 @@ describe Gitlab::JsonCache do .with(expanded_key) .and_return('{}') - expect(cache.read(key, BroadcastMessage)).to be_a(BroadcastMessage) + expect(cache.fetch(key, as: BroadcastMessage)).to be_a(BroadcastMessage) end it 'gracefully handles unknown attributes' do @@ -343,17 +357,19 @@ describe Gitlab::JsonCache do .with(expanded_key) .and_return(broadcast_message.attributes.merge(unknown_attribute: 1).to_json) - expect(cache.read(key, BroadcastMessage)).to be_nil + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result).to eq 'block result' end it 'gracefully handles excluded fields from attributes during serialization' do - backend.write(expanded_key, broadcast_message.to_json) + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(broadcast_message.attributes.except("message_html").to_json) result = cache.fetch(key, as: BroadcastMessage) { 'block result' } - excluded_fields = BroadcastMessage.cached_markdown_fields.html_fields - - (excluded_fields + ['cached_markdown_version']).each do |field| + BroadcastMessage.cached_markdown_fields.html_fields.each do |field| expect(result.public_send(field)).to be_nil end end -- cgit v1.2.1 From 6264694d572c48f4517f8999a61cc6e0fb32ccae Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 20 Mar 2019 17:01:27 -0300 Subject: Rename the hidden option to whitelisted --- spec/models/concerns/cache_markdown_field_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index a2e19f7d687..7d555f15e39 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -23,7 +23,7 @@ describe CacheMarkdownField do include CacheMarkdownField cache_markdown_field :foo cache_markdown_field :baz, pipeline: :single_line - cache_markdown_field :zoo, hidden: false + cache_markdown_field :zoo, whitelisted: true def self.add_attr(name) self.attribute_names += [name] @@ -85,7 +85,7 @@ describe CacheMarkdownField do end describe '.attributes' do - it 'excludes cache attributes that is hidden by default' do + it 'excludes cache attributes that is blacklisted by default' do expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html]) end end -- cgit v1.2.1 From 6fcc65b51330bec6bde577d1bf761f69b6f8605a Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Thu, 21 Mar 2019 12:42:28 +0800 Subject: Update Gitlab::AuthorizedKeys specs to be clearer --- spec/lib/gitlab/authorized_keys_spec.rb | 44 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb index ffe78967c2a..42bc509eeef 100644 --- a/spec/lib/gitlab/authorized_keys_spec.rb +++ b/spec/lib/gitlab/authorized_keys_spec.rb @@ -7,16 +7,16 @@ describe Gitlab::AuthorizedKeys do subject { described_class.new(logger) } - after do - delete_authorized_keys_file - end - describe '#add_key' do context 'authorized_keys file exists' do before do create_authorized_keys_fixture end + after do + delete_authorized_keys_file + end + it "adds a line at the end of the file and strips trailing garbage" do auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E" @@ -28,6 +28,10 @@ describe Gitlab::AuthorizedKeys do end context 'authorized_keys file does not exist' do + before do + delete_authorized_keys_file + end + it 'creates the file' do expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to be_truthy expect(File.exist?(tmp_authorized_keys_path)).to be_truthy @@ -48,6 +52,10 @@ describe Gitlab::AuthorizedKeys do create_authorized_keys_fixture end + after do + delete_authorized_keys_file + end + it "adds lines at the end of the file" do auth_line1 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG" auth_line2 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG" @@ -69,6 +77,10 @@ describe Gitlab::AuthorizedKeys do end context 'authorized_keys file does not exist' do + before do + delete_authorized_keys_file + end + it 'creates the file' do expect(subject.batch_add_keys(keys)).to be_truthy expect(File.exist?(tmp_authorized_keys_path)).to be_truthy @@ -82,6 +94,10 @@ describe Gitlab::AuthorizedKeys do create_authorized_keys_fixture end + after do + delete_authorized_keys_file + end + it "removes the right line" do other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" @@ -98,6 +114,10 @@ describe Gitlab::AuthorizedKeys do end context 'authorized_keys file does not exist' do + before do + delete_authorized_keys_file + end + it 'returns false' do expect(subject.rm_key('key-741')).to be_falsey end @@ -110,12 +130,20 @@ describe Gitlab::AuthorizedKeys do create_authorized_keys_fixture end + after do + delete_authorized_keys_file + end + it "returns true" do expect(subject.clear).to be_truthy end end context 'authorized_keys file does not exist' do + before do + delete_authorized_keys_file + end + it "still returns true" do expect(subject.clear).to be_truthy end @@ -131,12 +159,20 @@ describe Gitlab::AuthorizedKeys do ) end + after do + delete_authorized_keys_file + end + it 'returns array of key IDs' do expect(subject.list_key_ids).to eq([1, 2, 3, 9000]) end end context 'authorized_keys file does not exist' do + before do + delete_authorized_keys_file + end + it 'returns an empty array' do expect(subject.list_key_ids).to be_empty end -- cgit v1.2.1 From 01aa1ef77beee21a588f8b6b27bd372fc7ed7c8d Mon Sep 17 00:00:00 2001 From: James Fargher Date: Thu, 21 Mar 2019 08:06:47 +0000 Subject: Update dashboards to additionally use new environment selector Deploy boards now will check for app.gitlab.com/env and app.gitlab.com/app --- spec/lib/gitlab/kubernetes_spec.rb | 34 ++++++++++++++++++++-- spec/models/clusters/platforms/kubernetes_spec.rb | 6 ++-- .../project_services/kubernetes_service_spec.rb | 5 ++-- spec/support/helpers/kubernetes_helpers.rb | 16 ++++++---- 4 files changed, 49 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb index f326d57e9c6..57b570a9166 100644 --- a/spec/lib/gitlab/kubernetes_spec.rb +++ b/spec/lib/gitlab/kubernetes_spec.rb @@ -40,10 +40,40 @@ describe Gitlab::Kubernetes do describe '#filter_by_label' do it 'returns matching labels' do - matching_items = [kube_pod(app: 'foo')] + matching_items = [kube_pod(track: 'foo'), kube_deployment(track: 'foo')] + items = matching_items + [kube_pod, kube_deployment] + + expect(filter_by_label(items, 'track' => 'foo')).to eq(matching_items) + end + end + + describe '#filter_by_annotation' do + it 'returns matching labels' do + matching_items = [kube_pod(environment_slug: 'foo'), kube_deployment(environment_slug: 'foo')] + items = matching_items + [kube_pod, kube_deployment] + + expect(filter_by_annotation(items, 'app.gitlab.com/env' => 'foo')).to eq(matching_items) + end + end + + describe '#filter_by_project_environment' do + let(:matching_pod) { kube_pod(environment_slug: 'production', project_slug: 'my-cool-app') } + + it 'returns matching legacy env label' do + matching_pod['metadata']['annotations'].delete('app.gitlab.com/app') + matching_pod['metadata']['annotations'].delete('app.gitlab.com/env') + matching_pod['metadata']['labels']['app'] = 'production' + matching_items = [matching_pod] + items = matching_items + [kube_pod] + + expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items) + end + + it 'returns matching env label' do + matching_items = [matching_pod] items = matching_items + [kube_pod] - expect(filter_by_label(items, app: 'foo')).to eq(matching_items) + expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items) end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index cc93a1b4965..af65530e663 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -375,14 +375,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end context 'with valid pods' do - let(:pod) { kube_pod(app: environment.slug) } - let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") } + let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } before do stub_reactive_cache( service, - pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")] + pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] ) end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index cb20c747972..56e587262ef 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -323,13 +323,14 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end context 'with valid pods' do - let(:pod) { kube_pod(app: environment.slug) } + let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } before do stub_reactive_cache( service, - pods: [pod, pod, kube_pod(app: "should-be-filtered-out")] + pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] ) end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index cca11e112c9..ac52acb6570 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -250,16 +250,19 @@ module KubernetesHelpers # This is a partial response, it will have many more elements in reality but # these are the ones we care about at the moment - def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil) + def kube_pod(name: "kube-pod", environment_slug: "production", project_slug: "project-path-slug", status: "Running", track: nil) { "metadata" => { "name" => name, "generate_name" => "generated-name-with-suffix", "creationTimestamp" => "2016-11-25T19:55:19Z", + "annotations" => { + "app.gitlab.com/env" => environment_slug, + "app.gitlab.com/app" => project_slug + }, "labels" => { - "app" => app, "track" => track - } + }.compact }, "spec" => { "containers" => [ @@ -293,13 +296,16 @@ module KubernetesHelpers } end - def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil) + def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil) { "metadata" => { "name" => name, "generation" => 4, + "annotations" => { + "app.gitlab.com/env" => environment_slug, + "app.gitlab.com/app" => project_slug + }, "labels" => { - "app" => app, "track" => track }.compact }, -- cgit v1.2.1 From 727ec95528c3b928992406e570427728e7186fd4 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 11 Mar 2019 16:52:40 +0800 Subject: Hide related branches when user does not have permission Guest user of a project should not see branches --- .../user_creates_branch_and_merge_request_spec.rb | 36 +++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index 693ad89069c..0a006011c89 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -1,6 +1,7 @@ require 'rails_helper' describe 'User creates branch and merge request on issue page', :js do + let(:membership_level) { :developer } let(:user) { create(:user) } let!(:project) { create(:project, :repository) } let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') } @@ -17,7 +18,7 @@ describe 'User creates branch and merge request on issue page', :js do context 'when signed in' do before do - project.add_developer(user) + project.add_user(user, membership_level) sign_in(user) end @@ -167,6 +168,39 @@ describe 'User creates branch and merge request on issue page', :js do expect(page).not_to have_css('.create-mr-dropdown-wrap') end end + + context 'when related branch exists' do + let!(:project) { create(:project, :repository, :private) } + let(:branch_name) { "#{issue.iid}-foo" } + + before do + project.repository.create_branch(branch_name, 'master') + + visit project_issue_path(project, issue) + end + + context 'when user is developer' do + it 'shows related branches' do + expect(page).to have_css('#related-branches') + + wait_for_requests + + expect(page).to have_content(branch_name) + end + end + + context 'when user is guest' do + let(:membership_level) { :guest } + + it 'does not show related branches' do + expect(page).not_to have_css('#related-branches') + + wait_for_requests + + expect(page).not_to have_content(branch_name) + end + end + end end private -- cgit v1.2.1 From a97ec84f0503b164e57e7f43b217ddfea864209f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 21 Mar 2019 10:33:29 +0000 Subject: Revert "Merge branch '58805-allow-incomplete-commit-data-to-be-fetched-from-collection' into 'master'" This reverts merge request !26144 --- spec/lib/gitlab/git/commit_spec.rb | 12 --- spec/models/commit_collection_spec.rb | 86 +--------------------- spec/models/merge_request_spec.rb | 37 ++++++---- .../merge_request_widget_entity_spec.rb | 15 ++-- 4 files changed, 29 insertions(+), 121 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 4a4ac833e39..3fb41a626b2 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -537,18 +537,6 @@ describe Gitlab::Git::Commit, :seed_helper do end end - describe '#gitaly_commit?' do - context 'when the commit data comes from gitaly' do - it { expect(commit.gitaly_commit?).to eq(true) } - end - - context 'when the commit data comes from a Hash' do - let(:commit) { described_class.new(repository, sample_commit_hash) } - - it { expect(commit.gitaly_commit?).to eq(false) } - end - end - describe '#has_zero_stats?' do it { expect(commit.has_zero_stats?).to eq(false) } end diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb index 30c504ebea8..0f5d03ff458 100644 --- a/spec/models/commit_collection_spec.rb +++ b/spec/models/commit_collection_spec.rb @@ -37,92 +37,12 @@ describe CommitCollection do describe '#without_merge_commits' do it 'returns all commits except merge commits' do - merge_commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98") - expect(merge_commit).to receive(:merge_commit?).and_return(true) - collection = described_class.new(project, [ - commit, - merge_commit + build(:commit), + build(:commit, :merge_commit) ]) - expect(collection.without_merge_commits).to contain_exactly(commit) - end - end - - describe 'enrichment methods' do - let(:gitaly_commit) { commit } - let(:hash_commit) { Commit.from_hash(gitaly_commit.to_hash, project) } - - describe '#unenriched' do - it 'returns all commits that are not backed by gitaly data' do - collection = described_class.new(project, [gitaly_commit, hash_commit]) - - expect(collection.unenriched).to contain_exactly(hash_commit) - end - end - - describe '#fully_enriched?' do - it 'returns true when all commits are backed by gitaly data' do - collection = described_class.new(project, [gitaly_commit, gitaly_commit]) - - expect(collection.fully_enriched?).to eq(true) - end - - it 'returns false when any commits are not backed by gitaly data' do - collection = described_class.new(project, [gitaly_commit, hash_commit]) - - expect(collection.fully_enriched?).to eq(false) - end - - it 'returns true when the collection is empty' do - collection = described_class.new(project, []) - - expect(collection.fully_enriched?).to eq(true) - end - end - - describe '#enrich!' do - it 'replaces commits in the collection with those backed by gitaly data' do - collection = described_class.new(project, [hash_commit]) - - collection.enrich! - - new_commit = collection.commits.first - expect(new_commit.id).to eq(hash_commit.id) - expect(hash_commit.gitaly_commit?).to eq(false) - expect(new_commit.gitaly_commit?).to eq(true) - end - - it 'maintains the original order of the commits' do - gitaly_commits = [gitaly_commit] * 3 - hash_commits = [hash_commit] * 3 - # Interleave the gitaly and hash commits together - original_commits = gitaly_commits.zip(hash_commits).flatten - collection = described_class.new(project, original_commits) - - collection.enrich! - - original_commits.each_with_index do |original_commit, i| - new_commit = collection.commits[i] - expect(original_commit.id).to eq(new_commit.id) - end - end - - it 'fetches data if there are unenriched commits' do - collection = described_class.new(project, [hash_commit]) - - expect(Commit).to receive(:lazy).exactly(:once) - - collection.enrich! - end - - it 'does not fetch data if all commits are enriched' do - collection = described_class.new(project, [gitaly_commit]) - - expect(Commit).not_to receive(:lazy) - - collection.enrich! - end + expect(collection.without_merge_commits.size).to eq(1) end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index fad73613989..42c49e330cc 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -84,27 +84,32 @@ describe MergeRequest do describe '#default_squash_commit_message' do let(:project) { subject.project } - let(:is_multiline) { -> (c) { c.description.present? } } - let(:multiline_commits) { subject.commits.select(&is_multiline) } - let(:singleline_commits) { subject.commits.reject(&is_multiline) } - it 'returns the oldest multiline commit message' do - expect(subject.default_squash_commit_message).to eq(multiline_commits.last.message) + def commit_collection(commit_hashes) + raw_commits = commit_hashes.map { |raw| Commit.from_hash(raw, project) } + + CommitCollection.new(project, raw_commits) end - it 'returns the merge request title if there are no multiline commits' do - expect(subject).to receive(:commits).and_return( - CommitCollection.new(project, singleline_commits) - ) + it 'returns the oldest multiline commit message' do + commits = commit_collection([ + { message: 'Singleline', parent_ids: [] }, + { message: "Second multiline\nCommit message", parent_ids: [] }, + { message: "First multiline\nCommit message", parent_ids: [] } + ]) - expect(subject.default_squash_commit_message).to eq(subject.title) + expect(subject).to receive(:commits).and_return(commits) + + expect(subject.default_squash_commit_message).to eq("First multiline\nCommit message") end - it 'does not return commit messages from multiline merge commits' do - collection = CommitCollection.new(project, multiline_commits).enrich! + it 'returns the merge request title if there are no multiline commits' do + commits = commit_collection([ + { message: 'Singleline', parent_ids: [] } + ]) + + expect(subject).to receive(:commits).and_return(commits) - expect(collection.commits).to all( receive(:merge_commit?).and_return(true) ) - expect(subject).to receive(:commits).and_return(collection) expect(subject.default_squash_commit_message).to eq(subject.title) end end @@ -1040,7 +1045,7 @@ describe MergeRequest do describe '#commit_authors' do it 'returns all the authors of every commit in the merge request' do - users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| + users = subject.commits.map(&:author_email).uniq.map do |email| create(:user, email: email) end @@ -1054,7 +1059,7 @@ describe MergeRequest do describe '#authors' do it 'returns a list with all the commit authors in the merge request and author' do - users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| + users = subject.commits.map(&:author_email).uniq.map do |email| create(:user, email: email) end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 727fd8951f2..4dbd79f2fc0 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -279,18 +279,13 @@ describe MergeRequestWidgetEntity do end describe 'commits_without_merge_commits' do - def find_matching_commit(short_id) - resource.commits.find { |c| c.short_id == short_id } - end - it 'should not include merge commits' do - commits_in_widget = subject[:commits_without_merge_commits] - - expect(commits_in_widget.length).to be < resource.commits.length - expect(commits_in_widget.length).to eq(resource.commits.without_merge_commits.length) - commits_in_widget.each do |c| - expect(find_matching_commit(c[:short_id]).merge_commit?).to eq(false) + # Mock all but the first 5 commits to be merge commits + resource.commits.each_with_index do |commit, i| + expect(commit).to receive(:merge_commit?).at_least(:once).and_return(i > 4) end + + expect(subject[:commits_without_merge_commits].size).to eq(5) end end end -- cgit v1.2.1 From 438485ef88c762b59ee9fb6089d8b7256554fe24 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Thu, 21 Mar 2019 19:11:06 +0800 Subject: Allow users to create protected branches via CLI This is for fixing a regression introduced by: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24969 This fix will allow users who are allowed to push to protected branches to create protected branches via CLI as well, just like before. The checks for protected branch creation won't need to run. --- spec/lib/gitlab/checks/branch_check_spec.rb | 80 ++++++++++++++++++----------- 1 file changed, 51 insertions(+), 29 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb index 12beeecd470..8d5ab27a17c 100644 --- a/spec/lib/gitlab/checks/branch_check_spec.rb +++ b/spec/lib/gitlab/checks/branch_check_spec.rb @@ -108,64 +108,86 @@ describe Gitlab::Checks::BranchCheck do end context 'protected branch creation feature is enabled' do - context 'user is not allowed to create protected branches' do + context 'user can push to branch' do before do allow(user_access) - .to receive(:can_merge_to_branch?) + .to receive(:can_push_to_branch?) .with('feature') - .and_return(false) + .and_return(true) end - it 'raises an error' do - expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.') + it 'does not raise an error' do + expect { subject.validate! }.not_to raise_error end end - context 'user is allowed to create protected branches' do + context 'user cannot push to branch' do before do allow(user_access) - .to receive(:can_merge_to_branch?) + .to receive(:can_push_to_branch?) .with('feature') - .and_return(true) - - allow(project.repository) - .to receive(:branch_names_contains_sha) - .with(newrev) - .and_return(['branch']) + .and_return(false) end - context "newrev isn't in any protected branches" do + context 'user cannot merge to branch' do before do - allow(ProtectedBranch) - .to receive(:any_protected?) - .with(project, ['branch']) + allow(user_access) + .to receive(:can_merge_to_branch?) + .with('feature') .and_return(false) end it 'raises an error' do - expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.') + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.') end end - context 'newrev is included in a protected branch' do + context 'user can merge to branch' do before do - allow(ProtectedBranch) - .to receive(:any_protected?) - .with(project, ['branch']) + allow(user_access) + .to receive(:can_merge_to_branch?) + .with('feature') .and_return(true) + + allow(project.repository) + .to receive(:branch_names_contains_sha) + .with(newrev) + .and_return(['branch']) end - context 'via web interface' do - let(:protocol) { 'web' } + context "newrev isn't in any protected branches" do + before do + allow(ProtectedBranch) + .to receive(:any_protected?) + .with(project, ['branch']) + .and_return(false) + end - it 'allows branch creation' do - expect { subject.validate! }.not_to raise_error + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.') end end - context 'via SSH' do - it 'raises an error' do - expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.') + context 'newrev is included in a protected branch' do + before do + allow(ProtectedBranch) + .to receive(:any_protected?) + .with(project, ['branch']) + .and_return(true) + end + + context 'via web interface' do + let(:protocol) { 'web' } + + it 'allows branch creation' do + expect { subject.validate! }.not_to raise_error + end + end + + context 'via SSH' do + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.') + end end end end -- cgit v1.2.1 From 1a14e5230e5b7f705fc09c3baf46f6cf74ca3ad0 Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Fri, 1 Mar 2019 16:41:16 -0500 Subject: Add merge request popover with details - Show pipeline status, title, MR Status and project path - Popover attached to gitlab flavored markdown everywhere, including: + MR/Issue Title + MR/Issue description + MR/Issue comments + Rendered markdown files --- .../__snapshots__/mr_popover_spec.js.snap | 93 ++++++++++++++++++++++ spec/frontend/mr_popover/mr_popover_spec.js | 61 ++++++++++++++ spec/graphql/types/ci/detailed_status_type_spec.rb | 11 +++ .../filter/merge_request_reference_filter_spec.rb | 25 +++++- .../api/graphql/project/merge_request_spec.rb | 4 +- 5 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap create mode 100644 spec/frontend/mr_popover/mr_popover_spec.js create mode 100644 spec/graphql/types/ci/detailed_status_type_spec.rb (limited to 'spec') diff --git a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap new file mode 100644 index 00000000000..5f9f13d591d --- /dev/null +++ b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MR Popover loaded state matches the snapshot 1`] = ` + +
    +
    +
    +
    + + Open + +
    + + + Opened + + +
    + + +
    + +
    + MR Title +
    + +
    + + foo/bar!1 + +
    +
    +
    +`; + +exports[`MR Popover shows skeleton-loader while apollo is loading 1`] = ` + +
    +
    + +
    + +
    + MR Title +
    + +
    + + foo/bar!1 + +
    +
    +
    +`; diff --git a/spec/frontend/mr_popover/mr_popover_spec.js b/spec/frontend/mr_popover/mr_popover_spec.js new file mode 100644 index 00000000000..79ed4163010 --- /dev/null +++ b/spec/frontend/mr_popover/mr_popover_spec.js @@ -0,0 +1,61 @@ +import MRPopover from '~/mr_popover/components/mr_popover'; +import { shallowMount } from '@vue/test-utils'; + +describe('MR Popover', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallowMount(MRPopover, { + propsData: { + target: document.createElement('a'), + projectPath: 'foo/bar', + mergeRequestIID: '1', + mergeRequestTitle: 'MR Title', + }, + mocks: { + $apollo: { + loading: false, + }, + }, + }); + }); + + it('shows skeleton-loader while apollo is loading', () => { + wrapper.vm.$apollo.loading = true; + + expect(wrapper.element).toMatchSnapshot(); + }); + + describe('loaded state', () => { + it('matches the snapshot', () => { + wrapper.setData({ + mergeRequest: { + state: 'opened', + createdAt: new Date(), + headPipeline: { + detailedStatus: { + group: 'success', + status: 'status_success', + }, + }, + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('does not show CI Icon if there is no pipeline data', () => { + wrapper.setData({ + mergeRequest: { + state: 'opened', + headPipeline: null, + stateHumanName: 'Open', + title: 'Merge Request Title', + createdAt: new Date(), + }, + }); + + expect(wrapper.contains('ciicon-stub')).toBe(false); + }); + }); +}); diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb new file mode 100644 index 00000000000..a21162adb42 --- /dev/null +++ b/spec/graphql/types/ci/detailed_status_type_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Types::Ci::DetailedStatusType do + it { expect(described_class.graphql_name).to eq('DetailedStatus') } + + it "has all fields" do + expect(described_class).to have_graphql_fields(:group, :icon, :favicon, + :details_path, :has_details, + :label, :text, :tooltip) + end +end diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 55c41e55437..72dfd6ff9ea 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -30,6 +30,23 @@ describe Banzai::Filter::MergeRequestReferenceFilter do end end + describe 'all references' do + let(:doc) { reference_filter(merge.to_reference) } + let(:tag_el) { doc.css('a').first } + + it 'adds merge request iid' do + expect(tag_el["data-iid"]).to eq(merge.iid.to_s) + end + + it 'adds project data attribute with project id' do + expect(tag_el["data-project-path"]).to eq(project.full_path) + end + + it 'does not add `has-tooltip` class' do + expect(tag_el["class"]).not_to include('has-tooltip') + end + end + context 'internal reference' do let(:reference) { merge.to_reference } @@ -57,9 +74,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do expect(reference_filter(act).to_html).to eq exp end - it 'includes a title attribute' do + it 'has no title' do doc = reference_filter("Merge #{reference}") - expect(doc.css('a').first.attr('title')).to eq merge.title + expect(doc.css('a').first.attr('title')).to eq "" end it 'escapes the title attribute' do @@ -69,9 +86,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do expect(doc.text).to eq "Merge #{reference}" end - it 'includes default classes' do + it 'includes default classes, without tooltip' do doc = reference_filter("Merge #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' end it 'includes a data-project attribute' do diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index deb6abbc026..74820d39102 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -70,13 +70,13 @@ describe 'getting merge request information nested in a project' do context 'when there are pipelines' do before do - pipeline = create( + create( :ci_pipeline, project: merge_request.source_project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha ) - merge_request.update!(head_pipeline: pipeline) + merge_request.update_head_pipeline end it 'has a head pipeline' do -- cgit v1.2.1 From 8723f292552814cfe4566f213005e4e190fca456 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Thu, 21 Mar 2019 21:38:52 +0800 Subject: Fix diff bottom expand button appears twice This is a quick fix by only append match line once when calling diff_lines_for_serializer multiple time. Also enable feature by default --- spec/lib/gitlab/diff/file_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 611c3e946ed..cc36060f864 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -72,6 +72,13 @@ describe Gitlab::Diff::File do expect(diff_file.diff_lines_for_serializer.last.type).to eq('match') end + context 'when called multiple times' do + it 'only adds bottom match line once' do + expect(diff_file.diff_lines_for_serializer.size).to eq(31) + expect(diff_file.diff_lines_for_serializer.size).to eq(31) + end + end + context 'when deleted' do let(:commit) { project.commit('d59c60028b053793cecfb4022de34602e1a9218e') } let(:diff_file) { commit.diffs.diff_file_with_old_path('files/js/commit.js.coffee') } -- cgit v1.2.1 From ca13500a71c4f80d0c837c4e07df511988fc3907 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Mon, 18 Mar 2019 10:26:50 -0400 Subject: Hide/show ingress external ip help text Use not_to instead of to__not --- spec/features/clusters/cluster_detail_page_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index 0a9c4bcaf12..b9fc52d0dce 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -4,6 +4,8 @@ require 'spec_helper' describe 'Clusterable > Show page' do let(:current_user) { create(:user) } + let(:cluster_ingress_help_text_selector) { '.js-ingress-domain-help-text' } + let(:hide_modifier_selector) { '.hide' } before do sign_in(current_user) @@ -35,7 +37,7 @@ describe 'Clusterable > Show page' do it 'shows help text with the domain as an alternative to custom domain' do within '#cluster-integration' do - expect(page).to have_content('Alternatively 192.168.1.100.nip.io can be used instead of a custom domain') + expect(find(cluster_ingress_help_text_selector)).not_to match_css(hide_modifier_selector) end end end @@ -45,7 +47,7 @@ describe 'Clusterable > Show page' do visit cluster_path within '#cluster-integration' do - expect(page).not_to have_content('can be used instead of a custom domain.') + expect(find(cluster_ingress_help_text_selector)).to match_css(hide_modifier_selector) end end end -- cgit v1.2.1 From 43ca72689a6b826f4949a72303dda2a274ea33f2 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Mon, 18 Mar 2019 15:53:51 -0400 Subject: Update domain help text when ingress is installed When ingress application is installed, the cluster domain help text is updated to indicate that ingress external ip can be used as the cluster domain --- spec/javascripts/clusters/clusters_bundle_spec.js | 80 ++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 7928feeadfa..71f16dc259e 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -1,5 +1,10 @@ import Clusters from '~/clusters/clusters_bundle'; -import { REQUEST_SUBMITTED, REQUEST_FAILURE, APPLICATION_STATUS } from '~/clusters/constants'; +import { + REQUEST_SUBMITTED, + REQUEST_FAILURE, + APPLICATION_STATUS, + INGRESS_DOMAIN_SUFFIX, +} from '~/clusters/constants'; import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; describe('Clusters', () => { @@ -265,4 +270,77 @@ describe('Clusters', () => { .catch(done.fail); }); }); + + describe('handleSuccess', () => { + beforeEach(() => { + spyOn(cluster.store, 'updateStateFromServer'); + spyOn(cluster, 'toggleIngressDomainHelpText'); + spyOn(cluster, 'checkForNewInstalls'); + spyOn(cluster, 'updateContainer'); + + cluster.handleSuccess({ data: {} }); + }); + + it('updates clusters store', () => { + expect(cluster.store.updateStateFromServer).toHaveBeenCalled(); + }); + + it('checks for new installable apps', () => { + expect(cluster.checkForNewInstalls).toHaveBeenCalled(); + }); + + it('toggles ingress domain help text', () => { + expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled(); + }); + + it('updates message containers', () => { + expect(cluster.updateContainer).toHaveBeenCalled(); + }); + }); + + describe('toggleIngressDomainHelpText', () => { + const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS; + + const ingressPreviousState = { status: INSTALLABLE }; + const ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + + describe(`when ingress application new status is ${INSTALLED}`, () => { + beforeEach(() => { + ingressNewState.status = INSTALLED; + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + }); + + it('displays custom domain help text', () => { + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false); + }); + + it('updates ingress external ip address', () => { + expect(cluster.ingressDomainSnippet.textContent).toEqual( + `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`, + ); + }); + }); + + describe(`when ingress application new status is different from ${INSTALLED}`, () => { + it('hides custom domain help text', () => { + ingressNewState.status = NOT_INSTALLABLE; + cluster.ingressDomainHelpText.classList.remove('hide'); + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + + describe('when ingress application new status and old status are the same', () => { + it('does not modify custom domain help text', () => { + ingressPreviousState.status = INSTALLED; + ingressNewState.status = ingressPreviousState.status; + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + }); }); -- cgit v1.2.1 From 7e6b57499f838245136e0657069f7559b1eba02f Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Thu, 21 Mar 2019 15:31:09 +0000 Subject: Update merge request widget pipeline block This commit updates the pipeline block that appears on the merge request page to include information that will be exposed by the API as part of the post-merge pipeline feature. --- .../merge_request/user_sees_merge_widget_spec.rb | 113 +++++++++++++++++++++ .../components/mr_widget_pipeline_spec.js | 83 ++++++++++++++- spec/javascripts/vue_mr_widget/mock_data.js | 11 ++ spec/models/ci/pipeline_spec.rb | 24 +++++ spec/models/environment_status_spec.rb | 4 +- spec/models/merge_request_spec.rb | 14 ++- 6 files changed, 242 insertions(+), 7 deletions(-) (limited to 'spec') 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 afb978d7c45..2609546990d 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -145,6 +145,119 @@ describe 'Merge request > User sees merge widget', :js do end end + context 'when merge request has a branch pipeline as the head pipeline' do + let!(:pipeline) do + create(:ci_pipeline, + ref: merge_request.source_branch, + sha: merge_request.source_branch_sha, + project: merge_request.source_project) + end + + before do + merge_request.update_head_pipeline + visit project_merge_request_path(project, merge_request) + end + + it 'shows head pipeline information' do + within '.ci-widget-content' do + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + "for #{pipeline.short_sha} " \ + "on #{pipeline.ref}") + end + end + end + + context 'when merge request has a detached merge request pipeline as the head pipeline' do + let(:merge_request) do + create(:merge_request, + :with_detached_merge_request_pipeline, + source_project: source_project, + target_project: target_project) + end + + let!(:pipeline) do + merge_request.all_pipelines.last + end + + let(:source_project) { project } + let(:target_project) { project } + + before do + merge_request.update_head_pipeline + visit project_merge_request_path(project, merge_request) + end + + it 'shows head pipeline information' do + within '.ci-widget-content' do + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + "for #{pipeline.short_sha} " \ + "on #{merge_request.to_reference} " \ + "with #{merge_request.source_branch}") + end + end + + context 'when source project is a forked project' do + let(:source_project) { fork_project(project, user, repository: true) } + + it 'shows head pipeline information' do + within '.ci-widget-content' do + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + "for #{pipeline.short_sha} " \ + "on #{merge_request.to_reference} " \ + "with #{merge_request.source_branch}") + end + end + end + end + + context 'when merge request has a merge request pipeline as the head pipeline' do + let(:merge_request) do + create(:merge_request, + :with_merge_request_pipeline, + source_project: source_project, + target_project: target_project, + merge_sha: merge_sha) + end + + let!(:pipeline) do + merge_request.all_pipelines.last + end + + let(:source_project) { project } + let(:target_project) { project } + let(:merge_sha) { project.commit.sha } + + before do + merge_request.update_head_pipeline + visit project_merge_request_path(project, merge_request) + end + + it 'shows head pipeline information' do + within '.ci-widget-content' do + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + "for #{pipeline.short_sha} " \ + "on #{merge_request.to_reference} " \ + "with #{merge_request.source_branch} " \ + "into #{merge_request.target_branch}") + end + end + + context 'when source project is a forked project' do + let(:source_project) { fork_project(project, user, repository: true) } + let(:merge_sha) { source_project.commit.sha } + + it 'shows head pipeline information' do + within '.ci-widget-content' do + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + "for #{pipeline.short_sha} " \ + "on #{merge_request.to_reference} " \ + "with #{merge_request.source_branch} " \ + "into #{merge_request.target_branch}") + end + end + end + end + context 'view merge request with MWBS button' do before do commit_status = create(:commit_status, project: project, status: 'pending') 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 d905bbe4040..de213210cfc 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 @@ -1,6 +1,7 @@ import Vue from 'vue'; import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { trimText } from 'spec/helpers/vue_component_helper'; import mockData from '../mock_data'; describe('MRWidgetPipeline', () => { @@ -123,7 +124,7 @@ describe('MRWidgetPipeline', () => { describe('without commit path', () => { beforeEach(() => { - const mockCopy = Object.assign({}, mockData); + const mockCopy = JSON.parse(JSON.stringify(mockData)); delete mockCopy.pipeline.commit; vm = mountComponent(Component, { @@ -164,7 +165,7 @@ describe('MRWidgetPipeline', () => { describe('without coverage', () => { it('should not render a coverage', () => { - const mockCopy = Object.assign({}, mockData); + const mockCopy = JSON.parse(JSON.stringify(mockData)); delete mockCopy.pipeline.coverage; vm = mountComponent(Component, { @@ -180,7 +181,7 @@ describe('MRWidgetPipeline', () => { describe('without a pipeline graph', () => { it('should not render a pipeline graph', () => { - const mockCopy = Object.assign({}, mockData); + const mockCopy = JSON.parse(JSON.stringify(mockData)); delete mockCopy.pipeline.details.stages; vm = mountComponent(Component, { @@ -193,5 +194,81 @@ describe('MRWidgetPipeline', () => { expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null); }); }); + + describe('without pipeline.merge_request', () => { + it('should render info that includes the commit and branch details', () => { + const mockCopy = JSON.parse(JSON.stringify(mockData)); + delete mockCopy.pipeline.merge_request; + const { pipeline } = mockCopy; + + vm = mountComponent(Component, { + pipeline, + hasCi: true, + ciStatus: 'success', + troubleshootingDocsPath: 'help', + sourceBranchLink: mockCopy.source_branch_link, + }); + + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on ${mockCopy.source_branch_link}`; + + const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); + + expect(actual).toBe(expected); + }); + }); + + describe('with pipeline.merge_request and flags.merge_request_pipeline', () => { + it('should render info that includes the commit, MR, source branch, and target branch details', () => { + const mockCopy = JSON.parse(JSON.stringify(mockData)); + const { pipeline } = mockCopy; + pipeline.flags.merge_request_pipeline = true; + pipeline.flags.detached_merge_request_pipeline = false; + + vm = mountComponent(Component, { + pipeline, + hasCi: true, + ciStatus: 'success', + troubleshootingDocsPath: 'help', + sourceBranchLink: mockCopy.source_branch_link, + }); + + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${ + pipeline.merge_request.target_branch + }`; + + const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); + + expect(actual).toBe(expected); + }); + }); + + describe('with pipeline.merge_request and flags.detached_merge_request_pipeline', () => { + it('should render info that includes the commit, MR, and source branch details', () => { + const mockCopy = JSON.parse(JSON.stringify(mockData)); + const { pipeline } = mockCopy; + pipeline.flags.merge_request_pipeline = false; + pipeline.flags.detached_merge_request_pipeline = true; + + vm = mountComponent(Component, { + pipeline, + hasCi: true, + ciStatus: 'success', + troubleshootingDocsPath: 'help', + sourceBranchLink: mockCopy.source_branch_link, + }); + + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`; + + const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); + + expect(actual).toBe(expected); + }); + }); }); }); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 6ef07f81705..7ab203a6011 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -134,6 +134,8 @@ export default { yaml_errors: false, retryable: true, cancelable: false, + merge_request_pipeline: false, + detached_merge_request_pipeline: true, }, ref: { name: 'daaaa', @@ -141,6 +143,15 @@ export default { tag: false, branch: true, }, + merge_request: { + iid: 1, + path: '/root/detached-merge-request-pipelines/merge_requests/1', + title: 'Update README.md', + source_branch: 'feature-1', + source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1', + target_branch: 'master', + target_branch_path: '/root/detached-merge-request-pipelines/branches/master', + }, commit: { id: '104096c51715e12e7ae41f9333e9fa35b73f385d', short_id: '104096c5', diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2ac056f63b2..5b8097621e0 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -362,6 +362,30 @@ describe Ci::Pipeline, :mailer do end end + describe '#matches_sha_or_source_sha?' do + subject { pipeline.matches_sha_or_source_sha?(sample_sha) } + + let(:sample_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) } + + context 'when sha matches' do + let(:pipeline) { build(:ci_pipeline, sha: sample_sha) } + + it { is_expected.to be_truthy } + end + + context 'when source_sha matches' do + let(:pipeline) { build(:ci_pipeline, source_sha: sample_sha) } + + it { is_expected.to be_truthy } + end + + context 'when both sha and source_sha do not matche' do + let(:pipeline) { build(:ci_pipeline, sha: 'test', source_sha: 'test') } + + it { is_expected.to be_falsy } + end + end + describe '.triggered_for_branch' do subject { described_class.triggered_for_branch(ref) } diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index 9da16dea929..2576a9aba06 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -64,8 +64,8 @@ describe EnvironmentStatus do end describe '.for_merge_request' do - let(:admin) { create(:admin) } - let(:pipeline) { create(:ci_pipeline, sha: sha) } + let(:admin) { create(:admin) } + let!(:pipeline) { create(:ci_pipeline, sha: sha, merge_requests_as_head_pipeline: [merge_request]) } it 'is based on merge_request.diff_head_sha' do expect(merge_request).to receive(:diff_head_sha) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 42c49e330cc..22998bc5b6a 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1187,8 +1187,10 @@ describe MergeRequest do end context 'head pipeline' do + let(:diff_head_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) } + before do - allow(subject).to receive(:diff_head_sha).and_return('lastsha') + allow(subject).to receive(:diff_head_sha).and_return(diff_head_sha) end describe '#head_pipeline' do @@ -1216,7 +1218,15 @@ describe MergeRequest do end it 'returns the pipeline for MR with recent pipeline' do - pipeline = create(:ci_empty_pipeline, sha: 'lastsha') + pipeline = create(:ci_empty_pipeline, sha: diff_head_sha) + subject.update_attribute(:head_pipeline_id, pipeline.id) + + expect(subject.actual_head_pipeline).to eq(subject.head_pipeline) + expect(subject.actual_head_pipeline).to eq(pipeline) + end + + it 'returns the pipeline for MR with recent merge request pipeline' do + pipeline = create(:ci_empty_pipeline, sha: 'merge-sha', source_sha: diff_head_sha) subject.update_attribute(:head_pipeline_id, pipeline.id) expect(subject.actual_head_pipeline).to eq(subject.head_pipeline) -- cgit v1.2.1 From f5ce678dfeb3949968e6d1222d873a65fa72c0c1 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Feb 2019 15:02:58 +0100 Subject: Use autosave utilities in NoteForm component --- .../javascripts/notes/components/note_form_spec.js | 94 ++++++++++++++++++++-- 1 file changed, 86 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js index 7cc324cfe44..c48f8188105 100644 --- a/spec/javascripts/notes/components/note_form_spec.js +++ b/spec/javascripts/notes/components/note_form_spec.js @@ -5,11 +5,33 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import { noteableDataMock, notesDataMock } from '../mock_data'; describe('issue_note_form component', () => { + const dummyAutosaveKey = 'some-autosave-key'; + const dummyDraft = 'dummy draft content'; + let store; let wrapper; let props; + const createComponentWrapper = () => { + const localVue = createLocalVue(); + return shallowMount(NoteForm, { + store, + propsData: props, + // see https://gitlab.com/gitlab-org/gitlab-ce/issues/56317 for the following + localVue, + sync: false, + }); + }; + beforeEach(() => { + spyOnDependency(NoteForm, 'getDraft').and.callFake(key => { + if (key === dummyAutosaveKey) { + return dummyDraft; + } + + return null; + }); + store = createStore(); store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); @@ -20,14 +42,7 @@ describe('issue_note_form component', () => { noteId: '545', }; - const localVue = createLocalVue(); - wrapper = shallowMount(NoteForm, { - store, - propsData: props, - // see https://gitlab.com/gitlab-org/gitlab-ce/issues/56317 for the following - localVue, - sync: false, - }); + wrapper = createComponentWrapper(); }); afterEach(() => { @@ -181,4 +196,67 @@ describe('issue_note_form component', () => { }); }); }); + + describe('with autosaveKey', () => { + beforeEach(() => { + wrapper.destroy(); + }); + + describe('with draft', () => { + beforeEach(done => { + Object.assign(props, { + noteBody: '', + autosaveKey: dummyAutosaveKey, + }); + wrapper = createComponentWrapper(); + + wrapper.vm + .$nextTick() + .then(done) + .catch(done.fail); + }); + + it('displays the draft in textarea', () => { + const textarea = wrapper.find('textarea'); + + expect(textarea.element.value).toBe(dummyDraft); + }); + }); + + describe('without draft', () => { + beforeEach(done => { + Object.assign(props, { + noteBody: '', + autosaveKey: 'some key without draft', + }); + wrapper = createComponentWrapper(); + + wrapper.vm + .$nextTick() + .then(done) + .catch(done.fail); + }); + + it('leaves the textarea empty', () => { + const textarea = wrapper.find('textarea'); + + expect(textarea.element.value).toBe(''); + }); + }); + + it('updates the draft if textarea content changes', () => { + const updateDraftSpy = spyOnDependency(NoteForm, 'updateDraft').and.stub(); + Object.assign(props, { + noteBody: '', + autosaveKey: dummyAutosaveKey, + }); + wrapper = createComponentWrapper(); + const textarea = wrapper.find('textarea'); + const dummyContent = 'some new content'; + + textarea.setValue(dummyContent); + + expect(updateDraftSpy).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent); + }); + }); }); -- cgit v1.2.1 From 93c27c3b22fe597a2321a19911e84fd0abd93a0b Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Feb 2019 19:03:00 +0100 Subject: Validate NoteForm props in noteable_discussion_spec.js --- .../notes/components/noteable_discussion_spec.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index 2b93fb9fb45..bd94dfb9285 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -3,6 +3,7 @@ import createStore from '~/notes/stores'; import noteableDiscussion from '~/notes/components/noteable_discussion.vue'; import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue'; +import NoteForm from '~/notes/components/note_form.vue'; import '~/behaviors/markdown/render_gfm'; import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; import mockDiffFile from '../../diffs/mock_data/diff_file'; @@ -72,7 +73,17 @@ describe('noteable_discussion component', () => { .then(() => wrapper.vm.$nextTick()) .then(() => { expect(wrapper.vm.isReplying).toEqual(true); - expect(wrapper.vm.$refs.noteForm).not.toBeNull(); + + const noteForm = wrapper.find(NoteForm); + + expect(noteForm.exists()).toBe(true); + + const noteFormProps = noteForm.props(); + + expect(noteFormProps.discussion).toBe(discussionMock); + expect(noteFormProps.isEditing).toBe(false); + expect(noteFormProps.line).toBe(null); + expect(noteFormProps.saveButtonTitle).toBe('Comment'); }) .then(done) .catch(done.fail); -- cgit v1.2.1 From 53e23a296d8cae21485698750b20244b2e075608 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 4 Mar 2019 22:00:18 +0100 Subject: Pass autosaveKey from NoteableDiscussion to NoteForm --- spec/javascripts/notes/components/noteable_discussion_spec.js | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index bd94dfb9285..3304c79cdb7 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -84,6 +84,7 @@ describe('noteable_discussion component', () => { expect(noteFormProps.isEditing).toBe(false); expect(noteFormProps.line).toBe(null); expect(noteFormProps.saveButtonTitle).toBe('Comment'); + expect(noteFormProps.autosaveKey).toBe(`Note/Issue/${discussionMock.id}/Reply`); }) .then(done) .catch(done.fail); -- cgit v1.2.1 From 3e9bb4ad314955c6d33db5d2f7dc8bab1df4e85f Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 21 Mar 2019 07:44:05 +0100 Subject: Remove .raw from JavaScript fixture file names There is nothing more to say but "Commits that change 30 or more lines across at least three files must describe these changes in the commit body" --- spec/javascripts/activities_spec.js | 2 +- spec/javascripts/ajax_loading_spinner_spec.js | 2 +- spec/javascripts/awards_handler_spec.js | 4 +- spec/javascripts/behaviors/quick_submit_spec.js | 4 +- spec/javascripts/behaviors/requires_input_spec.js | 4 +- .../behaviors/shortcuts/shortcuts_issuable_spec.js | 2 +- .../balsamiq/balsamiq_viewer_integration_spec.js | 4 +- spec/javascripts/blob/blob_file_dropzone_spec.js | 4 +- spec/javascripts/blob/notebook/index_spec.js | 4 +- spec/javascripts/blob/pdf/index_spec.js | 4 +- spec/javascripts/blob/sketch/index_spec.js | 4 +- spec/javascripts/blob/viewer/index_spec.js | 4 +- spec/javascripts/boards/components/board_spec.js | 2 +- spec/javascripts/bootstrap_linked_tabs_spec.js | 4 +- .../ci_variable_list/ajax_variable_list_spec.js | 8 +- .../ci_variable_list/ci_variable_list_spec.js | 16 +-- .../native_form_variable_list_spec.js | 4 +- spec/javascripts/clusters/clusters_bundle_spec.js | 4 +- spec/javascripts/collapsed_sidebar_todo_spec.js | 2 +- spec/javascripts/create_item_dropdown_spec.js | 4 +- .../filtered_search/dropdown_user_spec.js | 2 +- .../filtered_search/dropdown_utils_spec.js | 2 +- spec/javascripts/fixtures/.gitignore | 1 + spec/javascripts/fixtures/abuse_reports.rb | 2 +- spec/javascripts/fixtures/admin_users.rb | 2 +- spec/javascripts/fixtures/application_settings.rb | 2 +- spec/javascripts/fixtures/blob.rb | 2 +- spec/javascripts/fixtures/boards.rb | 2 +- spec/javascripts/fixtures/branches.rb | 2 +- spec/javascripts/fixtures/clusters.rb | 2 +- spec/javascripts/fixtures/commit.rb | 2 +- spec/javascripts/fixtures/groups.rb | 4 +- spec/javascripts/fixtures/issues.rb | 10 +- spec/javascripts/fixtures/jobs.rb | 2 +- spec/javascripts/fixtures/merge_requests.rb | 10 +- spec/javascripts/fixtures/pipeline_schedules.rb | 4 +- spec/javascripts/fixtures/projects.rb | 10 +- spec/javascripts/fixtures/prometheus_service.rb | 2 +- spec/javascripts/fixtures/search.rb | 2 +- spec/javascripts/fixtures/services.rb | 2 +- spec/javascripts/fixtures/sessions.rb | 2 +- spec/javascripts/fixtures/snippet.rb | 2 +- .../fixtures/static/ajax_loading_spinner.html | 3 + .../fixtures/static/ajax_loading_spinner.html.raw | 3 - .../fixtures/static/balsamiq_viewer.html | 1 + .../fixtures/static/balsamiq_viewer.html.raw | 1 - .../fixtures/static/create_item_dropdown.html | 11 +++ .../fixtures/static/create_item_dropdown.html.raw | 11 --- spec/javascripts/fixtures/static/event_filter.html | 44 +++++++++ .../fixtures/static/event_filter.html.raw | 44 --------- spec/javascripts/fixtures/static/gl_dropdown.html | 26 +++++ .../fixtures/static/gl_dropdown.html.raw | 26 ----- .../fixtures/static/gl_field_errors.html | 22 +++++ .../fixtures/static/gl_field_errors.html.raw | 22 ----- .../fixtures/static/issuable_filter.html | 9 ++ .../fixtures/static/issuable_filter.html.raw | 9 -- .../fixtures/static/issue_sidebar_label.html | 26 +++++ .../fixtures/static/issue_sidebar_label.html.raw | 26 ----- .../fixtures/static/line_highlighter.html | 107 +++++++++++++++++++++ .../fixtures/static/line_highlighter.html.raw | 107 --------------------- spec/javascripts/fixtures/static/linked_tabs.html | 20 ++++ .../fixtures/static/linked_tabs.html.raw | 20 ---- .../fixtures/static/merge_requests_show.html | 15 +++ .../fixtures/static/merge_requests_show.html.raw | 15 --- .../fixtures/static/mini_dropdown_graph.html | 13 +++ .../fixtures/static/mini_dropdown_graph.html.raw | 13 --- .../fixtures/static/notebook_viewer.html | 1 + .../fixtures/static/notebook_viewer.html.raw | 1 - .../fixtures/static/oauth_remember_me.html | 6 ++ .../fixtures/static/oauth_remember_me.html.raw | 6 -- spec/javascripts/fixtures/static/pdf_viewer.html | 1 + .../fixtures/static/pdf_viewer.html.raw | 1 - .../fixtures/static/pipeline_graph.html | 24 +++++ .../fixtures/static/pipeline_graph.html.raw | 24 ----- spec/javascripts/fixtures/static/pipelines.html | 3 + .../javascripts/fixtures/static/pipelines.html.raw | 3 - .../static/project_select_combo_button.html | 9 ++ .../static/project_select_combo_button.html.raw | 9 -- .../fixtures/static/search_autocomplete.html | 15 +++ .../fixtures/static/search_autocomplete.html.raw | 15 --- spec/javascripts/fixtures/static/signin_tabs.html | 8 ++ .../fixtures/static/signin_tabs.html.raw | 8 -- .../javascripts/fixtures/static/sketch_viewer.html | 3 + .../fixtures/static/sketch_viewer.html.raw | 3 - spec/javascripts/fixtures/todos.rb | 2 +- spec/javascripts/fixtures/u2f.rb | 4 +- spec/javascripts/gl_dropdown_spec.js | 4 +- spec/javascripts/gl_field_errors_spec.js | 4 +- spec/javascripts/header_spec.js | 2 +- .../integrations/integration_settings_form_spec.js | 2 +- spec/javascripts/issue_spec.js | 10 +- spec/javascripts/labels_issue_sidebar_spec.js | 4 +- spec/javascripts/lazy_loader_spec.js | 6 +- spec/javascripts/line_highlighter_spec.js | 4 +- spec/javascripts/merge_request_spec.js | 8 +- spec/javascripts/merge_request_tabs_spec.js | 6 +- .../mini_pipeline_graph_dropdown_spec.js | 4 +- spec/javascripts/new_branch_spec.js | 4 +- spec/javascripts/notes_spec.js | 2 +- spec/javascripts/oauth_remember_me_spec.js | 4 +- .../admin/abuse_reports/abuse_reports_spec.js | 2 +- .../account_and_limits_spec.js | 2 +- .../pages/admin/users/new/index_spec.js | 2 +- .../sessions/new/preserve_url_fragment_spec.js | 4 +- spec/javascripts/pipelines_spec.js | 4 +- .../project_select_combo_button_spec.js | 2 +- .../prometheus_metrics/prometheus_metrics_spec.js | 2 +- spec/javascripts/read_more_spec.js | 2 +- spec/javascripts/right_sidebar_spec.js | 2 +- spec/javascripts/search_autocomplete_spec.js | 4 +- spec/javascripts/search_spec.js | 2 +- spec/javascripts/settings_panels_spec.js | 4 +- spec/javascripts/shortcuts_spec.js | 2 +- spec/javascripts/sidebar/sidebar_assignees_spec.js | 4 +- spec/javascripts/signin_tabs_memoizer_spec.js | 2 +- spec/javascripts/todos_spec.js | 4 +- spec/javascripts/u2f/authenticate_spec.js | 4 +- spec/javascripts/u2f/register_spec.js | 4 +- spec/javascripts/user_popovers_spec.js | 2 +- .../states/mr_widget_ready_to_merge_spec.js | 2 +- .../components/user_popover/user_popover_spec.js | 2 +- spec/javascripts/zen_mode_spec.js | 2 +- .../support/helpers/javascript_fixtures_helpers.rb | 2 +- 123 files changed, 512 insertions(+), 511 deletions(-) create mode 100644 spec/javascripts/fixtures/static/ajax_loading_spinner.html delete mode 100644 spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw create mode 100644 spec/javascripts/fixtures/static/balsamiq_viewer.html delete mode 100644 spec/javascripts/fixtures/static/balsamiq_viewer.html.raw create mode 100644 spec/javascripts/fixtures/static/create_item_dropdown.html delete mode 100644 spec/javascripts/fixtures/static/create_item_dropdown.html.raw create mode 100644 spec/javascripts/fixtures/static/event_filter.html delete mode 100644 spec/javascripts/fixtures/static/event_filter.html.raw create mode 100644 spec/javascripts/fixtures/static/gl_dropdown.html delete mode 100644 spec/javascripts/fixtures/static/gl_dropdown.html.raw create mode 100644 spec/javascripts/fixtures/static/gl_field_errors.html delete mode 100644 spec/javascripts/fixtures/static/gl_field_errors.html.raw create mode 100644 spec/javascripts/fixtures/static/issuable_filter.html delete mode 100644 spec/javascripts/fixtures/static/issuable_filter.html.raw create mode 100644 spec/javascripts/fixtures/static/issue_sidebar_label.html delete mode 100644 spec/javascripts/fixtures/static/issue_sidebar_label.html.raw create mode 100644 spec/javascripts/fixtures/static/line_highlighter.html delete mode 100644 spec/javascripts/fixtures/static/line_highlighter.html.raw create mode 100644 spec/javascripts/fixtures/static/linked_tabs.html delete mode 100644 spec/javascripts/fixtures/static/linked_tabs.html.raw create mode 100644 spec/javascripts/fixtures/static/merge_requests_show.html delete mode 100644 spec/javascripts/fixtures/static/merge_requests_show.html.raw create mode 100644 spec/javascripts/fixtures/static/mini_dropdown_graph.html delete mode 100644 spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw create mode 100644 spec/javascripts/fixtures/static/notebook_viewer.html delete mode 100644 spec/javascripts/fixtures/static/notebook_viewer.html.raw create mode 100644 spec/javascripts/fixtures/static/oauth_remember_me.html delete mode 100644 spec/javascripts/fixtures/static/oauth_remember_me.html.raw create mode 100644 spec/javascripts/fixtures/static/pdf_viewer.html delete mode 100644 spec/javascripts/fixtures/static/pdf_viewer.html.raw create mode 100644 spec/javascripts/fixtures/static/pipeline_graph.html delete mode 100644 spec/javascripts/fixtures/static/pipeline_graph.html.raw create mode 100644 spec/javascripts/fixtures/static/pipelines.html delete mode 100644 spec/javascripts/fixtures/static/pipelines.html.raw create mode 100644 spec/javascripts/fixtures/static/project_select_combo_button.html delete mode 100644 spec/javascripts/fixtures/static/project_select_combo_button.html.raw create mode 100644 spec/javascripts/fixtures/static/search_autocomplete.html delete mode 100644 spec/javascripts/fixtures/static/search_autocomplete.html.raw create mode 100644 spec/javascripts/fixtures/static/signin_tabs.html delete mode 100644 spec/javascripts/fixtures/static/signin_tabs.html.raw create mode 100644 spec/javascripts/fixtures/static/sketch_viewer.html delete mode 100644 spec/javascripts/fixtures/static/sketch_viewer.html.raw (limited to 'spec') diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js index 068b8eb65bc..23b6de7e4e0 100644 --- a/spec/javascripts/activities_spec.js +++ b/spec/javascripts/activities_spec.js @@ -7,7 +7,7 @@ import Pager from '~/pager'; describe('Activities', () => { window.gon || (window.gon = {}); - const fixtureTemplate = 'static/event_filter.html.raw'; + const fixtureTemplate = 'static/event_filter.html'; const filters = [ { id: 'all', diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js index 9389fc94f17..89195a4397f 100644 --- a/spec/javascripts/ajax_loading_spinner_spec.js +++ b/spec/javascripts/ajax_loading_spinner_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import AjaxLoadingSpinner from '~/ajax_loading_spinner'; describe('Ajax Loading Spinner', () => { - const fixtureTemplate = 'static/ajax_loading_spinner.html.raw'; + const fixtureTemplate = 'static/ajax_loading_spinner.html'; preloadFixtures(fixtureTemplate); beforeEach(() => { diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index e5b5707dcef..e10df1b45e7 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -24,13 +24,13 @@ const lazyAssert = function(done, assertFn) { describe('AwardsHandler', function() { const emojiData = getJSONFixture('emojis/emojis.json'); - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(function(done) { mock = new MockAdapter(axios); mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData); - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); loadAwardsHandler(true) .then(obj => { awardsHandler = obj; diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index 681463aab66..7af8c984841 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -4,10 +4,10 @@ import '~/behaviors/quick_submit'; describe('Quick Submit behavior', function() { const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options); - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(() => { - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); $('form').submit(e => { // Prevent a form submit from moving us off the testing page e.preventDefault(); diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js index 1bde2bb3024..617fe49b059 100644 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ b/spec/javascripts/behaviors/requires_input_spec.js @@ -3,10 +3,10 @@ import '~/behaviors/requires_input'; describe('requiresInput', () => { let submitButton; - preloadFixtures('branches/new_branch.html.raw'); + preloadFixtures('branches/new_branch.html'); beforeEach(() => { - loadFixtures('branches/new_branch.html.raw'); + loadFixtures('branches/new_branch.html'); submitButton = $('button[type="submit"]'); }); diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js index 4843a0386b5..5e457a4e823 100644 --- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js +++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js @@ -9,7 +9,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form'; describe('ShortcutsIssuable', function() { - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; preloadFixtures(fixtureName); beforeAll(done => { diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js index 5f027f59fcf..68b4f261617 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js @@ -6,10 +6,10 @@ describe('Balsamiq integration spec', () => { let endpoint; let balsamiqViewer; - preloadFixtures('static/balsamiq_viewer.html.raw'); + preloadFixtures('static/balsamiq_viewer.html'); beforeEach(() => { - loadFixtures('static/balsamiq_viewer.html.raw'); + loadFixtures('static/balsamiq_viewer.html'); container = document.getElementById('js-balsamiq-viewer'); balsamiqViewer = new BalsamiqViewer(container); diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js index 432d8a65b0a..cab06a0a9be 100644 --- a/spec/javascripts/blob/blob_file_dropzone_spec.js +++ b/spec/javascripts/blob/blob_file_dropzone_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import BlobFileDropzone from '~/blob/blob_file_dropzone'; describe('BlobFileDropzone', function() { - preloadFixtures('blob/show.html.raw'); + preloadFixtures('blob/show.html'); beforeEach(() => { - loadFixtures('blob/show.html.raw'); + loadFixtures('blob/show.html'); const form = $('.js-upload-blob-form'); this.blobFileDropzone = new BlobFileDropzone(form, 'POST'); this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone; diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index 28d3b2f5ea3..6bb5bac007f 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -3,10 +3,10 @@ import axios from '~/lib/utils/axios_utils'; import renderNotebook from '~/blob/notebook'; describe('iPython notebook renderer', () => { - preloadFixtures('static/notebook_viewer.html.raw'); + preloadFixtures('static/notebook_viewer.html'); beforeEach(() => { - loadFixtures('static/notebook_viewer.html.raw'); + loadFixtures('static/notebook_viewer.html'); }); it('shows loading icon', () => { diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index be917a0613f..acf87580777 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -15,10 +15,10 @@ describe('PDF renderer', () => { } }; - preloadFixtures('static/pdf_viewer.html.raw'); + preloadFixtures('static/pdf_viewer.html'); beforeEach(() => { - loadFixtures('static/pdf_viewer.html.raw'); + loadFixtures('static/pdf_viewer.html'); viewer = document.getElementById('js-pdf-viewer'); viewer.dataset.endpoint = testPDF; }); diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js index 2b1e81e9cbc..3d3129e10da 100644 --- a/spec/javascripts/blob/sketch/index_spec.js +++ b/spec/javascripts/blob/sketch/index_spec.js @@ -13,10 +13,10 @@ describe('Sketch viewer', () => { }); }; - preloadFixtures('static/sketch_viewer.html.raw'); + preloadFixtures('static/sketch_viewer.html'); beforeEach(() => { - loadFixtures('static/sketch_viewer.html.raw'); + loadFixtures('static/sketch_viewer.html'); }); describe('with error message', () => { diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index 93a942fe8d4..4ac15ca5aa2 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -9,12 +9,12 @@ describe('Blob viewer', () => { let blob; let mock; - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(() => { mock = new MockAdapter(axios); - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); $('#modal-upload-blob').remove(); blob = new BlobViewer(); diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index dee7841c088..6e6b3e6950b 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -9,7 +9,7 @@ describe('Board component', () => { let el; beforeEach(done => { - loadFixtures('boards/show.html.raw'); + loadFixtures('boards/show.html'); el = document.createElement('div'); document.body.appendChild(el); diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js index c3e3d78ff63..1d21637ceae 100644 --- a/spec/javascripts/bootstrap_linked_tabs_spec.js +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js @@ -1,10 +1,10 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; describe('Linked Tabs', () => { - preloadFixtures('static/linked_tabs.html.raw'); + preloadFixtures('static/linked_tabs.html'); beforeEach(() => { - loadFixtures('static/linked_tabs.html.raw'); + loadFixtures('static/linked_tabs.html'); }); describe('when is initialized', () => { diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 1fc0e206d5e..481b1a4d4b0 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -7,8 +7,8 @@ const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-proje const HIDE_CLASS = 'hide'; describe('AjaxFormVariableList', () => { - preloadFixtures('projects/ci_cd_settings.html.raw'); - preloadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + preloadFixtures('projects/ci_cd_settings.html'); + preloadFixtures('projects/ci_cd_settings_with_variables.html'); let container; let saveButton; @@ -18,7 +18,7 @@ describe('AjaxFormVariableList', () => { let ajaxVariableList; beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); container = document.querySelector('.js-ci-variable-list-section'); mock = new MockAdapter(axios); @@ -168,7 +168,7 @@ describe('AjaxFormVariableList', () => { describe('updateRowsWithPersistedVariables', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + loadFixtures('projects/ci_cd_settings_with_variables.html'); container = document.querySelector('.js-ci-variable-list-section'); const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section'); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index bef59b86d0c..70f49469300 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -5,9 +5,9 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; const HIDE_CLASS = 'hide'; describe('VariableList', () => { - preloadFixtures('pipeline_schedules/edit.html.raw'); - preloadFixtures('pipeline_schedules/edit_with_variables.html.raw'); - preloadFixtures('projects/ci_cd_settings.html.raw'); + preloadFixtures('pipeline_schedules/edit.html'); + preloadFixtures('pipeline_schedules/edit_with_variables.html'); + preloadFixtures('projects/ci_cd_settings.html'); let $wrapper; let variableList; @@ -15,7 +15,7 @@ describe('VariableList', () => { describe('with only key/value inputs', () => { describe('with no variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html.raw'); + loadFixtures('pipeline_schedules/edit.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -82,7 +82,7 @@ describe('VariableList', () => { describe('with persisted variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + loadFixtures('pipeline_schedules/edit_with_variables.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -115,7 +115,7 @@ describe('VariableList', () => { describe('with all inputs(key, value, protected)', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); $wrapper = $('.js-ci-variable-list-section'); $wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false'); @@ -149,7 +149,7 @@ describe('VariableList', () => { describe('toggleEnableRow method', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + loadFixtures('pipeline_schedules/edit_with_variables.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -198,7 +198,7 @@ describe('VariableList', () => { describe('hideValues', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js index 997d0d54d79..4982b68fa81 100644 --- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js @@ -2,12 +2,12 @@ import $ from 'jquery'; import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list'; describe('NativeFormVariableList', () => { - preloadFixtures('pipeline_schedules/edit.html.raw'); + preloadFixtures('pipeline_schedules/edit.html'); let $wrapper; beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html.raw'); + loadFixtures('pipeline_schedules/edit.html'); $wrapper = $('.js-ci-variable-list-section'); setupNativeFormVariableList({ diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 7928feeadfa..e094e6a462d 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -4,10 +4,10 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; describe('Clusters', () => { let cluster; - preloadFixtures('clusters/show_cluster.html.raw'); + preloadFixtures('clusters/show_cluster.html'); beforeEach(() => { - loadFixtures('clusters/show_cluster.html.raw'); + loadFixtures('clusters/show_cluster.html'); cluster = new Clusters(); }); diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js index dc5737558c0..bb90e53e525 100644 --- a/spec/javascripts/collapsed_sidebar_todo_spec.js +++ b/spec/javascripts/collapsed_sidebar_todo_spec.js @@ -6,7 +6,7 @@ import Sidebar from '~/right_sidebar'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Issuable right sidebar collapsed todo toggle', () => { - const fixtureName = 'issues/open-issue.html.raw'; + const fixtureName = 'issues/open-issue.html'; const jsonFixtureName = 'todos/todos.json'; let mock; diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js index 9cf72d7c55b..a814952faab 100644 --- a/spec/javascripts/create_item_dropdown_spec.js +++ b/spec/javascripts/create_item_dropdown_spec.js @@ -20,7 +20,7 @@ const DROPDOWN_ITEM_DATA = [ ]; describe('CreateItemDropdown', () => { - preloadFixtures('static/create_item_dropdown.html.raw'); + preloadFixtures('static/create_item_dropdown.html'); let $wrapperEl; let createItemDropdown; @@ -44,7 +44,7 @@ describe('CreateItemDropdown', () => { } beforeEach(() => { - loadFixtures('static/create_item_dropdown.html.raw'); + loadFixtures('static/create_item_dropdown.html'); $wrapperEl = $('.js-create-item-dropdown-fixture-root'); }); diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index e8fcc8592eb..f764800fff0 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -72,7 +72,7 @@ describe('Dropdown User', () => { }); describe('hideCurrentUser', () => { - const fixtureTemplate = 'issues/issue_list.html.raw'; + const fixtureTemplate = 'issues/issue_list.html'; preloadFixtures(fixtureTemplate); let dropdown; diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index cfd0b96ec43..62d1bd69635 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -4,7 +4,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; describe('Dropdown Utils', () => { - const issueListFixture = 'issues/issue_list.html.raw'; + const issueListFixture = 'issues/issue_list.html'; preloadFixtures(issueListFixture); describe('getEscapedText', () => { diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore index 0c35cdd778e..2507c8e7263 100644 --- a/spec/javascripts/fixtures/.gitignore +++ b/spec/javascripts/fixtures/.gitignore @@ -1,2 +1,3 @@ *.html.raw +*.html *.json diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb index 387858cba77..54b6419bcdb 100644 --- a/spec/javascripts/fixtures/abuse_reports.rb +++ b/spec/javascripts/fixtures/abuse_reports.rb @@ -18,7 +18,7 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll sign_in(admin) end - it 'abuse_reports/abuse_reports_list.html.raw' do |example| + it 'abuse_reports/abuse_reports_list.html' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb index 9989ac4fff2..76dbdf603da 100644 --- a/spec/javascripts/fixtures/admin_users.rb +++ b/spec/javascripts/fixtures/admin_users.rb @@ -17,7 +17,7 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('admin/users') end - it 'admin/users/new_with_internal_user_regex.html.raw' do |example| + it 'admin/users/new_with_internal_user_regex.html' do |example| stub_application_setting(user_default_external: true) stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?') diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb index a9d3043f73d..c535e598e12 100644 --- a/spec/javascripts/fixtures/application_settings.rb +++ b/spec/javascripts/fixtures/application_settings.rb @@ -23,7 +23,7 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c remove_repository(project) end - it 'application_settings/accounts_and_limit.html.raw' do |example| + it 'application_settings/accounts_and_limit.html' do |example| stub_application_setting(user_default_external: false) get :show diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index cd66d98f92a..db7749bc000 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -22,7 +22,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'blob/show.html.raw' do |example| + it 'blob/show.html' do |example| get(:show, params: { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb index 1d675e008ba..c4390e89578 100644 --- a/spec/javascripts/fixtures/boards.rb +++ b/spec/javascripts/fixtures/boards.rb @@ -17,7 +17,7 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller sign_in(admin) end - it 'boards/show.html.raw' do |example| + it 'boards/show.html' do |example| get(:index, params: { namespace_id: project.namespace, project_id: project diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb index 3cc713ef90f..5d2d6c7ec0e 100644 --- a/spec/javascripts/fixtures/branches.rb +++ b/spec/javascripts/fixtures/branches.rb @@ -21,7 +21,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'branches/new_branch.html.raw' do |example| + it 'branches/new_branch.html' do |example| get :new, params: { namespace_id: project.namespace.to_param, project_id: project diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 69dbe54ffc2..8ebd8a41366 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -22,7 +22,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'clusters/show_cluster.html.raw' do |example| + it 'clusters/show_cluster.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index 295f13b34a4..ab10f559e4b 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -19,7 +19,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end - it 'commit/show.html.raw' do |example| + it 'commit/show.html' do |example| params = { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index 03136f4e661..16e31028b05 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -18,7 +18,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe GroupsController, '(JavaScript fixtures)', type: :controller do - it 'groups/edit.html.raw' do |example| + it 'groups/edit.html' do |example| get :edit, params: { id: group } expect(response).to be_success @@ -27,7 +27,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'groups/ci_cd_settings.html.raw' do |example| + it 'groups/ci_cd_settings.html' do |example| get :show, params: { group_id: group } expect(response).to be_success diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 9b8e90c2a43..645b3aa788a 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -21,26 +21,26 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller remove_repository(project) end - it 'issues/open-issue.html.raw' do |example| + it 'issues/open-issue.html' do |example| render_issue(example.description, create(:issue, project: project)) end - it 'issues/closed-issue.html.raw' do |example| + it 'issues/closed-issue.html' do |example| render_issue(example.description, create(:closed_issue, project: project)) end - it 'issues/issue-with-task-list.html.raw' do |example| + it 'issues/issue-with-task-list.html' do |example| issue = create(:issue, project: project, description: '- [ ] Task List Item') render_issue(example.description, issue) end - it 'issues/issue_with_comment.html.raw' do |example| + it 'issues/issue_with_comment.html' do |example| issue = create(:issue, project: project) create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save render_issue(example.description, issue) end - it 'issues/issue_list.html.raw' do |example| + it 'issues/issue_list.html' do |example| create(:issue, project: project) get :index, params: { diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 433bb690a1c..941235190b5 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -32,7 +32,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'builds/build-with-artifacts.html.raw' do |example| + it 'builds/build-with-artifacts.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index eb37be87e1d..7df1e5cb512 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -42,19 +42,19 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont remove_repository(project) end - it 'merge_requests/merge_request_of_current_user.html.raw' do |example| + it 'merge_requests/merge_request_of_current_user.html' do |example| merge_request.update(author: admin) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_task_list.html.raw' do |example| + it 'merge_requests/merge_request_with_task_list.html' do |example| create(:ci_build, :pending, pipeline: pipeline) render_merge_request(example.description, merge_request) end - it 'merge_requests/merged_merge_request.html.raw' do |example| + it 'merge_requests/merged_merge_request.html' do |example| expect_next_instance_of(MergeRequest) do |merge_request| allow(merge_request).to receive(:source_branch_exists?).and_return(true) allow(merge_request).to receive(:can_remove_source_branch?).and_return(true) @@ -62,13 +62,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merged_merge_request) end - it 'merge_requests/diff_comment.html.raw' do |example| + it 'merge_requests/diff_comment.html' do |example| create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_comment.html.raw' do |example| + it 'merge_requests/merge_request_with_comment.html' do |example| create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item') render_merge_request(example.description, merge_request) end diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb index 05d79ec8de9..e5176a58273 100644 --- a/spec/javascripts/fixtures/pipeline_schedules.rb +++ b/spec/javascripts/fixtures/pipeline_schedules.rb @@ -21,7 +21,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : sign_in(admin) end - it 'pipeline_schedules/edit.html.raw' do |example| + it 'pipeline_schedules/edit.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, @@ -32,7 +32,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : store_frontend_fixture(response, example.description) end - it 'pipeline_schedules/edit_with_variables.html.raw' do |example| + it 'pipeline_schedules/edit_with_variables.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 85f02923804..446da83a7f9 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -28,7 +28,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe ProjectsController, '(JavaScript fixtures)', type: :controller do - it 'projects/dashboard.html.raw' do |example| + it 'projects/dashboard.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, id: project @@ -38,7 +38,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/overview.html.raw' do |example| + it 'projects/overview.html' do |example| get :show, params: { namespace_id: project_with_repo.namespace.to_param, id: project_with_repo @@ -48,7 +48,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/edit.html.raw' do |example| + it 'projects/edit.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, id: project @@ -60,7 +60,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'projects/ci_cd_settings.html.raw' do |example| + it 'projects/ci_cd_settings.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project @@ -70,7 +70,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/ci_cd_settings_with_variables.html.raw' do |example| + it 'projects/ci_cd_settings_with_variables.html' do |example| create(:ci_variable, project: project_variable_populated) create(:ci_variable, project: project_variable_populated) diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb index 746fbfd66dd..29dc95305b7 100644 --- a/spec/javascripts/fixtures/prometheus_service.rb +++ b/spec/javascripts/fixtures/prometheus_service.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/prometheus/prometheus_service.html.raw' do |example| + it 'services/prometheus/prometheus_service.html' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb index 703cd3d49fa..5f5b4d4e60d 100644 --- a/spec/javascripts/fixtures/search.rb +++ b/spec/javascripts/fixtures/search.rb @@ -9,7 +9,7 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('search/') end - it 'search/show.html.raw' do |example| + it 'search/show.html' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb index 6ccd74a07ff..dc7ee484c22 100644 --- a/spec/javascripts/fixtures/services.rb +++ b/spec/javascripts/fixtures/services.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/edit_service.html.raw' do |example| + it 'services/edit_service.html' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb index e90a58e8c54..8656dea696a 100644 --- a/spec/javascripts/fixtures/sessions.rb +++ b/spec/javascripts/fixtures/sessions.rb @@ -16,7 +16,7 @@ describe 'Sessions (JavaScript fixtures)' do set_devise_mapping(context: @request) end - it 'sessions/new.html.raw' do |example| + it 'sessions/new.html' do |example| get :new expect(response).to be_success diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index bcd6546f3df..ebc5b793166 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -23,7 +23,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'snippets/show.html.raw' do |example| + it 'snippets/show.html' do |example| create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') get(:show, params: { id: snippet.to_param }) diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html b/spec/javascripts/fixtures/static/ajax_loading_spinner.html new file mode 100644 index 00000000000..0e1ebb32b1c --- /dev/null +++ b/spec/javascripts/fixtures/static/ajax_loading_spinner.html @@ -0,0 +1,3 @@ + + + diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw b/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw deleted file mode 100644 index 0e1ebb32b1c..00000000000 --- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html b/spec/javascripts/fixtures/static/balsamiq_viewer.html new file mode 100644 index 00000000000..cdd723d1a84 --- /dev/null +++ b/spec/javascripts/fixtures/static/balsamiq_viewer.html @@ -0,0 +1 @@ +
    diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw b/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw deleted file mode 100644 index cdd723d1a84..00000000000 --- a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html b/spec/javascripts/fixtures/static/create_item_dropdown.html new file mode 100644 index 00000000000..d2d38370092 --- /dev/null +++ b/spec/javascripts/fixtures/static/create_item_dropdown.html @@ -0,0 +1,11 @@ +
    + +
    diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw b/spec/javascripts/fixtures/static/create_item_dropdown.html.raw deleted file mode 100644 index d2d38370092..00000000000 --- a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw +++ /dev/null @@ -1,11 +0,0 @@ -
    - -
    diff --git a/spec/javascripts/fixtures/static/event_filter.html b/spec/javascripts/fixtures/static/event_filter.html new file mode 100644 index 00000000000..8e9b6fb1b5c --- /dev/null +++ b/spec/javascripts/fixtures/static/event_filter.html @@ -0,0 +1,44 @@ + diff --git a/spec/javascripts/fixtures/static/event_filter.html.raw b/spec/javascripts/fixtures/static/event_filter.html.raw deleted file mode 100644 index 8e9b6fb1b5c..00000000000 --- a/spec/javascripts/fixtures/static/event_filter.html.raw +++ /dev/null @@ -1,44 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html b/spec/javascripts/fixtures/static/gl_dropdown.html new file mode 100644 index 00000000000..08f6738414e --- /dev/null +++ b/spec/javascripts/fixtures/static/gl_dropdown.html @@ -0,0 +1,26 @@ +
    + +
    diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html.raw b/spec/javascripts/fixtures/static/gl_dropdown.html.raw deleted file mode 100644 index 08f6738414e..00000000000 --- a/spec/javascripts/fixtures/static/gl_dropdown.html.raw +++ /dev/null @@ -1,26 +0,0 @@ -
    - -
    diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html b/spec/javascripts/fixtures/static/gl_field_errors.html new file mode 100644 index 00000000000..f8470e02b7c --- /dev/null +++ b/spec/javascripts/fixtures/static/gl_field_errors.html @@ -0,0 +1,22 @@ +
    +
    +Text +
    +
    +Email +
    +
    +Password +
    +
    +Alphanumeric +
    +
    + +
    +
    +Custom, do not validate +
    +
    +Submit +
    diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html.raw b/spec/javascripts/fixtures/static/gl_field_errors.html.raw deleted file mode 100644 index f8470e02b7c..00000000000 --- a/spec/javascripts/fixtures/static/gl_field_errors.html.raw +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -Text -
    -
    -Email -
    -
    -Password -
    -
    -Alphanumeric -
    -
    - -
    -
    -Custom, do not validate -
    -
    -Submit -
    diff --git a/spec/javascripts/fixtures/static/issuable_filter.html b/spec/javascripts/fixtures/static/issuable_filter.html new file mode 100644 index 00000000000..06b70fb43f1 --- /dev/null +++ b/spec/javascripts/fixtures/static/issuable_filter.html @@ -0,0 +1,9 @@ +
    + + + + + + + +
    diff --git a/spec/javascripts/fixtures/static/issuable_filter.html.raw b/spec/javascripts/fixtures/static/issuable_filter.html.raw deleted file mode 100644 index 06b70fb43f1..00000000000 --- a/spec/javascripts/fixtures/static/issuable_filter.html.raw +++ /dev/null @@ -1,9 +0,0 @@ -
    - - - - - - - -
    diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html b/spec/javascripts/fixtures/static/issue_sidebar_label.html new file mode 100644 index 00000000000..ec8fb30f219 --- /dev/null +++ b/spec/javascripts/fixtures/static/issue_sidebar_label.html @@ -0,0 +1,26 @@ +
    + + + +
    diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw b/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw deleted file mode 100644 index ec8fb30f219..00000000000 --- a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw +++ /dev/null @@ -1,26 +0,0 @@ -
    - - - -
    diff --git a/spec/javascripts/fixtures/static/line_highlighter.html b/spec/javascripts/fixtures/static/line_highlighter.html new file mode 100644 index 00000000000..897a25d6760 --- /dev/null +++ b/spec/javascripts/fixtures/static/line_highlighter.html @@ -0,0 +1,107 @@ +
    +
    + +
    Line 1Line 2Line 3Line 4Line 5Line 6Line 7Line 8Line 9Line 10Line 11Line 12Line 13Line 14Line 15Line 16Line 17Line 18Line 19Line 20Line 21Line 22Line 23Line 24Line 25
    +
    +
    diff --git a/spec/javascripts/fixtures/static/line_highlighter.html.raw b/spec/javascripts/fixtures/static/line_highlighter.html.raw deleted file mode 100644 index 897a25d6760..00000000000 --- a/spec/javascripts/fixtures/static/line_highlighter.html.raw +++ /dev/null @@ -1,107 +0,0 @@ -
    -
    - -
    Line 1Line 2Line 3Line 4Line 5Line 6Line 7Line 8Line 9Line 10Line 11Line 12Line 13Line 14Line 15Line 16Line 17Line 18Line 19Line 20Line 21Line 22Line 23Line 24Line 25
    -
    -
    diff --git a/spec/javascripts/fixtures/static/linked_tabs.html b/spec/javascripts/fixtures/static/linked_tabs.html new file mode 100644 index 00000000000..c25463bf1db --- /dev/null +++ b/spec/javascripts/fixtures/static/linked_tabs.html @@ -0,0 +1,20 @@ + +
    +
    +Tab 1 Content +
    +
    +Tab 2 Content +
    +
    diff --git a/spec/javascripts/fixtures/static/linked_tabs.html.raw b/spec/javascripts/fixtures/static/linked_tabs.html.raw deleted file mode 100644 index c25463bf1db..00000000000 --- a/spec/javascripts/fixtures/static/linked_tabs.html.raw +++ /dev/null @@ -1,20 +0,0 @@ - -
    -
    -Tab 1 Content -
    -
    -Tab 2 Content -
    -
    diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html b/spec/javascripts/fixtures/static/merge_requests_show.html new file mode 100644 index 00000000000..e219d9462aa --- /dev/null +++ b/spec/javascripts/fixtures/static/merge_requests_show.html @@ -0,0 +1,15 @@ + +
    +
    +
    +
      +
    • + +Task List Item +
    • +
    + +
    +
    +
    +
    diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html.raw b/spec/javascripts/fixtures/static/merge_requests_show.html.raw deleted file mode 100644 index e219d9462aa..00000000000 --- a/spec/javascripts/fixtures/static/merge_requests_show.html.raw +++ /dev/null @@ -1,15 +0,0 @@ - -
    -
    -
    -
      -
    • - -Task List Item -
    • -
    - -
    -
    -
    -
    diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html b/spec/javascripts/fixtures/static/mini_dropdown_graph.html new file mode 100644 index 00000000000..cd0b8dec3fc --- /dev/null +++ b/spec/javascripts/fixtures/static/mini_dropdown_graph.html @@ -0,0 +1,13 @@ + diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw b/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw deleted file mode 100644 index cd0b8dec3fc..00000000000 --- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html b/spec/javascripts/fixtures/static/notebook_viewer.html new file mode 100644 index 00000000000..4bbb7bf1094 --- /dev/null +++ b/spec/javascripts/fixtures/static/notebook_viewer.html @@ -0,0 +1 @@ +
    diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html.raw b/spec/javascripts/fixtures/static/notebook_viewer.html.raw deleted file mode 100644 index 4bbb7bf1094..00000000000 --- a/spec/javascripts/fixtures/static/notebook_viewer.html.raw +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html b/spec/javascripts/fixtures/static/oauth_remember_me.html new file mode 100644 index 00000000000..9ba1ffc72fe --- /dev/null +++ b/spec/javascripts/fixtures/static/oauth_remember_me.html @@ -0,0 +1,6 @@ +
    + + + + +
    diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw b/spec/javascripts/fixtures/static/oauth_remember_me.html.raw deleted file mode 100644 index 9ba1ffc72fe..00000000000 --- a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw +++ /dev/null @@ -1,6 +0,0 @@ -
    - - - - -
    diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html b/spec/javascripts/fixtures/static/pdf_viewer.html new file mode 100644 index 00000000000..350d35a262f --- /dev/null +++ b/spec/javascripts/fixtures/static/pdf_viewer.html @@ -0,0 +1 @@ +
    diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html.raw b/spec/javascripts/fixtures/static/pdf_viewer.html.raw deleted file mode 100644 index 350d35a262f..00000000000 --- a/spec/javascripts/fixtures/static/pdf_viewer.html.raw +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html b/spec/javascripts/fixtures/static/pipeline_graph.html new file mode 100644 index 00000000000..422372bb7d5 --- /dev/null +++ b/spec/javascripts/fixtures/static/pipeline_graph.html @@ -0,0 +1,24 @@ + diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html.raw b/spec/javascripts/fixtures/static/pipeline_graph.html.raw deleted file mode 100644 index 422372bb7d5..00000000000 --- a/spec/javascripts/fixtures/static/pipeline_graph.html.raw +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/pipelines.html b/spec/javascripts/fixtures/static/pipelines.html new file mode 100644 index 00000000000..42333f94f2f --- /dev/null +++ b/spec/javascripts/fixtures/static/pipelines.html @@ -0,0 +1,3 @@ +
    +
    +
    diff --git a/spec/javascripts/fixtures/static/pipelines.html.raw b/spec/javascripts/fixtures/static/pipelines.html.raw deleted file mode 100644 index 42333f94f2f..00000000000 --- a/spec/javascripts/fixtures/static/pipelines.html.raw +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    -
    diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html b/spec/javascripts/fixtures/static/project_select_combo_button.html new file mode 100644 index 00000000000..50c826051c0 --- /dev/null +++ b/spec/javascripts/fixtures/static/project_select_combo_button.html @@ -0,0 +1,9 @@ +
    + + + + + + + +
    diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw b/spec/javascripts/fixtures/static/project_select_combo_button.html.raw deleted file mode 100644 index 50c826051c0..00000000000 --- a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw +++ /dev/null @@ -1,9 +0,0 @@ -
    - - - - - - - -
    diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html b/spec/javascripts/fixtures/static/search_autocomplete.html new file mode 100644 index 00000000000..29db9020424 --- /dev/null +++ b/spec/javascripts/fixtures/static/search_autocomplete.html @@ -0,0 +1,15 @@ + diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html.raw b/spec/javascripts/fixtures/static/search_autocomplete.html.raw deleted file mode 100644 index 29db9020424..00000000000 --- a/spec/javascripts/fixtures/static/search_autocomplete.html.raw +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/signin_tabs.html b/spec/javascripts/fixtures/static/signin_tabs.html new file mode 100644 index 00000000000..7e66ab9394b --- /dev/null +++ b/spec/javascripts/fixtures/static/signin_tabs.html @@ -0,0 +1,8 @@ + diff --git a/spec/javascripts/fixtures/static/signin_tabs.html.raw b/spec/javascripts/fixtures/static/signin_tabs.html.raw deleted file mode 100644 index 7e66ab9394b..00000000000 --- a/spec/javascripts/fixtures/static/signin_tabs.html.raw +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html b/spec/javascripts/fixtures/static/sketch_viewer.html new file mode 100644 index 00000000000..e25e554e568 --- /dev/null +++ b/spec/javascripts/fixtures/static/sketch_viewer.html @@ -0,0 +1,3 @@ +
    +
    +
    diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html.raw b/spec/javascripts/fixtures/static/sketch_viewer.html.raw deleted file mode 100644 index e25e554e568..00000000000 --- a/spec/javascripts/fixtures/static/sketch_viewer.html.raw +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    -
    diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb index b5f6620873b..6e37a2e5a4c 100644 --- a/spec/javascripts/fixtures/todos.rb +++ b/spec/javascripts/fixtures/todos.rb @@ -26,7 +26,7 @@ describe 'Todos (JavaScript fixtures)' do sign_in(admin) end - it 'todos/todos.html.raw' do |example| + it 'todos/todos.html' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index 5cdbadef639..15866d65a4f 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -18,7 +18,7 @@ context 'U2F' do set_devise_mapping(context: @request) end - it 'u2f/authenticate.html.raw' do |example| + it 'u2f/authenticate.html' do |example| allow(controller).to receive(:find_user).and_return(user) post :create, params: { user: { login: user.username, password: user.password } } @@ -36,7 +36,7 @@ context 'U2F' do allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares') end - it 'u2f/register.html.raw' do |example| + it 'u2f/register.html' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 85083653db8..57e31d933ca 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -5,7 +5,7 @@ import GLDropdown from '~/gl_dropdown'; import '~/lib/utils/common_utils'; describe('glDropdown', function describeDropdown() { - preloadFixtures('static/gl_dropdown.html.raw'); + preloadFixtures('static/gl_dropdown.html'); loadJSONFixtures('projects.json'); const NON_SELECTABLE_CLASSES = @@ -64,7 +64,7 @@ describe('glDropdown', function describeDropdown() { } beforeEach(() => { - loadFixtures('static/gl_dropdown.html.raw'); + loadFixtures('static/gl_dropdown.html'); this.dropdownContainerElement = $('.dropdown.inline'); this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); this.projectsData = getJSONFixture('projects.json'); diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js index b463c9afbee..294f219d6fe 100644 --- a/spec/javascripts/gl_field_errors_spec.js +++ b/spec/javascripts/gl_field_errors_spec.js @@ -4,10 +4,10 @@ import $ from 'jquery'; import GlFieldErrors from '~/gl_field_errors'; describe('GL Style Field Errors', function() { - preloadFixtures('static/gl_field_errors.html.raw'); + preloadFixtures('static/gl_field_errors.html'); beforeEach(function() { - loadFixtures('static/gl_field_errors.html.raw'); + loadFixtures('static/gl_field_errors.html'); const $form = $('form.gl-show-field-errors'); this.$form = $form; diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 2fe34e5a76f..0ddf589f368 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -3,7 +3,7 @@ import initTodoToggle from '~/header'; describe('Header', function() { const todosPendingCount = '.todos-count'; - const fixtureTemplate = 'issues/open-issue.html.raw'; + const fixtureTemplate = 'issues/open-issue.html'; function isTodosCountHidden() { return $(todosPendingCount).hasClass('hidden'); diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 4f4c9a7b463..069e2cb07b5 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils'; import IntegrationSettingsForm from '~/integrations/integration_settings_form'; describe('IntegrationSettingsForm', () => { - const FIXTURE = 'services/edit_service.html.raw'; + const FIXTURE = 'services/edit_service.html'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 7be495d1d35..11ab6c38a55 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -9,9 +9,9 @@ import '~/lib/utils/text_utility'; describe('Issue', function() { let $boxClosed, $boxOpen, $btn; - preloadFixtures('issues/closed-issue.html.raw'); - preloadFixtures('issues/issue-with-task-list.html.raw'); - preloadFixtures('issues/open-issue.html.raw'); + preloadFixtures('issues/closed-issue.html'); + preloadFixtures('issues/issue-with-task-list.html'); + preloadFixtures('issues/open-issue.html'); function expectErrorMessage() { const $flashMessage = $('div.flash-alert'); @@ -105,9 +105,9 @@ describe('Issue', function() { beforeEach(function() { if (isIssueInitiallyOpen) { - loadFixtures('issues/open-issue.html.raw'); + loadFixtures('issues/open-issue.html'); } else { - loadFixtures('issues/closed-issue.html.raw'); + loadFixtures('issues/closed-issue.html'); } mock = new MockAdapter(axios); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index e5678ee5379..ccf439aac74 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -16,10 +16,10 @@ let saveLabelCount = 0; let mock; describe('Issue dropdown sidebar', () => { - preloadFixtures('static/issue_sidebar_label.html.raw'); + preloadFixtures('static/issue_sidebar_label.html'); beforeEach(() => { - loadFixtures('static/issue_sidebar_label.html.raw'); + loadFixtures('static/issue_sidebar_label.html'); mock = new MockAdapter(axios); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index cbdc1644430..f3fb792c62d 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -11,11 +11,11 @@ const execImmediately = callback => { describe('LazyLoader', function() { let lazyLoader = null; - preloadFixtures('issues/issue_with_comment.html.raw'); + preloadFixtures('issues/issue_with_comment.html'); describe('without IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html.raw'); + loadFixtures('issues/issue_with_comment.html'); lazyLoader = new LazyLoader({ observerNode: 'foobar', @@ -131,7 +131,7 @@ describe('LazyLoader', function() { describe('with IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html.raw'); + loadFixtures('issues/issue_with_comment.html'); lazyLoader = new LazyLoader({ observerNode: 'foobar', diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index 4eea364bd69..a75470b4db8 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -5,7 +5,7 @@ import LineHighlighter from '~/line_highlighter'; describe('LineHighlighter', function() { var clickLine; - preloadFixtures('static/line_highlighter.html.raw'); + preloadFixtures('static/line_highlighter.html'); clickLine = function(number, eventData = {}) { if ($.isEmptyObject(eventData)) { return $('#L' + number).click(); @@ -15,7 +15,7 @@ describe('LineHighlighter', function() { } }; beforeEach(function() { - loadFixtures('static/line_highlighter.html.raw'); + loadFixtures('static/line_highlighter.html'); this['class'] = new LineHighlighter(); this.css = this['class'].highlightLineClass; return (this.spies = { diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index ab809930804..431798c6ec3 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -11,9 +11,9 @@ describe('MergeRequest', function() { describe('task lists', function() { let mock; - preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + preloadFixtures('merge_requests/merge_request_with_task_list.html'); beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); spyOn(axios, 'patch').and.callThrough(); mock = new MockAdapter(axios); @@ -125,7 +125,7 @@ describe('MergeRequest', function() { describe('hideCloseButton', () => { describe('merge request of another user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); this.el = document.querySelector('.js-issuable-actions'); new MergeRequest(); // eslint-disable-line no-new MergeRequest.hideCloseButton(); @@ -145,7 +145,7 @@ describe('MergeRequest', function() { describe('merge request of current_user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + loadFixtures('merge_requests/merge_request_of_current_user.html'); this.el = document.querySelector('.js-issuable-actions'); MergeRequest.hideCloseButton(); }); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index c8df05eccf5..1295d900de7 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -22,8 +22,8 @@ describe('MergeRequestTabs', function() { }; preloadFixtures( - 'merge_requests/merge_request_with_task_list.html.raw', - 'merge_requests/diff_comment.html.raw', + 'merge_requests/merge_request_with_task_list.html', + 'merge_requests/diff_comment.html', ); beforeEach(function() { @@ -48,7 +48,7 @@ describe('MergeRequestTabs', function() { var windowTarget = '_blank'; beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); tabUrl = $('.commits-tab a').attr('href'); }); diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js index 092ca9e1dab..aa4a376caf7 100644 --- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js +++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js @@ -5,10 +5,10 @@ import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Mini Pipeline Graph Dropdown', () => { - preloadFixtures('static/mini_dropdown_graph.html.raw'); + preloadFixtures('static/mini_dropdown_graph.html'); beforeEach(() => { - loadFixtures('static/mini_dropdown_graph.html.raw'); + loadFixtures('static/mini_dropdown_graph.html'); }); describe('When is initialized', () => { diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 1d7b885e64f..4e3140ce4f1 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -3,7 +3,7 @@ import NewBranchForm from '~/new_branch_form'; describe('Branch', function() { describe('create a new branch', function() { - preloadFixtures('branches/new_branch.html.raw'); + preloadFixtures('branches/new_branch.html'); function fillNameWith(value) { $('.js-branch-name') @@ -16,7 +16,7 @@ describe('Branch', function() { } beforeEach(function() { - loadFixtures('branches/new_branch.html.raw'); + loadFixtures('branches/new_branch.html'); $('form').on('submit', function(e) { return e.preventDefault(); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 7c869d4c326..3d2c617e479 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -34,7 +34,7 @@ const htmlEscape = comment => { describe('Notes', function() { const FLASH_TYPE_ALERT = 'alert'; const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; - var fixture = 'snippets/show.html.raw'; + var fixture = 'snippets/show.html'; preloadFixtures(fixture); beforeEach(function() { diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js index 4125706a407..381be82697e 100644 --- a/spec/javascripts/oauth_remember_me_spec.js +++ b/spec/javascripts/oauth_remember_me_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me'; describe('OAuthRememberMe', () => { - preloadFixtures('static/oauth_remember_me.html.raw'); + preloadFixtures('static/oauth_remember_me.html'); beforeEach(() => { - loadFixtures('static/oauth_remember_me.html.raw'); + loadFixtures('static/oauth_remember_me.html'); new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents(); }); diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js index 23d07056925..f7637964c60 100644 --- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js +++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js @@ -3,7 +3,7 @@ import '~/lib/utils/text_utility'; import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; describe('Abuse Reports', () => { - const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; + const FIXTURE = 'abuse_reports/abuse_reports_list.html'; const MAX_MESSAGE_LENGTH = 500; let $messages; diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js index 561bd2c96cb..6a239e307e9 100644 --- a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js +++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js @@ -5,7 +5,7 @@ import initUserInternalRegexPlaceholder, { } from '~/pages/admin/application_settings/account_and_limits'; describe('AccountAndLimits', () => { - const FIXTURE = 'application_settings/accounts_and_limit.html.raw'; + const FIXTURE = 'application_settings/accounts_and_limit.html'; let $userDefaultExternal; let $userInternalRegex; preloadFixtures(FIXTURE); diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js index 5a849f34bc3..3896323eef7 100644 --- a/spec/javascripts/pages/admin/users/new/index_spec.js +++ b/spec/javascripts/pages/admin/users/new/index_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import UserInternalRegexHandler from '~/pages/admin/users/new/index'; describe('UserInternalRegexHandler', () => { - const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw'; + const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; let $userExternal; let $userEmail; let $warningMessage; diff --git a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js index 7a8227479d4..1809e92e1d9 100644 --- a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js +++ b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment'; describe('preserve_url_fragment', () => { - preloadFixtures('sessions/new.html.raw'); + preloadFixtures('sessions/new.html'); beforeEach(() => { - loadFixtures('sessions/new.html.raw'); + loadFixtures('sessions/new.html'); }); it('adds the url fragment to all login and sign up form actions', () => { diff --git a/spec/javascripts/pipelines_spec.js b/spec/javascripts/pipelines_spec.js index 6b86f9ea437..6d4d634c575 100644 --- a/spec/javascripts/pipelines_spec.js +++ b/spec/javascripts/pipelines_spec.js @@ -1,10 +1,10 @@ import Pipelines from '~/pipelines'; describe('Pipelines', () => { - preloadFixtures('static/pipeline_graph.html.raw'); + preloadFixtures('static/pipeline_graph.html'); beforeEach(() => { - loadFixtures('static/pipeline_graph.html.raw'); + loadFixtures('static/pipeline_graph.html'); }); it('should be defined', () => { diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js index 109a5000f5d..dc85292c23e 100644 --- a/spec/javascripts/project_select_combo_button_spec.js +++ b/spec/javascripts/project_select_combo_button_spec.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import ProjectSelectComboButton from '~/project_select_combo_button'; -const fixturePath = 'static/project_select_combo_button.html.raw'; +const fixturePath = 'static/project_select_combo_button.html'; describe('Project Select Combo Button', function() { preloadFixtures(fixturePath); diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js index 94e2f959d46..dca3e1553b9 100644 --- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js +++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js @@ -5,7 +5,7 @@ import PANEL_STATE from '~/prometheus_metrics/constants'; import { metrics, missingVarMetrics } from './mock_data'; describe('PrometheusMetrics', () => { - const FIXTURE = 'services/prometheus/prometheus_service.html.raw'; + const FIXTURE = 'services/prometheus/prometheus_service.html'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/read_more_spec.js b/spec/javascripts/read_more_spec.js index b1af0f80a50..d1d01272403 100644 --- a/spec/javascripts/read_more_spec.js +++ b/spec/javascripts/read_more_spec.js @@ -1,7 +1,7 @@ import initReadMore from '~/read_more'; describe('Read more click-to-expand functionality', () => { - const fixtureName = 'projects/overview.html.raw'; + const fixtureName = 'projects/overview.html'; preloadFixtures(fixtureName); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 992e17978c1..9565e3ce546 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -23,7 +23,7 @@ const assertSidebarState = function(state) { describe('RightSidebar', function() { describe('fixture tests', () => { - const fixtureName = 'issues/open-issue.html.raw'; + const fixtureName = 'issues/open-issue.html'; preloadFixtures(fixtureName); loadJSONFixtures('todos/todos.json'); let mock; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 7a4ca587313..ce7fa7a52ae 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -126,9 +126,9 @@ describe('Search autocomplete dropdown', () => { expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created"); }; - preloadFixtures('static/search_autocomplete.html.raw'); + preloadFixtures('static/search_autocomplete.html'); beforeEach(function() { - loadFixtures('static/search_autocomplete.html.raw'); + loadFixtures('static/search_autocomplete.html'); window.gon = {}; window.gon.current_user_id = userId; diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js index 40bdbac7451..32f60508fa3 100644 --- a/spec/javascripts/search_spec.js +++ b/spec/javascripts/search_spec.js @@ -3,7 +3,7 @@ import Api from '~/api'; import Search from '~/pages/search/show/search'; describe('Search', () => { - const fixturePath = 'search/show.html.raw'; + const fixturePath = 'search/show.html'; const searchTerm = 'some search'; const fillDropdownInput = dropdownSelector => { const dropdownElement = document.querySelector(dropdownSelector).parentNode; diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js index 3b681a9ff28..2c5d91a45bc 100644 --- a/spec/javascripts/settings_panels_spec.js +++ b/spec/javascripts/settings_panels_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import initSettingsPanels from '~/settings_panels'; describe('Settings Panels', () => { - preloadFixtures('groups/edit.html.raw'); + preloadFixtures('groups/edit.html'); beforeEach(() => { - loadFixtures('groups/edit.html.raw'); + loadFixtures('groups/edit.html'); }); describe('initSettingsPane', () => { diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js index 3ca6ecaa938..df7012bb659 100644 --- a/spec/javascripts/shortcuts_spec.js +++ b/spec/javascripts/shortcuts_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Shortcuts from '~/behaviors/shortcuts/shortcuts'; describe('Shortcuts', () => { - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; const createEvent = (type, target) => $.Event(type, { target, diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js index 3f0f67d71ca..016f5e033a5 100644 --- a/spec/javascripts/sidebar/sidebar_assignees_spec.js +++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js @@ -11,12 +11,12 @@ describe('sidebar assignees', () => { let vm; let mediator; let sidebarAssigneesEl; - preloadFixtures('issues/open-issue.html.raw'); + preloadFixtures('issues/open-issue.html'); beforeEach(() => { Vue.http.interceptors.push(Mock.sidebarMockInterceptor); - loadFixtures('issues/open-issue.html.raw'); + loadFixtures('issues/open-issue.html'); mediator = new SidebarMediator(Mock.mediator); spyOn(mediator, 'saveAssignees').and.callThrough(); diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js index 52da6a79939..ef5c774736b 100644 --- a/spec/javascripts/signin_tabs_memoizer_spec.js +++ b/spec/javascripts/signin_tabs_memoizer_spec.js @@ -2,7 +2,7 @@ import AccessorUtilities from '~/lib/utils/accessor'; import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; describe('SigninTabsMemoizer', () => { - const fixtureTemplate = 'static/signin_tabs.html.raw'; + const fixtureTemplate = 'static/signin_tabs.html'; const tabSelector = 'ul.new-session-tabs'; const currentTabKey = 'current_signin_tab'; let memo; diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js index 69e43274250..802f54f6a7e 100644 --- a/spec/javascripts/todos_spec.js +++ b/spec/javascripts/todos_spec.js @@ -3,11 +3,11 @@ import Todos from '~/pages/dashboard/todos/index/todos'; import '~/lib/utils/common_utils'; describe('Todos', () => { - preloadFixtures('todos/todos.html.raw'); + preloadFixtures('todos/todos.html'); let todoItem; beforeEach(() => { - loadFixtures('todos/todos.html.raw'); + loadFixtures('todos/todos.html'); todoItem = document.querySelector('.todos-list .todo'); return new Todos(); diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index ddb09811dda..8f9cb270729 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FAuthenticate', function() { - preloadFixtures('u2f/authenticate.html.raw'); + preloadFixtures('u2f/authenticate.html'); beforeEach(() => { - loadFixtures('u2f/authenticate.html.raw'); + loadFixtures('u2f/authenticate.html'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-authenticate-u2f'); this.component = new U2FAuthenticate( diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index 261db3d66d7..a75ceca9f4c 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FRegister', function() { - preloadFixtures('u2f/register.html.raw'); + preloadFixtures('u2f/register.html'); beforeEach(done => { - loadFixtures('u2f/register.html.raw'); + loadFixtures('u2f/register.html'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-register-u2f'); this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token'); diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js index b174a51c1a0..c0d5ee9c446 100644 --- a/spec/javascripts/user_popovers_spec.js +++ b/spec/javascripts/user_popovers_spec.js @@ -2,7 +2,7 @@ import initUserPopovers from '~/user_popovers'; import UsersCache from '~/lib/utils/users_cache'; describe('User Popovers', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + const fixtureTemplate = 'merge_requests/diff_comment.html'; preloadFixtures(fixtureTemplate); const selector = '.js-user-link'; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 30659ad16f3..368c997d318 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -415,7 +415,7 @@ describe('ReadyToMerge', () => { }); beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + loadFixtures('merge_requests/merge_request_of_current_user.html'); }); it('should call start and stop polling when MR merged', done => { diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js index e8b41e8eeff..852558a83bc 100644 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js @@ -17,7 +17,7 @@ const DEFAULT_PROPS = { const UserPopover = Vue.extend(userPopover); describe('User Popover Component', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + const fixtureTemplate = 'merge_requests/diff_comment.html'; preloadFixtures(fixtureTemplate); let vm; diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index e5f1e6ae937..8f662c71c7a 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -6,7 +6,7 @@ import ZenMode from '~/zen_mode'; describe('ZenMode', () => { let zen; let dropzoneForElementSpy; - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; preloadFixtures(fixtureName); diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index cceb179d53e..9cae8f934db 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -24,7 +24,7 @@ module JavaScriptFixturesHelpers # def clean_frontend_fixtures(directory_name) full_directory_name = File.expand_path(directory_name, fixture_root_path) - Dir[File.expand_path('*.html.raw', full_directory_name)].each do |file_name| + Dir[File.expand_path('*.html', full_directory_name)].each do |file_name| FileUtils.rm(file_name) end end -- cgit v1.2.1 From 30a4e5532dcf7a866319086a4e690a54e16e587f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 19 Mar 2019 18:24:16 +0100 Subject: [CE] Reduce differences with EE in group_projects_finder_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/finders/group_projects_finder_spec.rb | 39 +++++++++++----------- .../group_projects_finder_shared_contexts.rb | 24 +++++++++++++ 2 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb (limited to 'spec') diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index d6d95906f5e..f8fcc2d0e40 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -1,26 +1,7 @@ require 'spec_helper' describe GroupProjectsFinder do - let(:group) { create(:group) } - let(:subgroup) { create(:group, parent: group) } - let(:current_user) { create(:user) } - let(:options) { {} } - - let(:finder) { described_class.new(group: group, current_user: current_user, options: options) } - - let!(:public_project) { create(:project, :public, group: group, path: '1') } - let!(:private_project) { create(:project, :private, group: group, path: '2') } - let!(:shared_project_1) { create(:project, :public, path: '3') } - let!(:shared_project_2) { create(:project, :private, path: '4') } - let!(:shared_project_3) { create(:project, :internal, path: '5') } - let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) } - let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) } - - before do - shared_project_1.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) - shared_project_2.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) - shared_project_3.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) - end + include_context 'GroupProjectsFinder context' subject { finder.execute } @@ -144,6 +125,24 @@ describe GroupProjectsFinder do end end + describe 'with an admin current user' do + let(:current_user) { create(:admin) } + + context "only shared" do + let(:options) { { only_shared: true } } + it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) } + end + + context "only owned" do + let(:options) { { only_owned: true } } + it { is_expected.to eq([private_project, public_project]) } + end + + context "all" do + it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) } + end + end + describe "no user" do context "only shared" do let(:options) { { only_shared: true } } diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb new file mode 100644 index 00000000000..a0d994c4d8d --- /dev/null +++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +RSpec.shared_context 'GroupProjectsFinder context' do + let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + let(:current_user) { create(:user) } + let(:options) { {} } + + let(:finder) { described_class.new(group: group, current_user: current_user, options: options) } + + let!(:public_project) { create(:project, :public, group: group, path: '1') } + let!(:private_project) { create(:project, :private, group: group, path: '2') } + let!(:shared_project_1) { create(:project, :public, path: '3') } + let!(:shared_project_2) { create(:project, :private, path: '4') } + let!(:shared_project_3) { create(:project, :internal, path: '5') } + let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) } + let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) } + + before do + shared_project_1.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) + shared_project_2.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) + shared_project_3.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group) + end +end -- cgit v1.2.1 From 2f381e5e4ca3223796a562e65db38caac9c40ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 19 Mar 2019 18:25:10 +0100 Subject: [CE] Reduce differences with EE in issues_finder_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/finders/issues_finder_spec.rb | 80 ++++++++++++---------- .../finders/issues_finder_shared_contexts.rb | 44 ++++++++++++ 2 files changed, 87 insertions(+), 37 deletions(-) create mode 100644 spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb (limited to 'spec') diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index f74eb1364a6..00b6cad1a66 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -1,45 +1,10 @@ require 'spec_helper' describe IssuesFinder do - set(:user) { create(:user) } - set(:user2) { create(:user) } - set(:group) { create(:group) } - set(:subgroup) { create(:group, parent: group) } - set(:project1) { create(:project, group: group) } - set(:project2) { create(:project) } - set(:project3) { create(:project, group: subgroup) } - set(:milestone) { create(:milestone, project: project1) } - set(:label) { create(:label, project: project2) } - set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) } - set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) } - set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) } - set(:issue4) { create(:issue, project: project3) } - set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) } - set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) } - set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) } + include_context 'IssuesFinder context' describe '#execute' do - let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') } - let!(:label_link) { create(:label_link, label: label, target: issue2) } - let(:search_user) { user } - let(:params) { {} } - let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute } - - before(:context) do - project1.add_maintainer(user) - project2.add_developer(user) - project2.add_developer(user2) - project3.add_developer(user) - - issue1 - issue2 - issue3 - issue4 - - award_emoji1 - award_emoji2 - award_emoji3 - end + include_context 'IssuesFinder#execute context' context 'scope: all' do let(:scope) { 'all' } @@ -56,6 +21,21 @@ describe IssuesFinder do end end + context 'filtering by assignee usernames' do + set(:user3) { create(:user) } + let(:params) { { assignee_username: [user2.username, user3.username] } } + + before do + project2.add_developer(user3) + + issue3.assignees = [user2, user3] + end + + it 'returns issues assigned to those users' do + expect(issues).to contain_exactly(issue3) + end + end + context 'filtering by no assignee' do let(:params) { { assignee_id: 'None' } } @@ -643,6 +623,16 @@ describe IssuesFinder do expect(subject).to include(public_issue, confidential_issue) end end + + context 'for an admin' do + let(:admin_user) { create(:user, :admin) } + + subject { described_class.new(admin_user, params).with_confidentiality_access_check } + + it 'returns all issues' do + expect(subject).to include(public_issue, confidential_issue) + end + end end context 'when searching within a specific project' do @@ -710,6 +700,22 @@ describe IssuesFinder do subject end end + + context 'for an admin' do + let(:admin_user) { create(:user, :admin) } + + subject { described_class.new(admin_user, params).with_confidentiality_access_check } + + it 'returns all issues' do + expect(subject).to include(public_issue, confidential_issue) + end + + it 'does not filter by confidentiality' do + expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything) + + subject + end + end end end diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb new file mode 100644 index 00000000000..b8a9554f55f --- /dev/null +++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +RSpec.shared_context 'IssuesFinder context' do + set(:user) { create(:user) } + set(:user2) { create(:user) } + set(:group) { create(:group) } + set(:subgroup) { create(:group, parent: group) } + set(:project1) { create(:project, group: group) } + set(:project2) { create(:project) } + set(:project3) { create(:project, group: subgroup) } + set(:milestone) { create(:milestone, project: project1) } + set(:label) { create(:label, project: project2) } + set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) } + set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) } + set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) } + set(:issue4) { create(:issue, project: project3) } + set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) } + set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) } + set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) } +end + +RSpec.shared_context 'IssuesFinder#execute context' do + let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') } + let!(:label_link) { create(:label_link, label: label, target: issue2) } + let(:search_user) { user } + let(:params) { {} } + let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute } + + before(:context) do + project1.add_maintainer(user) + project2.add_developer(user) + project2.add_developer(user2) + project3.add_developer(user) + + issue1 + issue2 + issue3 + issue4 + + award_emoji1 + award_emoji2 + award_emoji3 + end +end -- cgit v1.2.1 From e9aefa112bcdc9b81135977356a64b94119eb383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 20 Mar 2019 10:26:04 +0100 Subject: [CE] Reduce differences with EE in merge_requests_finder_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/finders/merge_requests_finder_spec.rb | 137 +++++++-------------- .../merge_requests_finder_shared_contexts.rb | 65 ++++++++++ 2 files changed, 109 insertions(+), 93 deletions(-) create mode 100644 spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb (limited to 'spec') diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index f1178b07eec..5ddf5c14c83 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -1,72 +1,24 @@ require 'spec_helper' describe MergeRequestsFinder do - include ProjectForksHelper - - # We need to explicitly permit Gitaly N+1s because of the specs that use - # :request_store. Gitaly N+1 detection is only enabled when :request_store is, - # but we don't care about potential N+1s when we're just creating several - # projects in the setup phase. - def create_project_without_n_plus_1(*args) - Gitlab::GitalyClient.allow_n_plus_1_calls do - create(:project, :public, *args) - end - end - context "multiple projects with merge requests" do - let(:user) { create :user } - let(:user2) { create :user } - - let(:group) { create(:group) } - let(:subgroup) { create(:group, parent: group) } - let(:project1) { create_project_without_n_plus_1(group: group) } - let(:project2) do - Gitlab::GitalyClient.allow_n_plus_1_calls do - fork_project(project1, user) - end - end - let(:project3) do - Gitlab::GitalyClient.allow_n_plus_1_calls do - p = fork_project(project1, user) - p.update!(archived: true) - p - end - end - let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) } - let(:project5) { create_project_without_n_plus_1(group: subgroup) } - let(:project6) { create_project_without_n_plus_1(group: subgroup) } - - let!(:merge_request1) { create(:merge_request, author: user, source_project: project2, target_project: project1, target_branch: 'merged-target') } - let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } - let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } - let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') } - let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') } - let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') } - let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') } - let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') } - let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') } - - before do - project1.add_maintainer(user) - project2.add_developer(user) - project3.add_developer(user) - project2.add_developer(user2) - project4.add_developer(user) - project5.add_developer(user) - project6.add_developer(user) - end + include_context 'MergeRequestsFinder multiple projects with merge requests context' describe '#execute' do it 'filters by scope' do params = { scope: 'authored', state: 'opened' } + merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(7) + + expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5) end it 'filters by project' do params = { project_id: project1.id, scope: 'authored', state: 'opened' } + merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(2) + + expect(merge_requests).to contain_exactly(merge_request1) end it 'filters by commit sha' do @@ -79,24 +31,15 @@ describe MergeRequestsFinder do end context 'filtering by group' do - it 'includes all merge requests when user has access' do - params = { group_id: group.id } - - merge_requests = described_class.new(user, params).execute - - expect(merge_requests.size).to eq(3) - end - - it 'excludes merge requests from projects the user does not have access to' do - private_project = create_project_without_n_plus_1(:private, group: group) + it 'includes all merge requests when user has access exceluding merge requests from projects the user does not have access to' do + private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) } + private_project.add_guest(user) private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) params = { group_id: group.id } - private_project.add_guest(user) merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(3) - expect(merge_requests).not_to include(private_mr) + expect(merge_requests).to contain_exactly(merge_request1, merge_request2) end it 'filters by group including subgroups', :nested_groups do @@ -104,14 +47,16 @@ describe MergeRequestsFinder do merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(6) + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request5) end end it 'filters by non_archived' do params = { non_archived: true } + merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(8) + + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request5) end it 'filters by iid' do @@ -146,41 +91,45 @@ describe MergeRequestsFinder do expect(merge_requests).to contain_exactly(merge_request3) end - it 'filters by wip' do - params = { wip: 'yes' } + describe 'WIP state' do + let!(:wip_merge_request1) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') } + let!(:wip_merge_request2) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') } + let!(:wip_merge_request3) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') } + let!(:wip_merge_request4) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') } - merge_requests = described_class.new(user, params).execute + it 'filters by wip' do + params = { wip: 'yes' } - expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) - end + merge_requests = described_class.new(user, params).execute - it 'filters by not wip' do - params = { wip: 'no' } + expect(merge_requests).to contain_exactly(merge_request4, merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3, wip_merge_request4) + end - merge_requests = described_class.new(user, params).execute + it 'filters by not wip' do + params = { wip: 'no' } - expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) - end + merge_requests = described_class.new(user, params).execute - it 'returns all items if no valid wip param exists' do - params = { wip: '' } + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) + end - merge_requests = described_class.new(user, params).execute + it 'returns all items if no valid wip param exists' do + params = { wip: '' } - expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) - end + merge_requests = described_class.new(user, params).execute - it 'adds wip to scalar params' do - scalar_params = described_class.scalar_params + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3, wip_merge_request4) + end + + it 'adds wip to scalar params' do + scalar_params = described_class.scalar_params - expect(scalar_params).to include(:wip, :assignee_id) + expect(scalar_params).to include(:wip, :assignee_id) + end end context 'filtering by group milestone' do - let!(:group) { create(:group, :public) } let(:group_milestone) { create(:milestone, group: group) } - let!(:group_member) { create(:group_member, group: group, user: user) } - let(:params) { { milestone_title: group_milestone.title } } before do project2.update(namespace: group) @@ -188,7 +137,9 @@ describe MergeRequestsFinder do merge_request3.update(milestone: group_milestone) end - it 'returns issues assigned to that group milestone' do + it 'returns merge requests assigned to that group milestone' do + params = { milestone_title: group_milestone.title } + merge_requests = described_class.new(user, params).execute expect(merge_requests).to contain_exactly(merge_request2, merge_request3) @@ -285,7 +236,7 @@ describe MergeRequestsFinder do it 'returns the number of rows for the default state' do finder = described_class.new(user) - expect(finder.row_count).to eq(7) + expect(finder.row_count).to eq(3) end it 'returns the number of rows for a given state' do diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb new file mode 100644 index 00000000000..4df80b4168a --- /dev/null +++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests context' do + include ProjectForksHelper + + # We need to explicitly permit Gitaly N+1s because of the specs that use + # :request_store. Gitaly N+1 detection is only enabled when :request_store is, + # but we don't care about potential N+1s when we're just creating several + # projects in the setup phase. + def allow_gitaly_n_plus_1 + Gitlab::GitalyClient.allow_n_plus_1_calls do + yield + end + end + + set(:user) { create(:user) } + set(:user2) { create(:user) } + + set(:group) { create(:group) } + set(:subgroup) { create(:group, parent: group) } + set(:project1) do + allow_gitaly_n_plus_1 { create(:project, :public, group: group) } + end + # We cannot use `set` here otherwise we get: + # Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + # The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported. + let(:project2) do + allow_gitaly_n_plus_1 do + fork_project(project1, user) + end + end + let(:project3) do + allow_gitaly_n_plus_1 do + fork_project(project1, user).tap do |project| + project.update!(archived: true) + end + end + end + set(:project4) do + allow_gitaly_n_plus_1 { create(:project, :repository, group: subgroup) } + end + set(:project5) do + allow_gitaly_n_plus_1 { create(:project, group: subgroup) } + end + set(:project6) do + allow_gitaly_n_plus_1 { create(:project, group: subgroup) } + end + + let!(:merge_request1) { create(:merge_request, author: user, source_project: project2, target_project: project1, target_branch: 'merged-target') } + let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } + let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } + let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') } + let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') } + + before do + project1.add_maintainer(user) + project2.add_developer(user) + project3.add_developer(user) + project4.add_developer(user) + project5.add_developer(user) + project6.add_developer(user) + + project2.add_developer(user2) + end +end -- cgit v1.2.1 From 9e171a530fe885eca784ed6c5a602ab796a5790c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 20 Mar 2019 10:30:06 +0100 Subject: [CE] Reduce differences with EE in snippets_finder_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/finders/snippets_finder_spec.rb | 221 ++++++-------- spec/support/shared_examples/snippet_visibility.rb | 322 --------------------- .../snippet_visibility_shared_examples.rb | 322 +++++++++++++++++++++ 3 files changed, 416 insertions(+), 449 deletions(-) delete mode 100644 spec/support/shared_examples/snippet_visibility.rb create mode 100644 spec/support/shared_examples/snippet_visibility_shared_examples.rb (limited to 'spec') diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 134fb5f2c04..93287f3e9b8 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe SnippetsFinder do include Gitlab::Allowable - using RSpec::Parameterized::TableSyntax describe '#initialize' do it 'raises ArgumentError when a project and author are given' do @@ -14,174 +13,142 @@ describe SnippetsFinder do end end - context 'filter by scope' do - let(:user) { create :user } - let!(:snippet1) { create(:personal_snippet, :private, author: user) } - let!(:snippet2) { create(:personal_snippet, :internal, author: user) } - let!(:snippet3) { create(:personal_snippet, :public, author: user) } - - it "returns all snippets for 'all' scope" do - snippets = described_class.new(user, scope: :all).execute - - expect(snippets).to include(snippet1, snippet2, snippet3) - end - - it "returns all snippets for 'are_private' scope" do - snippets = described_class.new(user, scope: :are_private).execute + describe '#execute' do + set(:user) { create(:user) } + set(:private_personal_snippet) { create(:personal_snippet, :private, author: user) } + set(:internal_personal_snippet) { create(:personal_snippet, :internal, author: user) } + set(:public_personal_snippet) { create(:personal_snippet, :public, author: user) } - expect(snippets).to include(snippet1) - expect(snippets).not_to include(snippet2, snippet3) - end + context 'filter by scope' do + it "returns all snippets for 'all' scope" do + snippets = described_class.new(user, scope: :all).execute - it "returns all snippets for 'are_internal' scope" do - snippets = described_class.new(user, scope: :are_internal).execute + expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet) + end - expect(snippets).to include(snippet2) - expect(snippets).not_to include(snippet1, snippet3) - end + it "returns all snippets for 'are_private' scope" do + snippets = described_class.new(user, scope: :are_private).execute - it "returns all snippets for 'are_private' scope" do - snippets = described_class.new(user, scope: :are_public).execute + expect(snippets).to contain_exactly(private_personal_snippet) + end - expect(snippets).to include(snippet3) - expect(snippets).not_to include(snippet1, snippet2) - end - end + it "returns all snippets for 'are_internal' scope" do + snippets = described_class.new(user, scope: :are_internal).execute - context 'filter by author' do - let(:user) { create :user } - let(:user1) { create :user } - let!(:snippet1) { create(:personal_snippet, :private, author: user) } - let!(:snippet2) { create(:personal_snippet, :internal, author: user) } - let!(:snippet3) { create(:personal_snippet, :public, author: user) } + expect(snippets).to contain_exactly(internal_personal_snippet) + end - it "returns all public and internal snippets" do - snippets = described_class.new(user1, author: user).execute + it "returns all snippets for 'are_private' scope" do + snippets = described_class.new(user, scope: :are_public).execute - expect(snippets).to include(snippet2, snippet3) - expect(snippets).not_to include(snippet1) + expect(snippets).to contain_exactly(public_personal_snippet) + end end - it "returns internal snippets" do - snippets = described_class.new(user, author: user, scope: :are_internal).execute + context 'filter by author' do + it 'returns all public and internal snippets' do + snippets = described_class.new(create(:user), author: user).execute - expect(snippets).to include(snippet2) - expect(snippets).not_to include(snippet1, snippet3) - end + expect(snippets).to contain_exactly(internal_personal_snippet, public_personal_snippet) + end - it "returns private snippets" do - snippets = described_class.new(user, author: user, scope: :are_private).execute + it 'returns internal snippets' do + snippets = described_class.new(user, author: user, scope: :are_internal).execute - expect(snippets).to include(snippet1) - expect(snippets).not_to include(snippet2, snippet3) - end + expect(snippets).to contain_exactly(internal_personal_snippet) + end - it "returns public snippets" do - snippets = described_class.new(user, author: user, scope: :are_public).execute + it 'returns private snippets' do + snippets = described_class.new(user, author: user, scope: :are_private).execute - expect(snippets).to include(snippet3) - expect(snippets).not_to include(snippet1, snippet2) - end + expect(snippets).to contain_exactly(private_personal_snippet) + end - it "returns all snippets" do - snippets = described_class.new(user, author: user).execute + it 'returns public snippets' do + snippets = described_class.new(user, author: user, scope: :are_public).execute - expect(snippets).to include(snippet1, snippet2, snippet3) - end + expect(snippets).to contain_exactly(public_personal_snippet) + end - it "returns only public snippets if unauthenticated user" do - snippets = described_class.new(nil, author: user).execute + it 'returns all snippets' do + snippets = described_class.new(user, author: user).execute - expect(snippets).to include(snippet3) - expect(snippets).not_to include(snippet2, snippet1) - end + expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet) + end - it 'returns all snippets for an admin' do - admin = create(:user, :admin) - snippets = described_class.new(admin, author: user).execute + it 'returns only public snippets if unauthenticated user' do + snippets = described_class.new(nil, author: user).execute - expect(snippets).to include(snippet1, snippet2, snippet3) - end - end + expect(snippets).to contain_exactly(public_personal_snippet) + end - context 'filter by project' do - let(:user) { create :user } - let(:group) { create :group, :public } - let(:project1) { create(:project, :public, group: group) } + it 'returns all snippets for an admin' do + admin = create(:user, :admin) + snippets = described_class.new(admin, author: user).execute - before do - @snippet1 = create(:project_snippet, :private, project: project1) - @snippet2 = create(:project_snippet, :internal, project: project1) - @snippet3 = create(:project_snippet, :public, project: project1) + expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet) + end end - it "returns public snippets for unauthorized user" do - snippets = described_class.new(nil, project: project1).execute + context 'project snippets' do + let(:group) { create(:group, :public) } + let(:project) { create(:project, :public, group: group) } + let!(:private_project_snippet) { create(:project_snippet, :private, project: project) } + let!(:internal_project_snippet) { create(:project_snippet, :internal, project: project) } + let!(:public_project_snippet) { create(:project_snippet, :public, project: project) } - expect(snippets).to include(@snippet3) - expect(snippets).not_to include(@snippet1, @snippet2) - end + it 'returns public personal and project snippets for unauthorized user' do + snippets = described_class.new(nil, project: project).execute - it "returns public and internal snippets for non project members" do - snippets = described_class.new(user, project: project1).execute + expect(snippets).to contain_exactly(public_project_snippet) + end - expect(snippets).to include(@snippet2, @snippet3) - expect(snippets).not_to include(@snippet1) - end + it 'returns public and internal snippets for non project members' do + snippets = described_class.new(user, project: project).execute - it "returns public snippets for non project members" do - snippets = described_class.new(user, project: project1, scope: :are_public).execute + expect(snippets).to contain_exactly(internal_project_snippet, public_project_snippet) + end - expect(snippets).to include(@snippet3) - expect(snippets).not_to include(@snippet1, @snippet2) - end + it 'returns public snippets for non project members' do + snippets = described_class.new(user, project: project, scope: :are_public).execute - it "returns internal snippets for non project members" do - snippets = described_class.new(user, project: project1, scope: :are_internal).execute + expect(snippets).to contain_exactly(public_project_snippet) + end - expect(snippets).to include(@snippet2) - expect(snippets).not_to include(@snippet1, @snippet3) - end + it 'returns internal snippets for non project members' do + snippets = described_class.new(user, project: project, scope: :are_internal).execute - it "does not return private snippets for non project members" do - snippets = described_class.new(user, project: project1, scope: :are_private).execute + expect(snippets).to contain_exactly(internal_project_snippet) + end - expect(snippets).not_to include(@snippet1, @snippet2, @snippet3) - end + it 'does not return private snippets for non project members' do + snippets = described_class.new(user, project: project, scope: :are_private).execute - it "returns all snippets for project members" do - project1.add_developer(user) + expect(snippets).to be_empty + end - snippets = described_class.new(user, project: project1).execute + it 'returns all snippets for project members' do + project.add_developer(user) - expect(snippets).to include(@snippet1, @snippet2, @snippet3) - end + snippets = described_class.new(user, project: project).execute - it "returns private snippets for project members" do - project1.add_developer(user) + expect(snippets).to contain_exactly(private_project_snippet, internal_project_snippet, public_project_snippet) + end - snippets = described_class.new(user, project: project1, scope: :are_private).execute + it 'returns private snippets for project members' do + project.add_developer(user) - expect(snippets).to include(@snippet1) - end + snippets = described_class.new(user, project: project, scope: :are_private).execute - it 'returns all snippets for an admin' do - admin = create(:user, :admin) - snippets = described_class.new(admin, project: project1).execute + expect(snippets).to contain_exactly(private_project_snippet) + end - expect(snippets).to include(@snippet1, @snippet2, @snippet3) - end - end + it 'returns all snippets for an admin' do + admin = create(:user, :admin) + snippets = described_class.new(admin, project: project).execute - describe '#execute' do - let(:project) { create(:project, :public) } - let!(:project_snippet) { create(:project_snippet, :public, project: project) } - let!(:personal_snippet) { create(:personal_snippet, :public) } - let(:user) { create(:user) } - subject(:finder) { described_class.new(user) } - - it 'returns project- and personal snippets' do - expect(finder.execute).to contain_exactly(project_snippet, personal_snippet) + expect(snippets).to contain_exactly(private_project_snippet, internal_project_snippet, public_project_snippet) + end end context 'when the user cannot read cross project' do @@ -191,7 +158,7 @@ describe SnippetsFinder do end it 'returns only personal snippets when the user cannot read cross project' do - expect(finder.execute).to contain_exactly(personal_snippet) + expect(described_class.new(user).execute).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet) end end end diff --git a/spec/support/shared_examples/snippet_visibility.rb b/spec/support/shared_examples/snippet_visibility.rb deleted file mode 100644 index 3a7c69b7877..00000000000 --- a/spec/support/shared_examples/snippet_visibility.rb +++ /dev/null @@ -1,322 +0,0 @@ -RSpec.shared_examples 'snippet visibility' do - let!(:author) { create(:user) } - let!(:member) { create(:user) } - let!(:external) { create(:user, :external) } - - let!(:snippet_type_visibilities) do - { - public: Snippet::PUBLIC, - internal: Snippet::INTERNAL, - private: Snippet::PRIVATE - } - end - - context "For project snippets" do - let!(:users) do - { - unauthenticated: nil, - external: external, - non_member: create(:user), - member: member, - author: author - } - end - - let!(:project_type_visibilities) do - { - public: Gitlab::VisibilityLevel::PUBLIC, - internal: Gitlab::VisibilityLevel::INTERNAL, - private: Gitlab::VisibilityLevel::PRIVATE - } - end - - let(:project_feature_visibilities) do - { - enabled: ProjectFeature::ENABLED, - private: ProjectFeature::PRIVATE, - disabled: ProjectFeature::DISABLED - } - end - - where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do - [ - # Public projects - [:public, :enabled, :unauthenticated, :public, true], - [:public, :enabled, :unauthenticated, :internal, false], - [:public, :enabled, :unauthenticated, :private, false], - - [:public, :enabled, :external, :public, true], - [:public, :enabled, :external, :internal, false], - [:public, :enabled, :external, :private, false], - - [:public, :enabled, :non_member, :public, true], - [:public, :enabled, :non_member, :internal, true], - [:public, :enabled, :non_member, :private, false], - - [:public, :enabled, :member, :public, true], - [:public, :enabled, :member, :internal, true], - [:public, :enabled, :member, :private, true], - - [:public, :enabled, :author, :public, true], - [:public, :enabled, :author, :internal, true], - [:public, :enabled, :author, :private, true], - - [:public, :private, :unauthenticated, :public, false], - [:public, :private, :unauthenticated, :internal, false], - [:public, :private, :unauthenticated, :private, false], - - [:public, :private, :external, :public, false], - [:public, :private, :external, :internal, false], - [:public, :private, :external, :private, false], - - [:public, :private, :non_member, :public, false], - [:public, :private, :non_member, :internal, false], - [:public, :private, :non_member, :private, false], - - [:public, :private, :member, :public, true], - [:public, :private, :member, :internal, true], - [:public, :private, :member, :private, true], - - [:public, :private, :author, :public, true], - [:public, :private, :author, :internal, true], - [:public, :private, :author, :private, true], - - [:public, :disabled, :unauthenticated, :public, false], - [:public, :disabled, :unauthenticated, :internal, false], - [:public, :disabled, :unauthenticated, :private, false], - - [:public, :disabled, :external, :public, false], - [:public, :disabled, :external, :internal, false], - [:public, :disabled, :external, :private, false], - - [:public, :disabled, :non_member, :public, false], - [:public, :disabled, :non_member, :internal, false], - [:public, :disabled, :non_member, :private, false], - - [:public, :disabled, :member, :public, false], - [:public, :disabled, :member, :internal, false], - [:public, :disabled, :member, :private, false], - - [:public, :disabled, :author, :public, false], - [:public, :disabled, :author, :internal, false], - [:public, :disabled, :author, :private, false], - - # Internal projects - [:internal, :enabled, :unauthenticated, :public, false], - [:internal, :enabled, :unauthenticated, :internal, false], - [:internal, :enabled, :unauthenticated, :private, false], - - [:internal, :enabled, :external, :public, false], - [:internal, :enabled, :external, :internal, false], - [:internal, :enabled, :external, :private, false], - - [:internal, :enabled, :non_member, :public, true], - [:internal, :enabled, :non_member, :internal, true], - [:internal, :enabled, :non_member, :private, false], - - [:internal, :enabled, :member, :public, true], - [:internal, :enabled, :member, :internal, true], - [:internal, :enabled, :member, :private, true], - - [:internal, :enabled, :author, :public, true], - [:internal, :enabled, :author, :internal, true], - [:internal, :enabled, :author, :private, true], - - [:internal, :private, :unauthenticated, :public, false], - [:internal, :private, :unauthenticated, :internal, false], - [:internal, :private, :unauthenticated, :private, false], - - [:internal, :private, :external, :public, false], - [:internal, :private, :external, :internal, false], - [:internal, :private, :external, :private, false], - - [:internal, :private, :non_member, :public, false], - [:internal, :private, :non_member, :internal, false], - [:internal, :private, :non_member, :private, false], - - [:internal, :private, :member, :public, true], - [:internal, :private, :member, :internal, true], - [:internal, :private, :member, :private, true], - - [:internal, :private, :author, :public, true], - [:internal, :private, :author, :internal, true], - [:internal, :private, :author, :private, true], - - [:internal, :disabled, :unauthenticated, :public, false], - [:internal, :disabled, :unauthenticated, :internal, false], - [:internal, :disabled, :unauthenticated, :private, false], - - [:internal, :disabled, :external, :public, false], - [:internal, :disabled, :external, :internal, false], - [:internal, :disabled, :external, :private, false], - - [:internal, :disabled, :non_member, :public, false], - [:internal, :disabled, :non_member, :internal, false], - [:internal, :disabled, :non_member, :private, false], - - [:internal, :disabled, :member, :public, false], - [:internal, :disabled, :member, :internal, false], - [:internal, :disabled, :member, :private, false], - - [:internal, :disabled, :author, :public, false], - [:internal, :disabled, :author, :internal, false], - [:internal, :disabled, :author, :private, false], - - # Private projects - [:private, :enabled, :unauthenticated, :public, false], - [:private, :enabled, :unauthenticated, :internal, false], - [:private, :enabled, :unauthenticated, :private, false], - - [:private, :enabled, :external, :public, true], - [:private, :enabled, :external, :internal, true], - [:private, :enabled, :external, :private, true], - - [:private, :enabled, :non_member, :public, false], - [:private, :enabled, :non_member, :internal, false], - [:private, :enabled, :non_member, :private, false], - - [:private, :enabled, :member, :public, true], - [:private, :enabled, :member, :internal, true], - [:private, :enabled, :member, :private, true], - - [:private, :enabled, :author, :public, true], - [:private, :enabled, :author, :internal, true], - [:private, :enabled, :author, :private, true], - - [:private, :private, :unauthenticated, :public, false], - [:private, :private, :unauthenticated, :internal, false], - [:private, :private, :unauthenticated, :private, false], - - [:private, :private, :external, :public, true], - [:private, :private, :external, :internal, true], - [:private, :private, :external, :private, true], - - [:private, :private, :non_member, :public, false], - [:private, :private, :non_member, :internal, false], - [:private, :private, :non_member, :private, false], - - [:private, :private, :member, :public, true], - [:private, :private, :member, :internal, true], - [:private, :private, :member, :private, true], - - [:private, :private, :author, :public, true], - [:private, :private, :author, :internal, true], - [:private, :private, :author, :private, true], - - [:private, :disabled, :unauthenticated, :public, false], - [:private, :disabled, :unauthenticated, :internal, false], - [:private, :disabled, :unauthenticated, :private, false], - - [:private, :disabled, :external, :public, false], - [:private, :disabled, :external, :internal, false], - [:private, :disabled, :external, :private, false], - - [:private, :disabled, :non_member, :public, false], - [:private, :disabled, :non_member, :internal, false], - [:private, :disabled, :non_member, :private, false], - - [:private, :disabled, :member, :public, false], - [:private, :disabled, :member, :internal, false], - [:private, :disabled, :member, :private, false], - - [:private, :disabled, :author, :public, false], - [:private, :disabled, :author, :internal, false], - [:private, :disabled, :author, :private, false] - ] - end - - with_them do - let!(:project) { create(:project, visibility_level: project_type_visibilities[project_type]) } - let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, project_feature_visibilities[feature_visibility]) } - let!(:user) { users[user_type] } - let!(:snippet) { create(:project_snippet, visibility_level: snippet_type_visibilities[snippet_type], project: project, author: author) } - let!(:members) do - project.add_developer(author) - project.add_developer(member) - project.add_developer(external) if project.private? - end - - context "For #{params[:project_type]} project and #{params[:user_type]} users" do - it 'should agree with the read_project_snippet policy' do - expect(can?(user, :read_project_snippet, snippet)).to eq(outcome) - end - - it 'should return proper outcome' do - results = described_class.new(user, project: project).execute - expect(results.include?(snippet)).to eq(outcome) - end - end - - context "Without a given project and #{params[:user_type]} users" do - it 'should return proper outcome' do - results = described_class.new(user).execute - expect(results.include?(snippet)).to eq(outcome) - end - - it 'returns no snippets when the user cannot read cross project' do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } - - snippets = described_class.new(user).execute - - expect(snippets).to be_empty - end - end - end - end - - context 'For personal snippets' do - let!(:users) do - { - unauthenticated: nil, - external: external, - non_member: create(:user), - author: author - } - end - - where(:snippet_visibility, :user_type, :outcome) do - [ - [:public, :unauthenticated, true], - [:public, :external, true], - [:public, :non_member, true], - [:public, :author, true], - - [:internal, :unauthenticated, false], - [:internal, :external, false], - [:internal, :non_member, true], - [:internal, :author, true], - - [:private, :unauthenticated, false], - [:private, :external, false], - [:private, :non_member, false], - [:private, :author, true] - ] - end - - with_them do - let!(:user) { users[user_type] } - let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) } - - context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do - it 'should agree with read_personal_snippet policy' do - expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome) - end - - it 'should return proper outcome' do - results = described_class.new(user).execute - expect(results.include?(snippet)).to eq(outcome) - end - - it 'should return personal snippets when the user cannot read cross project' do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } - - results = described_class.new(user).execute - - expect(results.include?(snippet)).to eq(outcome) - end - end - end - end -end diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb new file mode 100644 index 00000000000..f622876d7a5 --- /dev/null +++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb @@ -0,0 +1,322 @@ +RSpec.shared_examples 'snippet visibility' do + using RSpec::Parameterized::TableSyntax + + # Make sure no snippets exist prior to running the test matrix + before(:context) do + DatabaseCleaner.clean_with(:truncation) + end + + set(:author) { create(:user) } + set(:member) { create(:user) } + set(:external) { create(:user, :external) } + + let!(:snippet_type_visibilities) do + { + public: Snippet::PUBLIC, + internal: Snippet::INTERNAL, + private: Snippet::PRIVATE + } + end + + context "For project snippets" do + let!(:users) do + { + unauthenticated: nil, + external: external, + non_member: create(:user), + member: member, + author: author + } + end + + let(:project_feature_visibilities) do + { + enabled: ProjectFeature::ENABLED, + private: ProjectFeature::PRIVATE, + disabled: ProjectFeature::DISABLED + } + end + + where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do + [ + # Public projects + [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, true], + [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false], + [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false], + + [:public, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, true], + [:public, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, false], + [:public, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, false], + + [:public, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, true], + [:public, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, true], + [:public, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false], + + [:public, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true], + [:public, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true], + [:public, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true], + + [:public, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true], + [:public, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true], + [:public, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true], + + [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false], + [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false], + [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false], + + [:public, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, false], + [:public, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, false], + [:public, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, false], + + [:public, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false], + [:public, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false], + [:public, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false], + + [:public, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true], + [:public, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true], + [:public, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true], + + [:public, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true], + [:public, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true], + [:public, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true], + + [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false], + [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false], + [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false], + + [:public, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false], + [:public, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false], + [:public, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false], + + [:public, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false], + [:public, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false], + [:public, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false], + + [:public, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false], + [:public, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false], + [:public, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false], + + [:public, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false], + [:public, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false], + [:public, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false], + + # Internal projects + [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, false], + [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false], + [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, false], + [:internal, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, false], + [:internal, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, true], + [:internal, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, true], + [:internal, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true], + [:internal, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true], + [:internal, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true], + + [:internal, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true], + [:internal, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true], + [:internal, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true], + + [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false], + [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false], + [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, false], + [:internal, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, false], + [:internal, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false], + [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false], + [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true], + [:internal, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true], + [:internal, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true], + + [:internal, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true], + [:internal, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true], + [:internal, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true], + + [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false], + [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false], + [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false], + [:internal, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false], + [:internal, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false], + [:internal, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false], + [:internal, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false], + [:internal, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false], + [:internal, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false], + + [:internal, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false], + [:internal, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false], + [:internal, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false], + + # Private projects + [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, false], + [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false], + [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false], + + [:private, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, true], + [:private, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, true], + [:private, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, true], + + [:private, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, false], + [:private, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, false], + [:private, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false], + + [:private, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true], + [:private, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true], + [:private, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true], + + [:private, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true], + [:private, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true], + [:private, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true], + + [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false], + [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false], + [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false], + + [:private, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, true], + [:private, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, true], + [:private, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, true], + + [:private, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false], + [:private, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false], + [:private, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false], + + [:private, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true], + [:private, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true], + [:private, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true], + + [:private, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true], + [:private, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true], + [:private, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true], + + [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false], + [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false], + [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false], + + [:private, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false], + [:private, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false], + [:private, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false], + + [:private, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false], + [:private, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false], + [:private, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false], + + [:private, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false], + [:private, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false], + [:private, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false], + + [:private, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false], + [:private, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false], + [:private, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false] + ] + end + + with_them do + let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel.level_value(project_type.to_s)) } + let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, feature_visibility) } + let!(:user) { users[user_type] } + let!(:snippet) { create(:project_snippet, visibility_level: snippet_type, project: project, author: author) } + let!(:members) do + project.add_developer(author) + project.add_developer(member) + project.add_developer(external) if project.private? + end + + context "For #{params[:project_type]} project and #{params[:user_type]} users" do + it 'should agree with the read_project_snippet policy' do + expect(can?(user, :read_project_snippet, snippet)).to eq(outcome) + end + + it 'should return proper outcome' do + results = described_class.new(user, project: project).execute + + expect(results.include?(snippet)).to eq(outcome) + end + end + + context "Without a given project and #{params[:user_type]} users" do + it 'should return proper outcome' do + results = described_class.new(user).execute + expect(results.include?(snippet)).to eq(outcome) + end + + it 'returns no snippets when the user cannot read cross project' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } + + snippets = described_class.new(user).execute + + expect(snippets).to be_empty + end + end + end + end + + context 'For personal snippets' do + let!(:users) do + { + unauthenticated: nil, + external: external, + non_member: create(:user), + author: author + } + end + + where(:snippet_visibility, :user_type, :outcome) do + [ + [:public, :unauthenticated, true], + [:public, :external, true], + [:public, :non_member, true], + [:public, :author, true], + + [:internal, :unauthenticated, false], + [:internal, :external, false], + [:internal, :non_member, true], + [:internal, :author, true], + + [:private, :unauthenticated, false], + [:private, :external, false], + [:private, :non_member, false], + [:private, :author, true] + ] + end + + with_them do + let!(:user) { users[user_type] } + let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) } + + context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do + it 'should agree with read_personal_snippet policy' do + expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome) + end + + it 'should return proper outcome' do + results = described_class.new(user).execute + expect(results.include?(snippet)).to eq(outcome) + end + + it 'should return personal snippets when the user cannot read cross project' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } + + results = described_class.new(user).execute + + expect(results.include?(snippet)).to eq(outcome) + end + end + end + end +end -- cgit v1.2.1 From 0750638f805eee7d386df5cfff23413610d4e528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 20 Mar 2019 10:33:24 +0100 Subject: [CE] Reduce differences with EE in users_finder_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/finders/merge_requests_finder_spec.rb | 2 +- spec/finders/users_finder_spec.rb | 31 +++++++-------- .../finders/users_finder_shared_contexts.rb | 8 ++++ .../snippet_visibility_shared_examples.rb | 46 +++++++--------------- 4 files changed, 38 insertions(+), 49 deletions(-) create mode 100644 spec/support/shared_contexts/finders/users_finder_shared_contexts.rb (limited to 'spec') diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 5ddf5c14c83..56136eb84bc 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -34,7 +34,7 @@ describe MergeRequestsFinder do it 'includes all merge requests when user has access exceluding merge requests from projects the user does not have access to' do private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) } private_project.add_guest(user) - private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) + create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) params = { group_id: group.id } merge_requests = described_class.new(user, params).execute diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb index fecf97dc641..d71d3c99272 100644 --- a/spec/finders/users_finder_spec.rb +++ b/spec/finders/users_finder_spec.rb @@ -2,10 +2,7 @@ require 'spec_helper' describe UsersFinder do describe '#execute' do - let!(:user1) { create(:user, username: 'johndoe') } - let!(:user2) { create(:user, :blocked, username: 'notsorandom') } - let!(:external_user) { create(:user, :external) } - let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } + include_context 'UsersFinder#execute filter by project context' context 'with a normal user' do let(:user) { create(:user) } @@ -13,43 +10,43 @@ describe UsersFinder do it 'returns all users' do users = described_class.new(user).execute - expect(users).to contain_exactly(user, user1, user2, omniauth_user) + expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user) end it 'filters by username' do users = described_class.new(user, username: 'johndoe').execute - expect(users).to contain_exactly(user1) + expect(users).to contain_exactly(normal_user) end it 'filters by username (case insensitive)' do users = described_class.new(user, username: 'joHNdoE').execute - expect(users).to contain_exactly(user1) + expect(users).to contain_exactly(normal_user) end it 'filters by search' do users = described_class.new(user, search: 'orando').execute - expect(users).to contain_exactly(user2) + expect(users).to contain_exactly(blocked_user) end it 'filters by blocked users' do users = described_class.new(user, blocked: true).execute - expect(users).to contain_exactly(user2) + expect(users).to contain_exactly(blocked_user) end it 'filters by active users' do users = described_class.new(user, active: true).execute - expect(users).to contain_exactly(user, user1, omniauth_user) + expect(users).to contain_exactly(user, normal_user, omniauth_user) end it 'returns no external users' do users = described_class.new(user, external: true).execute - expect(users).to contain_exactly(user, user1, user2, omniauth_user) + expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user) end it 'filters by created_at' do @@ -69,7 +66,7 @@ describe UsersFinder do custom_attributes: { foo: 'bar' } ).execute - expect(users).to contain_exactly(user, user1, user2, omniauth_user) + expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user) end end @@ -85,20 +82,20 @@ describe UsersFinder do it 'returns all users' do users = described_class.new(admin).execute - expect(users).to contain_exactly(admin, user1, user2, external_user, omniauth_user) + expect(users).to contain_exactly(admin, normal_user, blocked_user, external_user, omniauth_user) end it 'filters by custom attributes' do - create :user_custom_attribute, user: user1, key: 'foo', value: 'foo' - create :user_custom_attribute, user: user1, key: 'bar', value: 'bar' - create :user_custom_attribute, user: user2, key: 'foo', value: 'foo' + create :user_custom_attribute, user: normal_user, key: 'foo', value: 'foo' + create :user_custom_attribute, user: normal_user, key: 'bar', value: 'bar' + create :user_custom_attribute, user: blocked_user, key: 'foo', value: 'foo' users = described_class.new( admin, custom_attributes: { foo: 'foo', bar: 'bar' } ).execute - expect(users).to contain_exactly(user1) + expect(users).to contain_exactly(normal_user) end end end diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb new file mode 100644 index 00000000000..9e1f89ee0ed --- /dev/null +++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +RSpec.shared_context 'UsersFinder#execute filter by project context' do + set(:normal_user) { create(:user, username: 'johndoe') } + set(:blocked_user) { create(:user, :blocked, username: 'notsorandom') } + set(:external_user) { create(:user, :external) } + set(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } +end diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb index f622876d7a5..4f662db2120 100644 --- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb +++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb @@ -10,14 +10,6 @@ RSpec.shared_examples 'snippet visibility' do set(:member) { create(:user) } set(:external) { create(:user, :external) } - let!(:snippet_type_visibilities) do - { - public: Snippet::PUBLIC, - internal: Snippet::INTERNAL, - private: Snippet::PRIVATE - } - end - context "For project snippets" do let!(:users) do { @@ -29,14 +21,6 @@ RSpec.shared_examples 'snippet visibility' do } end - let(:project_feature_visibilities) do - { - enabled: ProjectFeature::ENABLED, - private: ProjectFeature::PRIVATE, - disabled: ProjectFeature::DISABLED - } - end - where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do [ # Public projects @@ -277,26 +261,26 @@ RSpec.shared_examples 'snippet visibility' do where(:snippet_visibility, :user_type, :outcome) do [ - [:public, :unauthenticated, true], - [:public, :external, true], - [:public, :non_member, true], - [:public, :author, true], - - [:internal, :unauthenticated, false], - [:internal, :external, false], - [:internal, :non_member, true], - [:internal, :author, true], - - [:private, :unauthenticated, false], - [:private, :external, false], - [:private, :non_member, false], - [:private, :author, true] + [Snippet::PUBLIC, :unauthenticated, true], + [Snippet::PUBLIC, :external, true], + [Snippet::PUBLIC, :non_member, true], + [Snippet::PUBLIC, :author, true], + + [Snippet::INTERNAL, :unauthenticated, false], + [Snippet::INTERNAL, :external, false], + [Snippet::INTERNAL, :non_member, true], + [Snippet::INTERNAL, :author, true], + + [Snippet::PRIVATE, :unauthenticated, false], + [Snippet::PRIVATE, :external, false], + [Snippet::PRIVATE, :non_member, false], + [Snippet::PRIVATE, :author, true] ] end with_them do let!(:user) { users[user_type] } - let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) } + let!(:snippet) { create(:personal_snippet, visibility_level: snippet_visibility, author: author) } context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do it 'should agree with read_personal_snippet policy' do -- cgit v1.2.1 From 202e9ae88bfcdc392f396a9aeb08936bf2f50c0e Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 21 Mar 2019 09:07:02 +0100 Subject: Add dummy test with setTimeout() --- spec/frontend/dummy_spec.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 spec/frontend/dummy_spec.js (limited to 'spec') diff --git a/spec/frontend/dummy_spec.js b/spec/frontend/dummy_spec.js new file mode 100644 index 00000000000..25dbb624ada --- /dev/null +++ b/spec/frontend/dummy_spec.js @@ -0,0 +1,7 @@ +describe('dummy test', () => { + it('waits for a loooooong time', () => { + setTimeout(() => { + throw new Error('broken'); + }, 10000); + }); +}); -- cgit v1.2.1 From d1251b42a09e0aaea6300f9dfc92377d24ea762e Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 21 Mar 2019 18:05:21 +0100 Subject: Wait for pending timers in Jest --- spec/frontend/test_setup.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'spec') diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 8c36d8ff49f..006fc60ef57 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -3,6 +3,11 @@ import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; import { initializeTestTimeout } from './helpers/timeout'; +// wait for pending setTimeout()s +afterEach(() => { + jest.runAllTimers(); +}); + initializeTestTimeout(300); // fail tests for unmocked requests -- cgit v1.2.1 From fcf64da04b868fe10e6583eeafd8f015f6b3743e Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 21 Mar 2019 18:05:59 +0100 Subject: Delete dummy test --- spec/frontend/dummy_spec.js | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 spec/frontend/dummy_spec.js (limited to 'spec') diff --git a/spec/frontend/dummy_spec.js b/spec/frontend/dummy_spec.js deleted file mode 100644 index 25dbb624ada..00000000000 --- a/spec/frontend/dummy_spec.js +++ /dev/null @@ -1,7 +0,0 @@ -describe('dummy test', () => { - it('waits for a loooooong time', () => { - setTimeout(() => { - throw new Error('broken'); - }, 10000); - }); -}); -- cgit v1.2.1 From 70c13de9b116220f46a8f571c899494585443107 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Wed, 20 Mar 2019 16:36:07 -0600 Subject: Change environments dropdown to use gitlab-ui --- spec/javascripts/monitoring/dashboard_spec.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index b1778029a77..6078a0e7872 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -98,7 +98,7 @@ describe('Dashboard', () => { }); }); - it('renders the dropdown with a number of environments', done => { + it('renders the environments dropdown with a number of environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, @@ -107,14 +107,16 @@ describe('Dashboard', () => { component.store.storeEnvironmentsData(environmentData); setTimeout(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul li a'); + const dropdownMenuEnvironments = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item', + ); expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length); done(); }); }); - it('hides the dropdown list when there is no environments', done => { + it('hides the environments dropdown list when there is no environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, @@ -123,14 +125,16 @@ describe('Dashboard', () => { component.store.storeEnvironmentsData([]); setTimeout(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul'); + const dropdownMenuEnvironments = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item', + ); expect(dropdownMenuEnvironments.length).toEqual(0); done(); }); }); - it('renders the dropdown with a single is-active element', done => { + it('renders the environments dropdown with a single is-active element', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, @@ -139,14 +143,12 @@ describe('Dashboard', () => { component.store.storeEnvironmentsData(environmentData); setTimeout(() => { - const dropdownIsActiveElement = component.$el.querySelectorAll( - '.dropdown-menu ul li a.is-active', + const dropdownItems = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item[active="true"]', ); - expect(dropdownIsActiveElement.length).toEqual(1); - expect(dropdownIsActiveElement[0].textContent.trim()).toEqual( - component.currentEnvironmentName, - ); + expect(dropdownItems.length).toEqual(1); + expect(dropdownItems[0].textContent.trim()).toEqual(component.currentEnvironmentName); done(); }); }); -- cgit v1.2.1 From 38bf176c3cf7b26233ad78103a04546445348983 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Thu, 14 Mar 2019 17:20:40 +1300 Subject: Enrich commits with full data in CommitCollection Allow incomplete commit records to load their full data from gitaly. Commits can be based on a Hash of data retrieved from PostgreSQL, and this data can be intentionally incomplete in order to save space. A new method #gitaly? has been added to Gitlab::Git::Commit, which returns true if the underlying data source of the Commit is a Gitaly::GitCommit. CommitCollection now has a method #enrich which replaces non-gitaly commits in place with commits from gitaly. CommitCollection#without_merge_commits has been updated to call this method, as in order to determine a merge commit we need to have parent data. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/58805 --- spec/lib/gitlab/git/commit_spec.rb | 12 +++ spec/models/commit_collection_spec.rb | 86 +++++++++++++++++++++- spec/models/merge_request_spec.rb | 33 ++++----- .../merge_request_widget_entity_spec.rb | 15 ++-- 4 files changed, 119 insertions(+), 27 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 3fb41a626b2..4a4ac833e39 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -537,6 +537,18 @@ describe Gitlab::Git::Commit, :seed_helper do end end + describe '#gitaly_commit?' do + context 'when the commit data comes from gitaly' do + it { expect(commit.gitaly_commit?).to eq(true) } + end + + context 'when the commit data comes from a Hash' do + let(:commit) { described_class.new(repository, sample_commit_hash) } + + it { expect(commit.gitaly_commit?).to eq(false) } + end + end + describe '#has_zero_stats?' do it { expect(commit.has_zero_stats?).to eq(false) } end diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb index 0f5d03ff458..30c504ebea8 100644 --- a/spec/models/commit_collection_spec.rb +++ b/spec/models/commit_collection_spec.rb @@ -37,12 +37,92 @@ describe CommitCollection do describe '#without_merge_commits' do it 'returns all commits except merge commits' do + merge_commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98") + expect(merge_commit).to receive(:merge_commit?).and_return(true) + collection = described_class.new(project, [ - build(:commit), - build(:commit, :merge_commit) + commit, + merge_commit ]) - expect(collection.without_merge_commits.size).to eq(1) + expect(collection.without_merge_commits).to contain_exactly(commit) + end + end + + describe 'enrichment methods' do + let(:gitaly_commit) { commit } + let(:hash_commit) { Commit.from_hash(gitaly_commit.to_hash, project) } + + describe '#unenriched' do + it 'returns all commits that are not backed by gitaly data' do + collection = described_class.new(project, [gitaly_commit, hash_commit]) + + expect(collection.unenriched).to contain_exactly(hash_commit) + end + end + + describe '#fully_enriched?' do + it 'returns true when all commits are backed by gitaly data' do + collection = described_class.new(project, [gitaly_commit, gitaly_commit]) + + expect(collection.fully_enriched?).to eq(true) + end + + it 'returns false when any commits are not backed by gitaly data' do + collection = described_class.new(project, [gitaly_commit, hash_commit]) + + expect(collection.fully_enriched?).to eq(false) + end + + it 'returns true when the collection is empty' do + collection = described_class.new(project, []) + + expect(collection.fully_enriched?).to eq(true) + end + end + + describe '#enrich!' do + it 'replaces commits in the collection with those backed by gitaly data' do + collection = described_class.new(project, [hash_commit]) + + collection.enrich! + + new_commit = collection.commits.first + expect(new_commit.id).to eq(hash_commit.id) + expect(hash_commit.gitaly_commit?).to eq(false) + expect(new_commit.gitaly_commit?).to eq(true) + end + + it 'maintains the original order of the commits' do + gitaly_commits = [gitaly_commit] * 3 + hash_commits = [hash_commit] * 3 + # Interleave the gitaly and hash commits together + original_commits = gitaly_commits.zip(hash_commits).flatten + collection = described_class.new(project, original_commits) + + collection.enrich! + + original_commits.each_with_index do |original_commit, i| + new_commit = collection.commits[i] + expect(original_commit.id).to eq(new_commit.id) + end + end + + it 'fetches data if there are unenriched commits' do + collection = described_class.new(project, [hash_commit]) + + expect(Commit).to receive(:lazy).exactly(:once) + + collection.enrich! + end + + it 'does not fetch data if all commits are enriched' do + collection = described_class.new(project, [gitaly_commit]) + + expect(Commit).not_to receive(:lazy) + + collection.enrich! + end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 42c49e330cc..c487ae45ceb 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -84,32 +84,27 @@ describe MergeRequest do describe '#default_squash_commit_message' do let(:project) { subject.project } - - def commit_collection(commit_hashes) - raw_commits = commit_hashes.map { |raw| Commit.from_hash(raw, project) } - - CommitCollection.new(project, raw_commits) - end + let(:is_multiline) { -> (c) { c.description.present? } } + let(:multiline_commits) { subject.commits.select(&is_multiline) } + let(:singleline_commits) { subject.commits.reject(&is_multiline) } it 'returns the oldest multiline commit message' do - commits = commit_collection([ - { message: 'Singleline', parent_ids: [] }, - { message: "Second multiline\nCommit message", parent_ids: [] }, - { message: "First multiline\nCommit message", parent_ids: [] } - ]) - - expect(subject).to receive(:commits).and_return(commits) - - expect(subject.default_squash_commit_message).to eq("First multiline\nCommit message") + expect(subject.default_squash_commit_message).to eq(multiline_commits.last.message) end it 'returns the merge request title if there are no multiline commits' do - commits = commit_collection([ - { message: 'Singleline', parent_ids: [] } - ]) + expect(subject).to receive(:commits).and_return( + CommitCollection.new(project, singleline_commits) + ) + + expect(subject.default_squash_commit_message).to eq(subject.title) + end - expect(subject).to receive(:commits).and_return(commits) + it 'does not return commit messages from multiline merge commits' do + collection = CommitCollection.new(project, multiline_commits).enrich! + expect(collection.commits).to all( receive(:merge_commit?).and_return(true) ) + expect(subject).to receive(:commits).and_return(collection) expect(subject.default_squash_commit_message).to eq(subject.title) end end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 4dbd79f2fc0..727fd8951f2 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -279,13 +279,18 @@ describe MergeRequestWidgetEntity do end describe 'commits_without_merge_commits' do + def find_matching_commit(short_id) + resource.commits.find { |c| c.short_id == short_id } + end + it 'should not include merge commits' do - # Mock all but the first 5 commits to be merge commits - resource.commits.each_with_index do |commit, i| - expect(commit).to receive(:merge_commit?).at_least(:once).and_return(i > 4) - end + commits_in_widget = subject[:commits_without_merge_commits] - expect(subject[:commits_without_merge_commits].size).to eq(5) + expect(commits_in_widget.length).to be < resource.commits.length + expect(commits_in_widget.length).to eq(resource.commits.without_merge_commits.length) + commits_in_widget.each do |c| + expect(find_matching_commit(c[:short_id]).merge_commit?).to eq(false) + end end end end -- cgit v1.2.1 From 33ca9d6515e1337858028006a32c3f6fa6717452 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Fri, 15 Mar 2019 11:49:28 +1300 Subject: Scope out merge commits in MergeRequest spec Previously the code for excluding merge commits from the commit collection (CommitCollection#without_merge_commits) was not working when the commits had come from a merge request. Now that this has been fixed, these tests fails. They should always have been written to exclude merge commits when comparing. --- spec/models/merge_request_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c487ae45ceb..fad73613989 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1040,7 +1040,7 @@ describe MergeRequest do describe '#commit_authors' do it 'returns all the authors of every commit in the merge request' do - users = subject.commits.map(&:author_email).uniq.map do |email| + users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| create(:user, email: email) end @@ -1054,7 +1054,7 @@ describe MergeRequest do describe '#authors' do it 'returns a list with all the commit authors in the merge request and author' do - users = subject.commits.map(&:author_email).uniq.map do |email| + users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| create(:user, email: email) end -- cgit v1.2.1 From a86f48c79b3d4b03279be0132e2a4eeadd5e22dc Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 21 Mar 2019 17:05:01 -0300 Subject: Add helper method to return a human-friendly name for database adapter --- spec/lib/gitlab/database_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index ae50abd0e7a..5f57cd6b825 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -17,6 +17,20 @@ describe Gitlab::Database do end end + describe '.human_adapter_name' do + it 'returns PostgreSQL when using PostgreSQL' do + allow(described_class).to receive(:postgresql?).and_return(true) + + expect(described_class.human_adapter_name).to eq('PostgreSQL') + end + + it 'returns MySQL when using MySQL' do + allow(described_class).to receive(:postgresql?).and_return(false) + + expect(described_class.human_adapter_name).to eq('MySQL') + end + end + # These are just simple smoke tests to check if the methods work (regardless # of what they may return). describe '.mysql?' do -- cgit v1.2.1 From 0b9a2295166935ab64cd6cbaf6549e4e01e4860c Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 22 Mar 2019 09:28:50 +0000 Subject: Freeze strings in ReferenceExtractor spec --- spec/lib/gitlab/reference_extractor_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 4139d1c650c..d982053d92e 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ReferenceExtractor do -- cgit v1.2.1 From 538741f2303f03e520d0bbfea150da6754f5a995 Mon Sep 17 00:00:00 2001 From: Thiago Presa Date: Fri, 22 Mar 2019 09:54:03 +0000 Subject: Add highest_role method to User --- spec/models/user_spec.rb | 62 +++++++++++++++++++++++++++++++++++++++++ spec/requests/api/users_spec.rb | 20 +++++++++++++ 2 files changed, 82 insertions(+) (limited to 'spec') diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 85b157a9435..1be29d039a7 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -660,6 +660,68 @@ describe User do end end + describe '#highest_role' do + let(:user) { create(:user) } + + let(:group) { create(:group) } + + it 'returns NO_ACCESS if none has been set' do + expect(user.highest_role).to eq(Gitlab::Access::NO_ACCESS) + end + + it 'returns MAINTAINER if user is maintainer of a project' do + create(:project, group: group) do |project| + project.add_maintainer(user) + end + + expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER) + end + + it 'returns the highest role if user is member of multiple projects' do + create(:project, group: group) do |project| + project.add_maintainer(user) + end + + create(:project, group: group) do |project| + project.add_developer(user) + end + + expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER) + end + + it 'returns MAINTAINER if user is maintainer of a group' do + create(:group) do |group| + group.add_user(user, GroupMember::MAINTAINER) + end + + expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER) + end + + it 'returns the highest role if user is member of multiple groups' do + create(:group) do |group| + group.add_user(user, GroupMember::MAINTAINER) + end + + create(:group) do |group| + group.add_user(user, GroupMember::DEVELOPER) + end + + expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER) + end + + it 'returns the highest role if user is member of multiple groups and projects' do + create(:group) do |group| + group.add_user(user, GroupMember::DEVELOPER) + end + + create(:project, group: group) do |project| + project.add_maintainer(user) + end + + expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER) + end + end + describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") } let(:user) { create(:user) } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a879426589d..b84202364e1 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -68,6 +68,13 @@ describe API::Users do expect(json_response.size).to eq(0) end + it "does not return the highest role" do + get api("/users"), params: { username: user.username } + + expect(response).to match_response_schema('public_api/v4/user/basics') + expect(json_response.first.keys).not_to include 'highest_role' + end + context "when public level is restricted" do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) @@ -286,6 +293,13 @@ describe API::Users do expect(json_response.keys).not_to include 'is_admin' end + it "does not return the user's `highest_role`" do + get api("/users/#{user.id}", user) + + expect(response).to match_response_schema('public_api/v4/user/basic') + expect(json_response.keys).not_to include 'highest_role' + end + context 'when authenticated as admin' do it 'includes the `is_admin` field' do get api("/users/#{user.id}", admin) @@ -300,6 +314,12 @@ describe API::Users do expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response.keys).to include 'created_at' end + it 'includes the `highest_role` field' do + get api("/users/#{user.id}", admin) + + expect(response).to match_response_schema('public_api/v4/user/admin') + expect(json_response['highest_role']).to be(0) + end end context 'for an anonymous user' do -- cgit v1.2.1 From 6006f8204998f4acd345c76481bb65be497b4673 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Fri, 22 Mar 2019 11:17:42 +0100 Subject: Update GitLab Runner Helm Chart to 0.3.0 --- spec/models/clusters/applications/runner_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 6972fc03415..06d9bc076cd 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -22,7 +22,7 @@ describe Clusters::Applications::Runner do it 'should be initialized with 4 arguments' do expect(subject.name).to eq('runner') expect(subject.chart).to eq('runner/gitlab-runner') - expect(subject.version).to eq('0.2.0') + expect(subject.version).to eq('0.3.0') expect(subject).to be_rbac expect(subject.repository).to eq('https://charts.gitlab.io') expect(subject.files).to eq(gitlab_runner.files) @@ -40,7 +40,7 @@ describe Clusters::Applications::Runner do let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } it 'should be initialized with the locked version' do - expect(subject.version).to eq('0.2.0') + expect(subject.version).to eq('0.3.0') end end end -- cgit v1.2.1 From 022968764ced43c5de0c3291cfe2fbc7b068e944 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 22 Mar 2019 11:03:15 +0000 Subject: Revert "Merge branch 'winh-remove-sushi' into 'master'" This reverts merge request !26430 --- spec/javascripts/activities_spec.js | 2 +- spec/javascripts/ajax_loading_spinner_spec.js | 2 +- spec/javascripts/awards_handler_spec.js | 4 +- spec/javascripts/behaviors/quick_submit_spec.js | 4 +- spec/javascripts/behaviors/requires_input_spec.js | 4 +- .../behaviors/shortcuts/shortcuts_issuable_spec.js | 2 +- .../balsamiq/balsamiq_viewer_integration_spec.js | 4 +- spec/javascripts/blob/blob_file_dropzone_spec.js | 4 +- spec/javascripts/blob/notebook/index_spec.js | 4 +- spec/javascripts/blob/pdf/index_spec.js | 4 +- spec/javascripts/blob/sketch/index_spec.js | 4 +- spec/javascripts/blob/viewer/index_spec.js | 4 +- spec/javascripts/boards/components/board_spec.js | 2 +- spec/javascripts/bootstrap_linked_tabs_spec.js | 4 +- .../ci_variable_list/ajax_variable_list_spec.js | 8 +- .../ci_variable_list/ci_variable_list_spec.js | 16 +-- .../native_form_variable_list_spec.js | 4 +- spec/javascripts/clusters/clusters_bundle_spec.js | 4 +- spec/javascripts/collapsed_sidebar_todo_spec.js | 2 +- spec/javascripts/create_item_dropdown_spec.js | 4 +- .../filtered_search/dropdown_user_spec.js | 2 +- .../filtered_search/dropdown_utils_spec.js | 2 +- spec/javascripts/fixtures/.gitignore | 1 - spec/javascripts/fixtures/abuse_reports.rb | 2 +- spec/javascripts/fixtures/admin_users.rb | 2 +- spec/javascripts/fixtures/application_settings.rb | 2 +- spec/javascripts/fixtures/blob.rb | 2 +- spec/javascripts/fixtures/boards.rb | 2 +- spec/javascripts/fixtures/branches.rb | 2 +- spec/javascripts/fixtures/clusters.rb | 2 +- spec/javascripts/fixtures/commit.rb | 2 +- spec/javascripts/fixtures/groups.rb | 4 +- spec/javascripts/fixtures/issues.rb | 10 +- spec/javascripts/fixtures/jobs.rb | 2 +- spec/javascripts/fixtures/merge_requests.rb | 10 +- spec/javascripts/fixtures/pipeline_schedules.rb | 4 +- spec/javascripts/fixtures/projects.rb | 10 +- spec/javascripts/fixtures/prometheus_service.rb | 2 +- spec/javascripts/fixtures/search.rb | 2 +- spec/javascripts/fixtures/services.rb | 2 +- spec/javascripts/fixtures/sessions.rb | 2 +- spec/javascripts/fixtures/snippet.rb | 2 +- .../fixtures/static/ajax_loading_spinner.html | 3 - .../fixtures/static/ajax_loading_spinner.html.raw | 3 + .../fixtures/static/balsamiq_viewer.html | 1 - .../fixtures/static/balsamiq_viewer.html.raw | 1 + .../fixtures/static/create_item_dropdown.html | 11 --- .../fixtures/static/create_item_dropdown.html.raw | 11 +++ spec/javascripts/fixtures/static/event_filter.html | 44 --------- .../fixtures/static/event_filter.html.raw | 44 +++++++++ spec/javascripts/fixtures/static/gl_dropdown.html | 26 ----- .../fixtures/static/gl_dropdown.html.raw | 26 +++++ .../fixtures/static/gl_field_errors.html | 22 ----- .../fixtures/static/gl_field_errors.html.raw | 22 +++++ .../fixtures/static/issuable_filter.html | 9 -- .../fixtures/static/issuable_filter.html.raw | 9 ++ .../fixtures/static/issue_sidebar_label.html | 26 ----- .../fixtures/static/issue_sidebar_label.html.raw | 26 +++++ .../fixtures/static/line_highlighter.html | 107 --------------------- .../fixtures/static/line_highlighter.html.raw | 107 +++++++++++++++++++++ spec/javascripts/fixtures/static/linked_tabs.html | 20 ---- .../fixtures/static/linked_tabs.html.raw | 20 ++++ .../fixtures/static/merge_requests_show.html | 15 --- .../fixtures/static/merge_requests_show.html.raw | 15 +++ .../fixtures/static/mini_dropdown_graph.html | 13 --- .../fixtures/static/mini_dropdown_graph.html.raw | 13 +++ .../fixtures/static/notebook_viewer.html | 1 - .../fixtures/static/notebook_viewer.html.raw | 1 + .../fixtures/static/oauth_remember_me.html | 6 -- .../fixtures/static/oauth_remember_me.html.raw | 6 ++ spec/javascripts/fixtures/static/pdf_viewer.html | 1 - .../fixtures/static/pdf_viewer.html.raw | 1 + .../fixtures/static/pipeline_graph.html | 24 ----- .../fixtures/static/pipeline_graph.html.raw | 24 +++++ spec/javascripts/fixtures/static/pipelines.html | 3 - .../javascripts/fixtures/static/pipelines.html.raw | 3 + .../static/project_select_combo_button.html | 9 -- .../static/project_select_combo_button.html.raw | 9 ++ .../fixtures/static/search_autocomplete.html | 15 --- .../fixtures/static/search_autocomplete.html.raw | 15 +++ spec/javascripts/fixtures/static/signin_tabs.html | 8 -- .../fixtures/static/signin_tabs.html.raw | 8 ++ .../javascripts/fixtures/static/sketch_viewer.html | 3 - .../fixtures/static/sketch_viewer.html.raw | 3 + spec/javascripts/fixtures/todos.rb | 2 +- spec/javascripts/fixtures/u2f.rb | 4 +- spec/javascripts/gl_dropdown_spec.js | 4 +- spec/javascripts/gl_field_errors_spec.js | 4 +- spec/javascripts/header_spec.js | 2 +- .../integrations/integration_settings_form_spec.js | 2 +- spec/javascripts/issue_spec.js | 10 +- spec/javascripts/labels_issue_sidebar_spec.js | 4 +- spec/javascripts/lazy_loader_spec.js | 6 +- spec/javascripts/line_highlighter_spec.js | 4 +- spec/javascripts/merge_request_spec.js | 8 +- spec/javascripts/merge_request_tabs_spec.js | 6 +- .../mini_pipeline_graph_dropdown_spec.js | 4 +- spec/javascripts/new_branch_spec.js | 4 +- spec/javascripts/notes_spec.js | 2 +- spec/javascripts/oauth_remember_me_spec.js | 4 +- .../admin/abuse_reports/abuse_reports_spec.js | 2 +- .../account_and_limits_spec.js | 2 +- .../pages/admin/users/new/index_spec.js | 2 +- .../sessions/new/preserve_url_fragment_spec.js | 4 +- spec/javascripts/pipelines_spec.js | 4 +- .../project_select_combo_button_spec.js | 2 +- .../prometheus_metrics/prometheus_metrics_spec.js | 2 +- spec/javascripts/read_more_spec.js | 2 +- spec/javascripts/right_sidebar_spec.js | 2 +- spec/javascripts/search_autocomplete_spec.js | 4 +- spec/javascripts/search_spec.js | 2 +- spec/javascripts/settings_panels_spec.js | 4 +- spec/javascripts/shortcuts_spec.js | 2 +- spec/javascripts/sidebar/sidebar_assignees_spec.js | 4 +- spec/javascripts/signin_tabs_memoizer_spec.js | 2 +- spec/javascripts/todos_spec.js | 4 +- spec/javascripts/u2f/authenticate_spec.js | 4 +- spec/javascripts/u2f/register_spec.js | 4 +- spec/javascripts/user_popovers_spec.js | 2 +- .../states/mr_widget_ready_to_merge_spec.js | 2 +- .../components/user_popover/user_popover_spec.js | 2 +- spec/javascripts/zen_mode_spec.js | 2 +- .../support/helpers/javascript_fixtures_helpers.rb | 2 +- 123 files changed, 511 insertions(+), 512 deletions(-) delete mode 100644 spec/javascripts/fixtures/static/ajax_loading_spinner.html create mode 100644 spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw delete mode 100644 spec/javascripts/fixtures/static/balsamiq_viewer.html create mode 100644 spec/javascripts/fixtures/static/balsamiq_viewer.html.raw delete mode 100644 spec/javascripts/fixtures/static/create_item_dropdown.html create mode 100644 spec/javascripts/fixtures/static/create_item_dropdown.html.raw delete mode 100644 spec/javascripts/fixtures/static/event_filter.html create mode 100644 spec/javascripts/fixtures/static/event_filter.html.raw delete mode 100644 spec/javascripts/fixtures/static/gl_dropdown.html create mode 100644 spec/javascripts/fixtures/static/gl_dropdown.html.raw delete mode 100644 spec/javascripts/fixtures/static/gl_field_errors.html create mode 100644 spec/javascripts/fixtures/static/gl_field_errors.html.raw delete mode 100644 spec/javascripts/fixtures/static/issuable_filter.html create mode 100644 spec/javascripts/fixtures/static/issuable_filter.html.raw delete mode 100644 spec/javascripts/fixtures/static/issue_sidebar_label.html create mode 100644 spec/javascripts/fixtures/static/issue_sidebar_label.html.raw delete mode 100644 spec/javascripts/fixtures/static/line_highlighter.html create mode 100644 spec/javascripts/fixtures/static/line_highlighter.html.raw delete mode 100644 spec/javascripts/fixtures/static/linked_tabs.html create mode 100644 spec/javascripts/fixtures/static/linked_tabs.html.raw delete mode 100644 spec/javascripts/fixtures/static/merge_requests_show.html create mode 100644 spec/javascripts/fixtures/static/merge_requests_show.html.raw delete mode 100644 spec/javascripts/fixtures/static/mini_dropdown_graph.html create mode 100644 spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw delete mode 100644 spec/javascripts/fixtures/static/notebook_viewer.html create mode 100644 spec/javascripts/fixtures/static/notebook_viewer.html.raw delete mode 100644 spec/javascripts/fixtures/static/oauth_remember_me.html create mode 100644 spec/javascripts/fixtures/static/oauth_remember_me.html.raw delete mode 100644 spec/javascripts/fixtures/static/pdf_viewer.html create mode 100644 spec/javascripts/fixtures/static/pdf_viewer.html.raw delete mode 100644 spec/javascripts/fixtures/static/pipeline_graph.html create mode 100644 spec/javascripts/fixtures/static/pipeline_graph.html.raw delete mode 100644 spec/javascripts/fixtures/static/pipelines.html create mode 100644 spec/javascripts/fixtures/static/pipelines.html.raw delete mode 100644 spec/javascripts/fixtures/static/project_select_combo_button.html create mode 100644 spec/javascripts/fixtures/static/project_select_combo_button.html.raw delete mode 100644 spec/javascripts/fixtures/static/search_autocomplete.html create mode 100644 spec/javascripts/fixtures/static/search_autocomplete.html.raw delete mode 100644 spec/javascripts/fixtures/static/signin_tabs.html create mode 100644 spec/javascripts/fixtures/static/signin_tabs.html.raw delete mode 100644 spec/javascripts/fixtures/static/sketch_viewer.html create mode 100644 spec/javascripts/fixtures/static/sketch_viewer.html.raw (limited to 'spec') diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js index 23b6de7e4e0..068b8eb65bc 100644 --- a/spec/javascripts/activities_spec.js +++ b/spec/javascripts/activities_spec.js @@ -7,7 +7,7 @@ import Pager from '~/pager'; describe('Activities', () => { window.gon || (window.gon = {}); - const fixtureTemplate = 'static/event_filter.html'; + const fixtureTemplate = 'static/event_filter.html.raw'; const filters = [ { id: 'all', diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js index 89195a4397f..9389fc94f17 100644 --- a/spec/javascripts/ajax_loading_spinner_spec.js +++ b/spec/javascripts/ajax_loading_spinner_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import AjaxLoadingSpinner from '~/ajax_loading_spinner'; describe('Ajax Loading Spinner', () => { - const fixtureTemplate = 'static/ajax_loading_spinner.html'; + const fixtureTemplate = 'static/ajax_loading_spinner.html.raw'; preloadFixtures(fixtureTemplate); beforeEach(() => { diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index e10df1b45e7..e5b5707dcef 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -24,13 +24,13 @@ const lazyAssert = function(done, assertFn) { describe('AwardsHandler', function() { const emojiData = getJSONFixture('emojis/emojis.json'); - preloadFixtures('snippets/show.html'); + preloadFixtures('snippets/show.html.raw'); beforeEach(function(done) { mock = new MockAdapter(axios); mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData); - loadFixtures('snippets/show.html'); + loadFixtures('snippets/show.html.raw'); loadAwardsHandler(true) .then(obj => { awardsHandler = obj; diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index 7af8c984841..681463aab66 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -4,10 +4,10 @@ import '~/behaviors/quick_submit'; describe('Quick Submit behavior', function() { const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options); - preloadFixtures('snippets/show.html'); + preloadFixtures('snippets/show.html.raw'); beforeEach(() => { - loadFixtures('snippets/show.html'); + loadFixtures('snippets/show.html.raw'); $('form').submit(e => { // Prevent a form submit from moving us off the testing page e.preventDefault(); diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js index 617fe49b059..1bde2bb3024 100644 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ b/spec/javascripts/behaviors/requires_input_spec.js @@ -3,10 +3,10 @@ import '~/behaviors/requires_input'; describe('requiresInput', () => { let submitButton; - preloadFixtures('branches/new_branch.html'); + preloadFixtures('branches/new_branch.html.raw'); beforeEach(() => { - loadFixtures('branches/new_branch.html'); + loadFixtures('branches/new_branch.html.raw'); submitButton = $('button[type="submit"]'); }); diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js index 5e457a4e823..4843a0386b5 100644 --- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js +++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js @@ -9,7 +9,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form'; describe('ShortcutsIssuable', function() { - const fixtureName = 'snippets/show.html'; + const fixtureName = 'snippets/show.html.raw'; preloadFixtures(fixtureName); beforeAll(done => { diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js index 68b4f261617..5f027f59fcf 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js @@ -6,10 +6,10 @@ describe('Balsamiq integration spec', () => { let endpoint; let balsamiqViewer; - preloadFixtures('static/balsamiq_viewer.html'); + preloadFixtures('static/balsamiq_viewer.html.raw'); beforeEach(() => { - loadFixtures('static/balsamiq_viewer.html'); + loadFixtures('static/balsamiq_viewer.html.raw'); container = document.getElementById('js-balsamiq-viewer'); balsamiqViewer = new BalsamiqViewer(container); diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js index cab06a0a9be..432d8a65b0a 100644 --- a/spec/javascripts/blob/blob_file_dropzone_spec.js +++ b/spec/javascripts/blob/blob_file_dropzone_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import BlobFileDropzone from '~/blob/blob_file_dropzone'; describe('BlobFileDropzone', function() { - preloadFixtures('blob/show.html'); + preloadFixtures('blob/show.html.raw'); beforeEach(() => { - loadFixtures('blob/show.html'); + loadFixtures('blob/show.html.raw'); const form = $('.js-upload-blob-form'); this.blobFileDropzone = new BlobFileDropzone(form, 'POST'); this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone; diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index 6bb5bac007f..28d3b2f5ea3 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -3,10 +3,10 @@ import axios from '~/lib/utils/axios_utils'; import renderNotebook from '~/blob/notebook'; describe('iPython notebook renderer', () => { - preloadFixtures('static/notebook_viewer.html'); + preloadFixtures('static/notebook_viewer.html.raw'); beforeEach(() => { - loadFixtures('static/notebook_viewer.html'); + loadFixtures('static/notebook_viewer.html.raw'); }); it('shows loading icon', () => { diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index acf87580777..be917a0613f 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -15,10 +15,10 @@ describe('PDF renderer', () => { } }; - preloadFixtures('static/pdf_viewer.html'); + preloadFixtures('static/pdf_viewer.html.raw'); beforeEach(() => { - loadFixtures('static/pdf_viewer.html'); + loadFixtures('static/pdf_viewer.html.raw'); viewer = document.getElementById('js-pdf-viewer'); viewer.dataset.endpoint = testPDF; }); diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js index 3d3129e10da..2b1e81e9cbc 100644 --- a/spec/javascripts/blob/sketch/index_spec.js +++ b/spec/javascripts/blob/sketch/index_spec.js @@ -13,10 +13,10 @@ describe('Sketch viewer', () => { }); }; - preloadFixtures('static/sketch_viewer.html'); + preloadFixtures('static/sketch_viewer.html.raw'); beforeEach(() => { - loadFixtures('static/sketch_viewer.html'); + loadFixtures('static/sketch_viewer.html.raw'); }); describe('with error message', () => { diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index 4ac15ca5aa2..93a942fe8d4 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -9,12 +9,12 @@ describe('Blob viewer', () => { let blob; let mock; - preloadFixtures('snippets/show.html'); + preloadFixtures('snippets/show.html.raw'); beforeEach(() => { mock = new MockAdapter(axios); - loadFixtures('snippets/show.html'); + loadFixtures('snippets/show.html.raw'); $('#modal-upload-blob').remove(); blob = new BlobViewer(); diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index 6e6b3e6950b..dee7841c088 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -9,7 +9,7 @@ describe('Board component', () => { let el; beforeEach(done => { - loadFixtures('boards/show.html'); + loadFixtures('boards/show.html.raw'); el = document.createElement('div'); document.body.appendChild(el); diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js index 1d21637ceae..c3e3d78ff63 100644 --- a/spec/javascripts/bootstrap_linked_tabs_spec.js +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js @@ -1,10 +1,10 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; describe('Linked Tabs', () => { - preloadFixtures('static/linked_tabs.html'); + preloadFixtures('static/linked_tabs.html.raw'); beforeEach(() => { - loadFixtures('static/linked_tabs.html'); + loadFixtures('static/linked_tabs.html.raw'); }); describe('when is initialized', () => { diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 481b1a4d4b0..1fc0e206d5e 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -7,8 +7,8 @@ const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-proje const HIDE_CLASS = 'hide'; describe('AjaxFormVariableList', () => { - preloadFixtures('projects/ci_cd_settings.html'); - preloadFixtures('projects/ci_cd_settings_with_variables.html'); + preloadFixtures('projects/ci_cd_settings.html.raw'); + preloadFixtures('projects/ci_cd_settings_with_variables.html.raw'); let container; let saveButton; @@ -18,7 +18,7 @@ describe('AjaxFormVariableList', () => { let ajaxVariableList; beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html'); + loadFixtures('projects/ci_cd_settings.html.raw'); container = document.querySelector('.js-ci-variable-list-section'); mock = new MockAdapter(axios); @@ -168,7 +168,7 @@ describe('AjaxFormVariableList', () => { describe('updateRowsWithPersistedVariables', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings_with_variables.html'); + loadFixtures('projects/ci_cd_settings_with_variables.html.raw'); container = document.querySelector('.js-ci-variable-list-section'); const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section'); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 70f49469300..bef59b86d0c 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -5,9 +5,9 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; const HIDE_CLASS = 'hide'; describe('VariableList', () => { - preloadFixtures('pipeline_schedules/edit.html'); - preloadFixtures('pipeline_schedules/edit_with_variables.html'); - preloadFixtures('projects/ci_cd_settings.html'); + preloadFixtures('pipeline_schedules/edit.html.raw'); + preloadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + preloadFixtures('projects/ci_cd_settings.html.raw'); let $wrapper; let variableList; @@ -15,7 +15,7 @@ describe('VariableList', () => { describe('with only key/value inputs', () => { describe('with no variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html'); + loadFixtures('pipeline_schedules/edit.html.raw'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -82,7 +82,7 @@ describe('VariableList', () => { describe('with persisted variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html'); + loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -115,7 +115,7 @@ describe('VariableList', () => { describe('with all inputs(key, value, protected)', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html'); + loadFixtures('projects/ci_cd_settings.html.raw'); $wrapper = $('.js-ci-variable-list-section'); $wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false'); @@ -149,7 +149,7 @@ describe('VariableList', () => { describe('toggleEnableRow method', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html'); + loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -198,7 +198,7 @@ describe('VariableList', () => { describe('hideValues', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html'); + loadFixtures('projects/ci_cd_settings.html.raw'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js index 4982b68fa81..997d0d54d79 100644 --- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js @@ -2,12 +2,12 @@ import $ from 'jquery'; import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list'; describe('NativeFormVariableList', () => { - preloadFixtures('pipeline_schedules/edit.html'); + preloadFixtures('pipeline_schedules/edit.html.raw'); let $wrapper; beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html'); + loadFixtures('pipeline_schedules/edit.html.raw'); $wrapper = $('.js-ci-variable-list-section'); setupNativeFormVariableList({ diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 0d3dcc29f22..71f16dc259e 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -9,10 +9,10 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; describe('Clusters', () => { let cluster; - preloadFixtures('clusters/show_cluster.html'); + preloadFixtures('clusters/show_cluster.html.raw'); beforeEach(() => { - loadFixtures('clusters/show_cluster.html'); + loadFixtures('clusters/show_cluster.html.raw'); cluster = new Clusters(); }); diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js index bb90e53e525..dc5737558c0 100644 --- a/spec/javascripts/collapsed_sidebar_todo_spec.js +++ b/spec/javascripts/collapsed_sidebar_todo_spec.js @@ -6,7 +6,7 @@ import Sidebar from '~/right_sidebar'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Issuable right sidebar collapsed todo toggle', () => { - const fixtureName = 'issues/open-issue.html'; + const fixtureName = 'issues/open-issue.html.raw'; const jsonFixtureName = 'todos/todos.json'; let mock; diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js index a814952faab..9cf72d7c55b 100644 --- a/spec/javascripts/create_item_dropdown_spec.js +++ b/spec/javascripts/create_item_dropdown_spec.js @@ -20,7 +20,7 @@ const DROPDOWN_ITEM_DATA = [ ]; describe('CreateItemDropdown', () => { - preloadFixtures('static/create_item_dropdown.html'); + preloadFixtures('static/create_item_dropdown.html.raw'); let $wrapperEl; let createItemDropdown; @@ -44,7 +44,7 @@ describe('CreateItemDropdown', () => { } beforeEach(() => { - loadFixtures('static/create_item_dropdown.html'); + loadFixtures('static/create_item_dropdown.html.raw'); $wrapperEl = $('.js-create-item-dropdown-fixture-root'); }); diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index f764800fff0..e8fcc8592eb 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -72,7 +72,7 @@ describe('Dropdown User', () => { }); describe('hideCurrentUser', () => { - const fixtureTemplate = 'issues/issue_list.html'; + const fixtureTemplate = 'issues/issue_list.html.raw'; preloadFixtures(fixtureTemplate); let dropdown; diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 62d1bd69635..cfd0b96ec43 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -4,7 +4,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; describe('Dropdown Utils', () => { - const issueListFixture = 'issues/issue_list.html'; + const issueListFixture = 'issues/issue_list.html.raw'; preloadFixtures(issueListFixture); describe('getEscapedText', () => { diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore index 2507c8e7263..0c35cdd778e 100644 --- a/spec/javascripts/fixtures/.gitignore +++ b/spec/javascripts/fixtures/.gitignore @@ -1,3 +1,2 @@ *.html.raw -*.html *.json diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb index 54b6419bcdb..387858cba77 100644 --- a/spec/javascripts/fixtures/abuse_reports.rb +++ b/spec/javascripts/fixtures/abuse_reports.rb @@ -18,7 +18,7 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll sign_in(admin) end - it 'abuse_reports/abuse_reports_list.html' do |example| + it 'abuse_reports/abuse_reports_list.html.raw' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb index 76dbdf603da..9989ac4fff2 100644 --- a/spec/javascripts/fixtures/admin_users.rb +++ b/spec/javascripts/fixtures/admin_users.rb @@ -17,7 +17,7 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('admin/users') end - it 'admin/users/new_with_internal_user_regex.html' do |example| + it 'admin/users/new_with_internal_user_regex.html.raw' do |example| stub_application_setting(user_default_external: true) stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?') diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb index c535e598e12..a9d3043f73d 100644 --- a/spec/javascripts/fixtures/application_settings.rb +++ b/spec/javascripts/fixtures/application_settings.rb @@ -23,7 +23,7 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c remove_repository(project) end - it 'application_settings/accounts_and_limit.html' do |example| + it 'application_settings/accounts_and_limit.html.raw' do |example| stub_application_setting(user_default_external: false) get :show diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index db7749bc000..cd66d98f92a 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -22,7 +22,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'blob/show.html' do |example| + it 'blob/show.html.raw' do |example| get(:show, params: { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb index c4390e89578..1d675e008ba 100644 --- a/spec/javascripts/fixtures/boards.rb +++ b/spec/javascripts/fixtures/boards.rb @@ -17,7 +17,7 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller sign_in(admin) end - it 'boards/show.html' do |example| + it 'boards/show.html.raw' do |example| get(:index, params: { namespace_id: project.namespace, project_id: project diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb index 5d2d6c7ec0e..3cc713ef90f 100644 --- a/spec/javascripts/fixtures/branches.rb +++ b/spec/javascripts/fixtures/branches.rb @@ -21,7 +21,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'branches/new_branch.html' do |example| + it 'branches/new_branch.html.raw' do |example| get :new, params: { namespace_id: project.namespace.to_param, project_id: project diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 8ebd8a41366..69dbe54ffc2 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -22,7 +22,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'clusters/show_cluster.html' do |example| + it 'clusters/show_cluster.html.raw' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index ab10f559e4b..295f13b34a4 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -19,7 +19,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end - it 'commit/show.html' do |example| + it 'commit/show.html.raw' do |example| params = { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index 16e31028b05..03136f4e661 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -18,7 +18,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe GroupsController, '(JavaScript fixtures)', type: :controller do - it 'groups/edit.html' do |example| + it 'groups/edit.html.raw' do |example| get :edit, params: { id: group } expect(response).to be_success @@ -27,7 +27,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'groups/ci_cd_settings.html' do |example| + it 'groups/ci_cd_settings.html.raw' do |example| get :show, params: { group_id: group } expect(response).to be_success diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 645b3aa788a..9b8e90c2a43 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -21,26 +21,26 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller remove_repository(project) end - it 'issues/open-issue.html' do |example| + it 'issues/open-issue.html.raw' do |example| render_issue(example.description, create(:issue, project: project)) end - it 'issues/closed-issue.html' do |example| + it 'issues/closed-issue.html.raw' do |example| render_issue(example.description, create(:closed_issue, project: project)) end - it 'issues/issue-with-task-list.html' do |example| + it 'issues/issue-with-task-list.html.raw' do |example| issue = create(:issue, project: project, description: '- [ ] Task List Item') render_issue(example.description, issue) end - it 'issues/issue_with_comment.html' do |example| + it 'issues/issue_with_comment.html.raw' do |example| issue = create(:issue, project: project) create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save render_issue(example.description, issue) end - it 'issues/issue_list.html' do |example| + it 'issues/issue_list.html.raw' do |example| create(:issue, project: project) get :index, params: { diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 941235190b5..433bb690a1c 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -32,7 +32,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'builds/build-with-artifacts.html' do |example| + it 'builds/build-with-artifacts.html.raw' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 7df1e5cb512..eb37be87e1d 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -42,19 +42,19 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont remove_repository(project) end - it 'merge_requests/merge_request_of_current_user.html' do |example| + it 'merge_requests/merge_request_of_current_user.html.raw' do |example| merge_request.update(author: admin) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_task_list.html' do |example| + it 'merge_requests/merge_request_with_task_list.html.raw' do |example| create(:ci_build, :pending, pipeline: pipeline) render_merge_request(example.description, merge_request) end - it 'merge_requests/merged_merge_request.html' do |example| + it 'merge_requests/merged_merge_request.html.raw' do |example| expect_next_instance_of(MergeRequest) do |merge_request| allow(merge_request).to receive(:source_branch_exists?).and_return(true) allow(merge_request).to receive(:can_remove_source_branch?).and_return(true) @@ -62,13 +62,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merged_merge_request) end - it 'merge_requests/diff_comment.html' do |example| + it 'merge_requests/diff_comment.html.raw' do |example| create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_comment.html' do |example| + it 'merge_requests/merge_request_with_comment.html.raw' do |example| create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item') render_merge_request(example.description, merge_request) end diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb index e5176a58273..05d79ec8de9 100644 --- a/spec/javascripts/fixtures/pipeline_schedules.rb +++ b/spec/javascripts/fixtures/pipeline_schedules.rb @@ -21,7 +21,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : sign_in(admin) end - it 'pipeline_schedules/edit.html' do |example| + it 'pipeline_schedules/edit.html.raw' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, @@ -32,7 +32,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : store_frontend_fixture(response, example.description) end - it 'pipeline_schedules/edit_with_variables.html' do |example| + it 'pipeline_schedules/edit_with_variables.html.raw' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 446da83a7f9..85f02923804 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -28,7 +28,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe ProjectsController, '(JavaScript fixtures)', type: :controller do - it 'projects/dashboard.html' do |example| + it 'projects/dashboard.html.raw' do |example| get :show, params: { namespace_id: project.namespace.to_param, id: project @@ -38,7 +38,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/overview.html' do |example| + it 'projects/overview.html.raw' do |example| get :show, params: { namespace_id: project_with_repo.namespace.to_param, id: project_with_repo @@ -48,7 +48,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/edit.html' do |example| + it 'projects/edit.html.raw' do |example| get :edit, params: { namespace_id: project.namespace.to_param, id: project @@ -60,7 +60,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'projects/ci_cd_settings.html' do |example| + it 'projects/ci_cd_settings.html.raw' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project @@ -70,7 +70,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/ci_cd_settings_with_variables.html' do |example| + it 'projects/ci_cd_settings_with_variables.html.raw' do |example| create(:ci_variable, project: project_variable_populated) create(:ci_variable, project: project_variable_populated) diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb index 29dc95305b7..746fbfd66dd 100644 --- a/spec/javascripts/fixtures/prometheus_service.rb +++ b/spec/javascripts/fixtures/prometheus_service.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/prometheus/prometheus_service.html' do |example| + it 'services/prometheus/prometheus_service.html.raw' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb index 5f5b4d4e60d..703cd3d49fa 100644 --- a/spec/javascripts/fixtures/search.rb +++ b/spec/javascripts/fixtures/search.rb @@ -9,7 +9,7 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('search/') end - it 'search/show.html' do |example| + it 'search/show.html.raw' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb index dc7ee484c22..6ccd74a07ff 100644 --- a/spec/javascripts/fixtures/services.rb +++ b/spec/javascripts/fixtures/services.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/edit_service.html' do |example| + it 'services/edit_service.html.raw' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb index 8656dea696a..e90a58e8c54 100644 --- a/spec/javascripts/fixtures/sessions.rb +++ b/spec/javascripts/fixtures/sessions.rb @@ -16,7 +16,7 @@ describe 'Sessions (JavaScript fixtures)' do set_devise_mapping(context: @request) end - it 'sessions/new.html' do |example| + it 'sessions/new.html.raw' do |example| get :new expect(response).to be_success diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index ebc5b793166..bcd6546f3df 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -23,7 +23,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'snippets/show.html' do |example| + it 'snippets/show.html.raw' do |example| create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') get(:show, params: { id: snippet.to_param }) diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html b/spec/javascripts/fixtures/static/ajax_loading_spinner.html deleted file mode 100644 index 0e1ebb32b1c..00000000000 --- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw b/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw new file mode 100644 index 00000000000..0e1ebb32b1c --- /dev/null +++ b/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw @@ -0,0 +1,3 @@ + + + diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html b/spec/javascripts/fixtures/static/balsamiq_viewer.html deleted file mode 100644 index cdd723d1a84..00000000000 --- a/spec/javascripts/fixtures/static/balsamiq_viewer.html +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw b/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw new file mode 100644 index 00000000000..cdd723d1a84 --- /dev/null +++ b/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw @@ -0,0 +1 @@ +
    diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html b/spec/javascripts/fixtures/static/create_item_dropdown.html deleted file mode 100644 index d2d38370092..00000000000 --- a/spec/javascripts/fixtures/static/create_item_dropdown.html +++ /dev/null @@ -1,11 +0,0 @@ -
    - -
    diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw b/spec/javascripts/fixtures/static/create_item_dropdown.html.raw new file mode 100644 index 00000000000..d2d38370092 --- /dev/null +++ b/spec/javascripts/fixtures/static/create_item_dropdown.html.raw @@ -0,0 +1,11 @@ +
    + +
    diff --git a/spec/javascripts/fixtures/static/event_filter.html b/spec/javascripts/fixtures/static/event_filter.html deleted file mode 100644 index 8e9b6fb1b5c..00000000000 --- a/spec/javascripts/fixtures/static/event_filter.html +++ /dev/null @@ -1,44 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/event_filter.html.raw b/spec/javascripts/fixtures/static/event_filter.html.raw new file mode 100644 index 00000000000..8e9b6fb1b5c --- /dev/null +++ b/spec/javascripts/fixtures/static/event_filter.html.raw @@ -0,0 +1,44 @@ + diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html b/spec/javascripts/fixtures/static/gl_dropdown.html deleted file mode 100644 index 08f6738414e..00000000000 --- a/spec/javascripts/fixtures/static/gl_dropdown.html +++ /dev/null @@ -1,26 +0,0 @@ -
    - -
    diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html.raw b/spec/javascripts/fixtures/static/gl_dropdown.html.raw new file mode 100644 index 00000000000..08f6738414e --- /dev/null +++ b/spec/javascripts/fixtures/static/gl_dropdown.html.raw @@ -0,0 +1,26 @@ +
    + +
    diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html b/spec/javascripts/fixtures/static/gl_field_errors.html deleted file mode 100644 index f8470e02b7c..00000000000 --- a/spec/javascripts/fixtures/static/gl_field_errors.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -Text -
    -
    -Email -
    -
    -Password -
    -
    -Alphanumeric -
    -
    - -
    -
    -Custom, do not validate -
    -
    -Submit -
    diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html.raw b/spec/javascripts/fixtures/static/gl_field_errors.html.raw new file mode 100644 index 00000000000..f8470e02b7c --- /dev/null +++ b/spec/javascripts/fixtures/static/gl_field_errors.html.raw @@ -0,0 +1,22 @@ +
    +
    +Text +
    +
    +Email +
    +
    +Password +
    +
    +Alphanumeric +
    +
    + +
    +
    +Custom, do not validate +
    +
    +Submit +
    diff --git a/spec/javascripts/fixtures/static/issuable_filter.html b/spec/javascripts/fixtures/static/issuable_filter.html deleted file mode 100644 index 06b70fb43f1..00000000000 --- a/spec/javascripts/fixtures/static/issuable_filter.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - - - - - - - -
    diff --git a/spec/javascripts/fixtures/static/issuable_filter.html.raw b/spec/javascripts/fixtures/static/issuable_filter.html.raw new file mode 100644 index 00000000000..06b70fb43f1 --- /dev/null +++ b/spec/javascripts/fixtures/static/issuable_filter.html.raw @@ -0,0 +1,9 @@ +
    + + + + + + + +
    diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html b/spec/javascripts/fixtures/static/issue_sidebar_label.html deleted file mode 100644 index ec8fb30f219..00000000000 --- a/spec/javascripts/fixtures/static/issue_sidebar_label.html +++ /dev/null @@ -1,26 +0,0 @@ -
    - - - -
    diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw b/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw new file mode 100644 index 00000000000..ec8fb30f219 --- /dev/null +++ b/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw @@ -0,0 +1,26 @@ +
    + + + +
    diff --git a/spec/javascripts/fixtures/static/line_highlighter.html b/spec/javascripts/fixtures/static/line_highlighter.html deleted file mode 100644 index 897a25d6760..00000000000 --- a/spec/javascripts/fixtures/static/line_highlighter.html +++ /dev/null @@ -1,107 +0,0 @@ -
    -
    - -
    Line 1Line 2Line 3Line 4Line 5Line 6Line 7Line 8Line 9Line 10Line 11Line 12Line 13Line 14Line 15Line 16Line 17Line 18Line 19Line 20Line 21Line 22Line 23Line 24Line 25
    -
    -
    diff --git a/spec/javascripts/fixtures/static/line_highlighter.html.raw b/spec/javascripts/fixtures/static/line_highlighter.html.raw new file mode 100644 index 00000000000..897a25d6760 --- /dev/null +++ b/spec/javascripts/fixtures/static/line_highlighter.html.raw @@ -0,0 +1,107 @@ +
    +
    + +
    Line 1Line 2Line 3Line 4Line 5Line 6Line 7Line 8Line 9Line 10Line 11Line 12Line 13Line 14Line 15Line 16Line 17Line 18Line 19Line 20Line 21Line 22Line 23Line 24Line 25
    +
    +
    diff --git a/spec/javascripts/fixtures/static/linked_tabs.html b/spec/javascripts/fixtures/static/linked_tabs.html deleted file mode 100644 index c25463bf1db..00000000000 --- a/spec/javascripts/fixtures/static/linked_tabs.html +++ /dev/null @@ -1,20 +0,0 @@ - -
    -
    -Tab 1 Content -
    -
    -Tab 2 Content -
    -
    diff --git a/spec/javascripts/fixtures/static/linked_tabs.html.raw b/spec/javascripts/fixtures/static/linked_tabs.html.raw new file mode 100644 index 00000000000..c25463bf1db --- /dev/null +++ b/spec/javascripts/fixtures/static/linked_tabs.html.raw @@ -0,0 +1,20 @@ + +
    +
    +Tab 1 Content +
    +
    +Tab 2 Content +
    +
    diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html b/spec/javascripts/fixtures/static/merge_requests_show.html deleted file mode 100644 index e219d9462aa..00000000000 --- a/spec/javascripts/fixtures/static/merge_requests_show.html +++ /dev/null @@ -1,15 +0,0 @@ - -
    -
    -
    -
      -
    • - -Task List Item -
    • -
    - -
    -
    -
    -
    diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html.raw b/spec/javascripts/fixtures/static/merge_requests_show.html.raw new file mode 100644 index 00000000000..e219d9462aa --- /dev/null +++ b/spec/javascripts/fixtures/static/merge_requests_show.html.raw @@ -0,0 +1,15 @@ + +
    +
    +
    +
      +
    • + +Task List Item +
    • +
    + +
    +
    +
    +
    diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html b/spec/javascripts/fixtures/static/mini_dropdown_graph.html deleted file mode 100644 index cd0b8dec3fc..00000000000 --- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw b/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw new file mode 100644 index 00000000000..cd0b8dec3fc --- /dev/null +++ b/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw @@ -0,0 +1,13 @@ + diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html b/spec/javascripts/fixtures/static/notebook_viewer.html deleted file mode 100644 index 4bbb7bf1094..00000000000 --- a/spec/javascripts/fixtures/static/notebook_viewer.html +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html.raw b/spec/javascripts/fixtures/static/notebook_viewer.html.raw new file mode 100644 index 00000000000..4bbb7bf1094 --- /dev/null +++ b/spec/javascripts/fixtures/static/notebook_viewer.html.raw @@ -0,0 +1 @@ +
    diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html b/spec/javascripts/fixtures/static/oauth_remember_me.html deleted file mode 100644 index 9ba1ffc72fe..00000000000 --- a/spec/javascripts/fixtures/static/oauth_remember_me.html +++ /dev/null @@ -1,6 +0,0 @@ -
    - - - - -
    diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw b/spec/javascripts/fixtures/static/oauth_remember_me.html.raw new file mode 100644 index 00000000000..9ba1ffc72fe --- /dev/null +++ b/spec/javascripts/fixtures/static/oauth_remember_me.html.raw @@ -0,0 +1,6 @@ +
    + + + + +
    diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html b/spec/javascripts/fixtures/static/pdf_viewer.html deleted file mode 100644 index 350d35a262f..00000000000 --- a/spec/javascripts/fixtures/static/pdf_viewer.html +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html.raw b/spec/javascripts/fixtures/static/pdf_viewer.html.raw new file mode 100644 index 00000000000..350d35a262f --- /dev/null +++ b/spec/javascripts/fixtures/static/pdf_viewer.html.raw @@ -0,0 +1 @@ +
    diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html b/spec/javascripts/fixtures/static/pipeline_graph.html deleted file mode 100644 index 422372bb7d5..00000000000 --- a/spec/javascripts/fixtures/static/pipeline_graph.html +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html.raw b/spec/javascripts/fixtures/static/pipeline_graph.html.raw new file mode 100644 index 00000000000..422372bb7d5 --- /dev/null +++ b/spec/javascripts/fixtures/static/pipeline_graph.html.raw @@ -0,0 +1,24 @@ + diff --git a/spec/javascripts/fixtures/static/pipelines.html b/spec/javascripts/fixtures/static/pipelines.html deleted file mode 100644 index 42333f94f2f..00000000000 --- a/spec/javascripts/fixtures/static/pipelines.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    -
    diff --git a/spec/javascripts/fixtures/static/pipelines.html.raw b/spec/javascripts/fixtures/static/pipelines.html.raw new file mode 100644 index 00000000000..42333f94f2f --- /dev/null +++ b/spec/javascripts/fixtures/static/pipelines.html.raw @@ -0,0 +1,3 @@ +
    +
    +
    diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html b/spec/javascripts/fixtures/static/project_select_combo_button.html deleted file mode 100644 index 50c826051c0..00000000000 --- a/spec/javascripts/fixtures/static/project_select_combo_button.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - - - - - - - -
    diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw b/spec/javascripts/fixtures/static/project_select_combo_button.html.raw new file mode 100644 index 00000000000..50c826051c0 --- /dev/null +++ b/spec/javascripts/fixtures/static/project_select_combo_button.html.raw @@ -0,0 +1,9 @@ +
    + + + + + + + +
    diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html b/spec/javascripts/fixtures/static/search_autocomplete.html deleted file mode 100644 index 29db9020424..00000000000 --- a/spec/javascripts/fixtures/static/search_autocomplete.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html.raw b/spec/javascripts/fixtures/static/search_autocomplete.html.raw new file mode 100644 index 00000000000..29db9020424 --- /dev/null +++ b/spec/javascripts/fixtures/static/search_autocomplete.html.raw @@ -0,0 +1,15 @@ + diff --git a/spec/javascripts/fixtures/static/signin_tabs.html b/spec/javascripts/fixtures/static/signin_tabs.html deleted file mode 100644 index 7e66ab9394b..00000000000 --- a/spec/javascripts/fixtures/static/signin_tabs.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/signin_tabs.html.raw b/spec/javascripts/fixtures/static/signin_tabs.html.raw new file mode 100644 index 00000000000..7e66ab9394b --- /dev/null +++ b/spec/javascripts/fixtures/static/signin_tabs.html.raw @@ -0,0 +1,8 @@ + diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html b/spec/javascripts/fixtures/static/sketch_viewer.html deleted file mode 100644 index e25e554e568..00000000000 --- a/spec/javascripts/fixtures/static/sketch_viewer.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    -
    diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html.raw b/spec/javascripts/fixtures/static/sketch_viewer.html.raw new file mode 100644 index 00000000000..e25e554e568 --- /dev/null +++ b/spec/javascripts/fixtures/static/sketch_viewer.html.raw @@ -0,0 +1,3 @@ +
    +
    +
    diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb index 6e37a2e5a4c..b5f6620873b 100644 --- a/spec/javascripts/fixtures/todos.rb +++ b/spec/javascripts/fixtures/todos.rb @@ -26,7 +26,7 @@ describe 'Todos (JavaScript fixtures)' do sign_in(admin) end - it 'todos/todos.html' do |example| + it 'todos/todos.html.raw' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index 15866d65a4f..5cdbadef639 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -18,7 +18,7 @@ context 'U2F' do set_devise_mapping(context: @request) end - it 'u2f/authenticate.html' do |example| + it 'u2f/authenticate.html.raw' do |example| allow(controller).to receive(:find_user).and_return(user) post :create, params: { user: { login: user.username, password: user.password } } @@ -36,7 +36,7 @@ context 'U2F' do allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares') end - it 'u2f/register.html' do |example| + it 'u2f/register.html.raw' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 57e31d933ca..85083653db8 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -5,7 +5,7 @@ import GLDropdown from '~/gl_dropdown'; import '~/lib/utils/common_utils'; describe('glDropdown', function describeDropdown() { - preloadFixtures('static/gl_dropdown.html'); + preloadFixtures('static/gl_dropdown.html.raw'); loadJSONFixtures('projects.json'); const NON_SELECTABLE_CLASSES = @@ -64,7 +64,7 @@ describe('glDropdown', function describeDropdown() { } beforeEach(() => { - loadFixtures('static/gl_dropdown.html'); + loadFixtures('static/gl_dropdown.html.raw'); this.dropdownContainerElement = $('.dropdown.inline'); this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); this.projectsData = getJSONFixture('projects.json'); diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js index 294f219d6fe..b463c9afbee 100644 --- a/spec/javascripts/gl_field_errors_spec.js +++ b/spec/javascripts/gl_field_errors_spec.js @@ -4,10 +4,10 @@ import $ from 'jquery'; import GlFieldErrors from '~/gl_field_errors'; describe('GL Style Field Errors', function() { - preloadFixtures('static/gl_field_errors.html'); + preloadFixtures('static/gl_field_errors.html.raw'); beforeEach(function() { - loadFixtures('static/gl_field_errors.html'); + loadFixtures('static/gl_field_errors.html.raw'); const $form = $('form.gl-show-field-errors'); this.$form = $form; diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 0ddf589f368..2fe34e5a76f 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -3,7 +3,7 @@ import initTodoToggle from '~/header'; describe('Header', function() { const todosPendingCount = '.todos-count'; - const fixtureTemplate = 'issues/open-issue.html'; + const fixtureTemplate = 'issues/open-issue.html.raw'; function isTodosCountHidden() { return $(todosPendingCount).hasClass('hidden'); diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 069e2cb07b5..4f4c9a7b463 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils'; import IntegrationSettingsForm from '~/integrations/integration_settings_form'; describe('IntegrationSettingsForm', () => { - const FIXTURE = 'services/edit_service.html'; + const FIXTURE = 'services/edit_service.html.raw'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 11ab6c38a55..7be495d1d35 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -9,9 +9,9 @@ import '~/lib/utils/text_utility'; describe('Issue', function() { let $boxClosed, $boxOpen, $btn; - preloadFixtures('issues/closed-issue.html'); - preloadFixtures('issues/issue-with-task-list.html'); - preloadFixtures('issues/open-issue.html'); + preloadFixtures('issues/closed-issue.html.raw'); + preloadFixtures('issues/issue-with-task-list.html.raw'); + preloadFixtures('issues/open-issue.html.raw'); function expectErrorMessage() { const $flashMessage = $('div.flash-alert'); @@ -105,9 +105,9 @@ describe('Issue', function() { beforeEach(function() { if (isIssueInitiallyOpen) { - loadFixtures('issues/open-issue.html'); + loadFixtures('issues/open-issue.html.raw'); } else { - loadFixtures('issues/closed-issue.html'); + loadFixtures('issues/closed-issue.html.raw'); } mock = new MockAdapter(axios); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index ccf439aac74..e5678ee5379 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -16,10 +16,10 @@ let saveLabelCount = 0; let mock; describe('Issue dropdown sidebar', () => { - preloadFixtures('static/issue_sidebar_label.html'); + preloadFixtures('static/issue_sidebar_label.html.raw'); beforeEach(() => { - loadFixtures('static/issue_sidebar_label.html'); + loadFixtures('static/issue_sidebar_label.html.raw'); mock = new MockAdapter(axios); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index f3fb792c62d..cbdc1644430 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -11,11 +11,11 @@ const execImmediately = callback => { describe('LazyLoader', function() { let lazyLoader = null; - preloadFixtures('issues/issue_with_comment.html'); + preloadFixtures('issues/issue_with_comment.html.raw'); describe('without IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html'); + loadFixtures('issues/issue_with_comment.html.raw'); lazyLoader = new LazyLoader({ observerNode: 'foobar', @@ -131,7 +131,7 @@ describe('LazyLoader', function() { describe('with IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html'); + loadFixtures('issues/issue_with_comment.html.raw'); lazyLoader = new LazyLoader({ observerNode: 'foobar', diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index a75470b4db8..4eea364bd69 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -5,7 +5,7 @@ import LineHighlighter from '~/line_highlighter'; describe('LineHighlighter', function() { var clickLine; - preloadFixtures('static/line_highlighter.html'); + preloadFixtures('static/line_highlighter.html.raw'); clickLine = function(number, eventData = {}) { if ($.isEmptyObject(eventData)) { return $('#L' + number).click(); @@ -15,7 +15,7 @@ describe('LineHighlighter', function() { } }; beforeEach(function() { - loadFixtures('static/line_highlighter.html'); + loadFixtures('static/line_highlighter.html.raw'); this['class'] = new LineHighlighter(); this.css = this['class'].highlightLineClass; return (this.spies = { diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index 431798c6ec3..ab809930804 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -11,9 +11,9 @@ describe('MergeRequest', function() { describe('task lists', function() { let mock; - preloadFixtures('merge_requests/merge_request_with_task_list.html'); + preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html'); + loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); spyOn(axios, 'patch').and.callThrough(); mock = new MockAdapter(axios); @@ -125,7 +125,7 @@ describe('MergeRequest', function() { describe('hideCloseButton', () => { describe('merge request of another user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_with_task_list.html'); + loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); this.el = document.querySelector('.js-issuable-actions'); new MergeRequest(); // eslint-disable-line no-new MergeRequest.hideCloseButton(); @@ -145,7 +145,7 @@ describe('MergeRequest', function() { describe('merge request of current_user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html'); + loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); this.el = document.querySelector('.js-issuable-actions'); MergeRequest.hideCloseButton(); }); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 1295d900de7..c8df05eccf5 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -22,8 +22,8 @@ describe('MergeRequestTabs', function() { }; preloadFixtures( - 'merge_requests/merge_request_with_task_list.html', - 'merge_requests/diff_comment.html', + 'merge_requests/merge_request_with_task_list.html.raw', + 'merge_requests/diff_comment.html.raw', ); beforeEach(function() { @@ -48,7 +48,7 @@ describe('MergeRequestTabs', function() { var windowTarget = '_blank'; beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html'); + loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); tabUrl = $('.commits-tab a').attr('href'); }); diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js index aa4a376caf7..092ca9e1dab 100644 --- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js +++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js @@ -5,10 +5,10 @@ import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Mini Pipeline Graph Dropdown', () => { - preloadFixtures('static/mini_dropdown_graph.html'); + preloadFixtures('static/mini_dropdown_graph.html.raw'); beforeEach(() => { - loadFixtures('static/mini_dropdown_graph.html'); + loadFixtures('static/mini_dropdown_graph.html.raw'); }); describe('When is initialized', () => { diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 4e3140ce4f1..1d7b885e64f 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -3,7 +3,7 @@ import NewBranchForm from '~/new_branch_form'; describe('Branch', function() { describe('create a new branch', function() { - preloadFixtures('branches/new_branch.html'); + preloadFixtures('branches/new_branch.html.raw'); function fillNameWith(value) { $('.js-branch-name') @@ -16,7 +16,7 @@ describe('Branch', function() { } beforeEach(function() { - loadFixtures('branches/new_branch.html'); + loadFixtures('branches/new_branch.html.raw'); $('form').on('submit', function(e) { return e.preventDefault(); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 3d2c617e479..7c869d4c326 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -34,7 +34,7 @@ const htmlEscape = comment => { describe('Notes', function() { const FLASH_TYPE_ALERT = 'alert'; const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; - var fixture = 'snippets/show.html'; + var fixture = 'snippets/show.html.raw'; preloadFixtures(fixture); beforeEach(function() { diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js index 381be82697e..4125706a407 100644 --- a/spec/javascripts/oauth_remember_me_spec.js +++ b/spec/javascripts/oauth_remember_me_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me'; describe('OAuthRememberMe', () => { - preloadFixtures('static/oauth_remember_me.html'); + preloadFixtures('static/oauth_remember_me.html.raw'); beforeEach(() => { - loadFixtures('static/oauth_remember_me.html'); + loadFixtures('static/oauth_remember_me.html.raw'); new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents(); }); diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js index f7637964c60..23d07056925 100644 --- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js +++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js @@ -3,7 +3,7 @@ import '~/lib/utils/text_utility'; import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; describe('Abuse Reports', () => { - const FIXTURE = 'abuse_reports/abuse_reports_list.html'; + const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; const MAX_MESSAGE_LENGTH = 500; let $messages; diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js index 6a239e307e9..561bd2c96cb 100644 --- a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js +++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js @@ -5,7 +5,7 @@ import initUserInternalRegexPlaceholder, { } from '~/pages/admin/application_settings/account_and_limits'; describe('AccountAndLimits', () => { - const FIXTURE = 'application_settings/accounts_and_limit.html'; + const FIXTURE = 'application_settings/accounts_and_limit.html.raw'; let $userDefaultExternal; let $userInternalRegex; preloadFixtures(FIXTURE); diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js index 3896323eef7..5a849f34bc3 100644 --- a/spec/javascripts/pages/admin/users/new/index_spec.js +++ b/spec/javascripts/pages/admin/users/new/index_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import UserInternalRegexHandler from '~/pages/admin/users/new/index'; describe('UserInternalRegexHandler', () => { - const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; + const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw'; let $userExternal; let $userEmail; let $warningMessage; diff --git a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js index 1809e92e1d9..7a8227479d4 100644 --- a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js +++ b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment'; describe('preserve_url_fragment', () => { - preloadFixtures('sessions/new.html'); + preloadFixtures('sessions/new.html.raw'); beforeEach(() => { - loadFixtures('sessions/new.html'); + loadFixtures('sessions/new.html.raw'); }); it('adds the url fragment to all login and sign up form actions', () => { diff --git a/spec/javascripts/pipelines_spec.js b/spec/javascripts/pipelines_spec.js index 6d4d634c575..6b86f9ea437 100644 --- a/spec/javascripts/pipelines_spec.js +++ b/spec/javascripts/pipelines_spec.js @@ -1,10 +1,10 @@ import Pipelines from '~/pipelines'; describe('Pipelines', () => { - preloadFixtures('static/pipeline_graph.html'); + preloadFixtures('static/pipeline_graph.html.raw'); beforeEach(() => { - loadFixtures('static/pipeline_graph.html'); + loadFixtures('static/pipeline_graph.html.raw'); }); it('should be defined', () => { diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js index dc85292c23e..109a5000f5d 100644 --- a/spec/javascripts/project_select_combo_button_spec.js +++ b/spec/javascripts/project_select_combo_button_spec.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import ProjectSelectComboButton from '~/project_select_combo_button'; -const fixturePath = 'static/project_select_combo_button.html'; +const fixturePath = 'static/project_select_combo_button.html.raw'; describe('Project Select Combo Button', function() { preloadFixtures(fixturePath); diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js index dca3e1553b9..94e2f959d46 100644 --- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js +++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js @@ -5,7 +5,7 @@ import PANEL_STATE from '~/prometheus_metrics/constants'; import { metrics, missingVarMetrics } from './mock_data'; describe('PrometheusMetrics', () => { - const FIXTURE = 'services/prometheus/prometheus_service.html'; + const FIXTURE = 'services/prometheus/prometheus_service.html.raw'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/read_more_spec.js b/spec/javascripts/read_more_spec.js index d1d01272403..b1af0f80a50 100644 --- a/spec/javascripts/read_more_spec.js +++ b/spec/javascripts/read_more_spec.js @@ -1,7 +1,7 @@ import initReadMore from '~/read_more'; describe('Read more click-to-expand functionality', () => { - const fixtureName = 'projects/overview.html'; + const fixtureName = 'projects/overview.html.raw'; preloadFixtures(fixtureName); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 9565e3ce546..992e17978c1 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -23,7 +23,7 @@ const assertSidebarState = function(state) { describe('RightSidebar', function() { describe('fixture tests', () => { - const fixtureName = 'issues/open-issue.html'; + const fixtureName = 'issues/open-issue.html.raw'; preloadFixtures(fixtureName); loadJSONFixtures('todos/todos.json'); let mock; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index ce7fa7a52ae..7a4ca587313 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -126,9 +126,9 @@ describe('Search autocomplete dropdown', () => { expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created"); }; - preloadFixtures('static/search_autocomplete.html'); + preloadFixtures('static/search_autocomplete.html.raw'); beforeEach(function() { - loadFixtures('static/search_autocomplete.html'); + loadFixtures('static/search_autocomplete.html.raw'); window.gon = {}; window.gon.current_user_id = userId; diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js index 32f60508fa3..40bdbac7451 100644 --- a/spec/javascripts/search_spec.js +++ b/spec/javascripts/search_spec.js @@ -3,7 +3,7 @@ import Api from '~/api'; import Search from '~/pages/search/show/search'; describe('Search', () => { - const fixturePath = 'search/show.html'; + const fixturePath = 'search/show.html.raw'; const searchTerm = 'some search'; const fillDropdownInput = dropdownSelector => { const dropdownElement = document.querySelector(dropdownSelector).parentNode; diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js index 2c5d91a45bc..3b681a9ff28 100644 --- a/spec/javascripts/settings_panels_spec.js +++ b/spec/javascripts/settings_panels_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import initSettingsPanels from '~/settings_panels'; describe('Settings Panels', () => { - preloadFixtures('groups/edit.html'); + preloadFixtures('groups/edit.html.raw'); beforeEach(() => { - loadFixtures('groups/edit.html'); + loadFixtures('groups/edit.html.raw'); }); describe('initSettingsPane', () => { diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js index df7012bb659..3ca6ecaa938 100644 --- a/spec/javascripts/shortcuts_spec.js +++ b/spec/javascripts/shortcuts_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Shortcuts from '~/behaviors/shortcuts/shortcuts'; describe('Shortcuts', () => { - const fixtureName = 'snippets/show.html'; + const fixtureName = 'snippets/show.html.raw'; const createEvent = (type, target) => $.Event(type, { target, diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js index 016f5e033a5..3f0f67d71ca 100644 --- a/spec/javascripts/sidebar/sidebar_assignees_spec.js +++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js @@ -11,12 +11,12 @@ describe('sidebar assignees', () => { let vm; let mediator; let sidebarAssigneesEl; - preloadFixtures('issues/open-issue.html'); + preloadFixtures('issues/open-issue.html.raw'); beforeEach(() => { Vue.http.interceptors.push(Mock.sidebarMockInterceptor); - loadFixtures('issues/open-issue.html'); + loadFixtures('issues/open-issue.html.raw'); mediator = new SidebarMediator(Mock.mediator); spyOn(mediator, 'saveAssignees').and.callThrough(); diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js index ef5c774736b..52da6a79939 100644 --- a/spec/javascripts/signin_tabs_memoizer_spec.js +++ b/spec/javascripts/signin_tabs_memoizer_spec.js @@ -2,7 +2,7 @@ import AccessorUtilities from '~/lib/utils/accessor'; import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; describe('SigninTabsMemoizer', () => { - const fixtureTemplate = 'static/signin_tabs.html'; + const fixtureTemplate = 'static/signin_tabs.html.raw'; const tabSelector = 'ul.new-session-tabs'; const currentTabKey = 'current_signin_tab'; let memo; diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js index 802f54f6a7e..69e43274250 100644 --- a/spec/javascripts/todos_spec.js +++ b/spec/javascripts/todos_spec.js @@ -3,11 +3,11 @@ import Todos from '~/pages/dashboard/todos/index/todos'; import '~/lib/utils/common_utils'; describe('Todos', () => { - preloadFixtures('todos/todos.html'); + preloadFixtures('todos/todos.html.raw'); let todoItem; beforeEach(() => { - loadFixtures('todos/todos.html'); + loadFixtures('todos/todos.html.raw'); todoItem = document.querySelector('.todos-list .todo'); return new Todos(); diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index 8f9cb270729..ddb09811dda 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FAuthenticate', function() { - preloadFixtures('u2f/authenticate.html'); + preloadFixtures('u2f/authenticate.html.raw'); beforeEach(() => { - loadFixtures('u2f/authenticate.html'); + loadFixtures('u2f/authenticate.html.raw'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-authenticate-u2f'); this.component = new U2FAuthenticate( diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index a75ceca9f4c..261db3d66d7 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FRegister', function() { - preloadFixtures('u2f/register.html'); + preloadFixtures('u2f/register.html.raw'); beforeEach(done => { - loadFixtures('u2f/register.html'); + loadFixtures('u2f/register.html.raw'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-register-u2f'); this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token'); diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js index c0d5ee9c446..b174a51c1a0 100644 --- a/spec/javascripts/user_popovers_spec.js +++ b/spec/javascripts/user_popovers_spec.js @@ -2,7 +2,7 @@ import initUserPopovers from '~/user_popovers'; import UsersCache from '~/lib/utils/users_cache'; describe('User Popovers', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html'; + const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; preloadFixtures(fixtureTemplate); const selector = '.js-user-link'; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 368c997d318..30659ad16f3 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -415,7 +415,7 @@ describe('ReadyToMerge', () => { }); beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html'); + loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); }); it('should call start and stop polling when MR merged', done => { diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js index 852558a83bc..e8b41e8eeff 100644 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js @@ -17,7 +17,7 @@ const DEFAULT_PROPS = { const UserPopover = Vue.extend(userPopover); describe('User Popover Component', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html'; + const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; preloadFixtures(fixtureTemplate); let vm; diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index 8f662c71c7a..e5f1e6ae937 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -6,7 +6,7 @@ import ZenMode from '~/zen_mode'; describe('ZenMode', () => { let zen; let dropzoneForElementSpy; - const fixtureName = 'snippets/show.html'; + const fixtureName = 'snippets/show.html.raw'; preloadFixtures(fixtureName); diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 9cae8f934db..cceb179d53e 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -24,7 +24,7 @@ module JavaScriptFixturesHelpers # def clean_frontend_fixtures(directory_name) full_directory_name = File.expand_path(directory_name, fixture_root_path) - Dir[File.expand_path('*.html', full_directory_name)].each do |file_name| + Dir[File.expand_path('*.html.raw', full_directory_name)].each do |file_name| FileUtils.rm(file_name) end end -- cgit v1.2.1 From e64589a370c9536f8701c4b7124242b2d1016ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 22 Mar 2019 13:10:38 +0100 Subject: Refresh commit count after repository head changes --- spec/models/project_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index b2392f9521f..9d9611b1db0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2388,6 +2388,12 @@ describe Project do project.change_head(project.default_branch) end + it 'updates commit count' do + expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:commit_count]) + + project.change_head(project.default_branch) + end + it 'copies the gitattributes' do expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch) project.change_head(project.default_branch) -- cgit v1.2.1 From a01c738e1bb584fabf3a2dcdd9500f5543043f3e Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 22 Mar 2019 14:56:06 +0000 Subject: Freeze strings in email service specs --- spec/services/emails/create_service_spec.rb | 2 ++ spec/services/emails/destroy_service_spec.rb | 2 ++ 2 files changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb index 54692c88623..87f93ec97c9 100644 --- a/spec/services/emails/create_service_spec.rb +++ b/spec/services/emails/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Emails::CreateService do diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb index c3204fac3df..5abe8da2529 100644 --- a/spec/services/emails/destroy_service_spec.rb +++ b/spec/services/emails/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Emails::DestroyService do -- cgit v1.2.1 From 98194f176618fc2e83c3e8e89759356ca132f86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 22 Mar 2019 18:40:50 +0000 Subject: Set proper default-branch on GitHub Import --- .../gitlab/github_import/importer/repository_importer_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 47233ea6ee2..41810a8ec03 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -179,6 +179,17 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do describe '#import_repository' do it 'imports the repository' do + repo = double(:repo, default_branch: 'develop') + + expect(client) + .to receive(:repository) + .with('foo/bar') + .and_return(repo) + + expect(project) + .to receive(:change_head) + .with('develop') + expect(project) .to receive(:ensure_repository) -- cgit v1.2.1 From 12adb2a4bcca8b8ea07be2b9d120404e8a14a4be Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Sat, 23 Mar 2019 00:44:35 +0100 Subject: Show statistics also when repository is disabled --- spec/requests/api/projects_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 60d9d7fed13..4c3c088b307 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1183,6 +1183,16 @@ describe API::Projects do expect(response).to have_gitlab_http_status(200) expect(json_response).to include 'statistics' end + + it "includes statistics also when repository is disabled" do + project.add_developer(user) + project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) + + get api("/projects/#{project.id}", user), params: { statistics: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to include 'statistics' + end end it "includes import_error if user can admin project" do -- cgit v1.2.1 From d0336ae88f9ed7bfee1f74d8945e4f8770d0b9b0 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 23 Mar 2019 07:24:44 -0700 Subject: GitHub importer: Use the project creator to create branches from forks The project owner may actually be a group, in which case the branch creation would fail due to a lack of username. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59396 --- .../lib/gitlab/github_import/importer/pull_request_importer_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index 37c3fae7cb7..680de47de2b 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -273,10 +273,10 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi mr.state = 'opened' mr.save - # Ensure the project owner is creating the branches because the + # Ensure the project creator is creating the branches because the # merge request author may not have access to push to this - # repository. - allow(project.repository).to receive(:add_branch).with(project.owner, anything, anything).and_call_original + # repository. The project owner may also be a group. + allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original importer.insert_git_data(mr, exists) -- cgit v1.2.1 From 6d330015dfdb1979a0773c87c53b84cc86b28a6d Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sat, 23 Mar 2019 14:54:51 +0100 Subject: Copy vue_mount_component_helper.js to Jest --- .../frontend/helpers/vue_mount_component_helper.js | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 spec/frontend/helpers/vue_mount_component_helper.js (limited to 'spec') diff --git a/spec/frontend/helpers/vue_mount_component_helper.js b/spec/frontend/helpers/vue_mount_component_helper.js new file mode 100644 index 00000000000..6848c95d95d --- /dev/null +++ b/spec/frontend/helpers/vue_mount_component_helper.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; + +const mountComponent = (Component, props = {}, el = null) => + new Component({ + propsData: props, + }).$mount(el); + +export const createComponentWithStore = (Component, store, propsData = {}) => + new Component({ + store, + propsData, + }); + +export const mountComponentWithStore = (Component, { el, props, store }) => + new Component({ + store, + propsData: props || {}, + }).$mount(el); + +export const mountComponentWithSlots = (Component, { props, slots }) => { + const component = new Component({ + propsData: props || {}, + }); + + component.$slots = slots; + + return component.$mount(); +}; + +/** + * Mount a component with the given render method. + * + * This helps with inserting slots that need to be compiled. + */ +export const mountComponentWithRender = (render, el = null) => + mountComponent(Vue.extend({ render }), {}, el); + +export default mountComponent; -- cgit v1.2.1 From 514ee63826e47229bfd03bdbb740f2dd1eae1d03 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sat, 23 Mar 2019 17:52:35 +0100 Subject: Move some tests from Karma to Jest --- spec/frontend/behaviors/secret_values_spec.js | 230 +++++++++++++++++++ spec/frontend/blob/blob_fork_suggestion_spec.js | 39 ++++ spec/frontend/boards/modal_store_spec.js | 134 ++++++++++++ .../limit_warning_component_spec.js | 42 ++++ spec/frontend/diffs/components/diff_stats_spec.js | 33 +++ spec/frontend/diffs/components/edit_button_spec.js | 61 ++++++ .../diffs/components/hidden_files_warning_spec.js | 48 ++++ spec/frontend/diffs/components/no_changes_spec.js | 40 ++++ .../components/error_tracking_list_spec.js | 118 ++++++++++ .../frontend/error_tracking/store/mutation_spec.js | 36 +++ .../filtered_search_token_keys_spec.js | 141 ++++++++++++ .../services/recent_searches_service_error_spec.js | 18 ++ .../stores/recent_searches_store_spec.js | 53 +++++ spec/frontend/frequent_items/store/getters_spec.js | 24 ++ spec/frontend/ide/lib/common/disposable_spec.js | 44 ++++ spec/frontend/ide/lib/diff/diff_spec.js | 80 +++++++ spec/frontend/ide/lib/editor_options_spec.js | 11 + spec/frontend/ide/lib/files_spec.js | 77 +++++++ .../ide/stores/modules/commit/mutations_spec.js | 42 ++++ .../stores/modules/file_templates/getters_spec.js | 59 +++++ .../modules/file_templates/mutations_spec.js | 69 ++++++ .../ide/stores/modules/pane/getters_spec.js | 55 +++++ .../ide/stores/modules/pane/mutations_spec.js | 42 ++++ .../ide/stores/modules/pipelines/getters_spec.js | 40 ++++ spec/frontend/ide/stores/mutations/branch_spec.js | 40 ++++ .../ide/stores/mutations/merge_request_spec.js | 67 ++++++ spec/frontend/image_diff/view_types_spec.js | 24 ++ .../frontend/import_projects/store/getters_spec.js | 83 +++++++ .../import_projects/store/mutations_spec.js | 34 +++ spec/frontend/jobs/components/empty_state_spec.js | 93 ++++++++ spec/frontend/jobs/components/erased_block_spec.js | 56 +++++ .../jobs/components/sidebar_detail_row_spec.js | 61 ++++++ spec/frontend/jobs/components/stuck_block_spec.js | 81 +++++++ spec/frontend/jobs/store/getters_spec.js | 243 +++++++++++++++++++++ spec/frontend/jobs/store/mutations_spec.js | 230 +++++++++++++++++++ spec/frontend/labels_select_spec.js | 52 +++++ spec/frontend/lib/utils/cache_spec.js | 65 ++++++ spec/frontend/lib/utils/grammar_spec.js | 35 +++ spec/frontend/lib/utils/image_utility_spec.js | 32 +++ spec/frontend/lib/utils/number_utility_spec.js | 101 +++++++++ spec/frontend/lib/utils/text_utility_spec.js | 154 +++++++++++++ spec/frontend/locale/ensure_single_line_spec.js | 38 ++++ spec/frontend/locale/sprintf_spec.js | 74 +++++++ spec/frontend/notebook/lib/highlight_spec.js | 15 ++ .../discussion_reply_placeholder_spec.js | 34 +++ .../components/discussion_resolve_button_spec.js | 74 +++++++ .../notes/components/note_attachment_spec.js | 23 ++ .../notes/components/note_edited_text_spec.js | 47 ++++ .../services/performance_bar_service_spec.js | 68 ++++++ spec/frontend/pipelines/blank_state_spec.js | 25 +++ spec/frontend/pipelines/empty_state_spec.js | 58 +++++ spec/frontend/pipelines/pipeline_store_spec.js | 27 +++ spec/frontend/pipelines/pipelines_store_spec.js | 77 +++++++ spec/frontend/registry/getters_spec.js | 46 ++++ .../reports/components/report_link_spec.js | 69 ++++++ spec/frontend/reports/store/utils_spec.js | 138 ++++++++++++ .../sidebar/confidential_edit_buttons_spec.js | 35 +++ .../sidebar/confidential_edit_form_buttons_spec.js | 35 +++ spec/frontend/sidebar/lock/edit_form_spec.js | 37 ++++ spec/frontend/u2f/util_spec.js | 61 ++++++ .../components/mr_widget_container_spec.js | 51 +++++ .../components/mr_widget_icon_spec.js | 30 +++ .../components/states/commit_edit_spec.js | 85 +++++++ .../mr_widget_commit_message_dropdown_spec.js | 61 ++++++ .../states/mr_widget_commits_header_spec.js | 136 ++++++++++++ .../vue_mr_widget/stores/get_state_key_spec.js | 103 +++++++++ .../frontend/vue_shared/components/callout_spec.js | 66 ++++++ .../vue_shared/components/code_block_spec.js | 33 +++ .../diff_viewer/viewers/mode_changed_spec.js | 23 ++ .../vue_shared/components/identicon_spec.js | 65 ++++++ .../components/lib/utils/dom_utils_spec.js | 13 ++ .../vue_shared/components/pagination_links_spec.js | 59 +++++ .../vue_shared/components/time_ago_tooltip_spec.js | 44 ++++ .../vuex_shared/modules/modal/mutations_spec.js | 49 +++++ spec/javascripts/behaviors/secret_values_spec.js | 230 ------------------- spec/javascripts/blob/blob_fork_suggestion_spec.js | 39 ---- spec/javascripts/boards/modal_store_spec.js | 134 ------------ .../limit_warning_component_spec.js | 42 ---- .../diffs/components/diff_stats_spec.js | 33 --- .../diffs/components/edit_button_spec.js | 61 ------ .../diffs/components/hidden_files_warning_spec.js | 48 ---- .../diffs/components/no_changes_spec.js | 40 ---- .../components/error_tracking_list_spec.js | 118 ---------- .../error_tracking/store/mutation_spec.js | 36 --- .../filtered_search_token_keys_spec.js | 141 ------------ .../services/recent_searches_service_error_spec.js | 18 -- .../stores/recent_searches_store_spec.js | 53 ----- .../frequent_items/store/getters_spec.js | 24 -- spec/javascripts/ide/lib/common/disposable_spec.js | 44 ---- spec/javascripts/ide/lib/diff/diff_spec.js | 80 ------- spec/javascripts/ide/lib/editor_options_spec.js | 11 - spec/javascripts/ide/lib/files_spec.js | 77 ------- .../ide/stores/modules/commit/mutations_spec.js | 42 ---- .../stores/modules/file_templates/getters_spec.js | 59 ----- .../modules/file_templates/mutations_spec.js | 69 ------ .../ide/stores/modules/pane/getters_spec.js | 55 ----- .../ide/stores/modules/pane/mutations_spec.js | 42 ---- .../ide/stores/modules/pipelines/getters_spec.js | 40 ---- .../ide/stores/mutations/branch_spec.js | 40 ---- .../ide/stores/mutations/merge_request_spec.js | 67 ------ spec/javascripts/image_diff/view_types_spec.js | 24 -- .../import_projects/store/getters_spec.js | 83 ------- .../import_projects/store/mutations_spec.js | 34 --- .../jobs/components/empty_state_spec.js | 93 -------- .../jobs/components/erased_block_spec.js | 56 ----- .../jobs/components/sidebar_detail_row_spec.js | 61 ------ .../jobs/components/stuck_block_spec.js | 81 ------- spec/javascripts/jobs/store/getters_spec.js | 243 --------------------- spec/javascripts/jobs/store/mutations_spec.js | 230 ------------------- spec/javascripts/labels_select_spec.js | 52 ----- spec/javascripts/lib/utils/cache_spec.js | 65 ------ spec/javascripts/lib/utils/grammar_spec.js | 35 --- spec/javascripts/lib/utils/image_utility_spec.js | 32 --- spec/javascripts/lib/utils/number_utility_spec.js | 101 --------- spec/javascripts/lib/utils/text_utility_spec.js | 154 ------------- spec/javascripts/locale/ensure_single_line_spec.js | 38 ---- spec/javascripts/locale/sprintf_spec.js | 74 ------- spec/javascripts/notebook/lib/highlight_spec.js | 15 -- .../discussion_reply_placeholder_spec.js | 34 --- .../components/discussion_resolve_button_spec.js | 74 ------- .../notes/components/note_attachment_spec.js | 23 -- .../notes/components/note_edited_text_spec.js | 47 ---- .../services/performance_bar_service_spec.js | 68 ------ spec/javascripts/pipelines/blank_state_spec.js | 25 --- spec/javascripts/pipelines/empty_state_spec.js | 58 ----- spec/javascripts/pipelines/pipeline_store_spec.js | 27 --- spec/javascripts/pipelines/pipelines_store_spec.js | 77 ------- spec/javascripts/registry/getters_spec.js | 46 ---- .../reports/components/report_link_spec.js | 69 ------ spec/javascripts/reports/store/utils_spec.js | 138 ------------ .../sidebar/confidential_edit_buttons_spec.js | 35 --- .../sidebar/confidential_edit_form_buttons_spec.js | 35 --- spec/javascripts/sidebar/lock/edit_form_spec.js | 37 ---- spec/javascripts/u2f/util_spec.js | 61 ------ .../components/mr_widget_container_spec.js | 51 ----- .../components/mr_widget_icon_spec.js | 30 --- .../components/states/commit_edit_spec.js | 85 ------- .../mr_widget_commit_message_dropdown_spec.js | 61 ------ .../states/mr_widget_commits_header_spec.js | 136 ------------ .../vue_mr_widget/stores/get_state_key_spec.js | 103 --------- .../vue_shared/components/callout_spec.js | 66 ------ .../vue_shared/components/code_block_spec.js | 33 --- .../diff_viewer/viewers/mode_changed_spec.js | 23 -- .../vue_shared/components/identicon_spec.js | 65 ------ .../components/lib/utils/dom_utils_spec.js | 13 -- .../vue_shared/components/pagination_links_spec.js | 59 ----- .../vue_shared/components/time_ago_tooltip_spec.js | 44 ---- .../vuex_shared/modules/modal/mutations_spec.js | 49 ----- 148 files changed, 4786 insertions(+), 4786 deletions(-) create mode 100644 spec/frontend/behaviors/secret_values_spec.js create mode 100644 spec/frontend/blob/blob_fork_suggestion_spec.js create mode 100644 spec/frontend/boards/modal_store_spec.js create mode 100644 spec/frontend/cycle_analytics/limit_warning_component_spec.js create mode 100644 spec/frontend/diffs/components/diff_stats_spec.js create mode 100644 spec/frontend/diffs/components/edit_button_spec.js create mode 100644 spec/frontend/diffs/components/hidden_files_warning_spec.js create mode 100644 spec/frontend/diffs/components/no_changes_spec.js create mode 100644 spec/frontend/error_tracking/components/error_tracking_list_spec.js create mode 100644 spec/frontend/error_tracking/store/mutation_spec.js create mode 100644 spec/frontend/filtered_search/filtered_search_token_keys_spec.js create mode 100644 spec/frontend/filtered_search/services/recent_searches_service_error_spec.js create mode 100644 spec/frontend/filtered_search/stores/recent_searches_store_spec.js create mode 100644 spec/frontend/frequent_items/store/getters_spec.js create mode 100644 spec/frontend/ide/lib/common/disposable_spec.js create mode 100644 spec/frontend/ide/lib/diff/diff_spec.js create mode 100644 spec/frontend/ide/lib/editor_options_spec.js create mode 100644 spec/frontend/ide/lib/files_spec.js create mode 100644 spec/frontend/ide/stores/modules/commit/mutations_spec.js create mode 100644 spec/frontend/ide/stores/modules/file_templates/getters_spec.js create mode 100644 spec/frontend/ide/stores/modules/file_templates/mutations_spec.js create mode 100644 spec/frontend/ide/stores/modules/pane/getters_spec.js create mode 100644 spec/frontend/ide/stores/modules/pane/mutations_spec.js create mode 100644 spec/frontend/ide/stores/modules/pipelines/getters_spec.js create mode 100644 spec/frontend/ide/stores/mutations/branch_spec.js create mode 100644 spec/frontend/ide/stores/mutations/merge_request_spec.js create mode 100644 spec/frontend/image_diff/view_types_spec.js create mode 100644 spec/frontend/import_projects/store/getters_spec.js create mode 100644 spec/frontend/import_projects/store/mutations_spec.js create mode 100644 spec/frontend/jobs/components/empty_state_spec.js create mode 100644 spec/frontend/jobs/components/erased_block_spec.js create mode 100644 spec/frontend/jobs/components/sidebar_detail_row_spec.js create mode 100644 spec/frontend/jobs/components/stuck_block_spec.js create mode 100644 spec/frontend/jobs/store/getters_spec.js create mode 100644 spec/frontend/jobs/store/mutations_spec.js create mode 100644 spec/frontend/labels_select_spec.js create mode 100644 spec/frontend/lib/utils/cache_spec.js create mode 100644 spec/frontend/lib/utils/grammar_spec.js create mode 100644 spec/frontend/lib/utils/image_utility_spec.js create mode 100644 spec/frontend/lib/utils/number_utility_spec.js create mode 100644 spec/frontend/lib/utils/text_utility_spec.js create mode 100644 spec/frontend/locale/ensure_single_line_spec.js create mode 100644 spec/frontend/locale/sprintf_spec.js create mode 100644 spec/frontend/notebook/lib/highlight_spec.js create mode 100644 spec/frontend/notes/components/discussion_reply_placeholder_spec.js create mode 100644 spec/frontend/notes/components/discussion_resolve_button_spec.js create mode 100644 spec/frontend/notes/components/note_attachment_spec.js create mode 100644 spec/frontend/notes/components/note_edited_text_spec.js create mode 100644 spec/frontend/performance_bar/services/performance_bar_service_spec.js create mode 100644 spec/frontend/pipelines/blank_state_spec.js create mode 100644 spec/frontend/pipelines/empty_state_spec.js create mode 100644 spec/frontend/pipelines/pipeline_store_spec.js create mode 100644 spec/frontend/pipelines/pipelines_store_spec.js create mode 100644 spec/frontend/registry/getters_spec.js create mode 100644 spec/frontend/reports/components/report_link_spec.js create mode 100644 spec/frontend/reports/store/utils_spec.js create mode 100644 spec/frontend/sidebar/confidential_edit_buttons_spec.js create mode 100644 spec/frontend/sidebar/confidential_edit_form_buttons_spec.js create mode 100644 spec/frontend/sidebar/lock/edit_form_spec.js create mode 100644 spec/frontend/u2f/util_spec.js create mode 100644 spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js create mode 100644 spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js create mode 100644 spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js create mode 100644 spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js create mode 100644 spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js create mode 100644 spec/frontend/vue_mr_widget/stores/get_state_key_spec.js create mode 100644 spec/frontend/vue_shared/components/callout_spec.js create mode 100644 spec/frontend/vue_shared/components/code_block_spec.js create mode 100644 spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js create mode 100644 spec/frontend/vue_shared/components/identicon_spec.js create mode 100644 spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js create mode 100644 spec/frontend/vue_shared/components/pagination_links_spec.js create mode 100644 spec/frontend/vue_shared/components/time_ago_tooltip_spec.js create mode 100644 spec/frontend/vuex_shared/modules/modal/mutations_spec.js delete mode 100644 spec/javascripts/behaviors/secret_values_spec.js delete mode 100644 spec/javascripts/blob/blob_fork_suggestion_spec.js delete mode 100644 spec/javascripts/boards/modal_store_spec.js delete mode 100644 spec/javascripts/cycle_analytics/limit_warning_component_spec.js delete mode 100644 spec/javascripts/diffs/components/diff_stats_spec.js delete mode 100644 spec/javascripts/diffs/components/edit_button_spec.js delete mode 100644 spec/javascripts/diffs/components/hidden_files_warning_spec.js delete mode 100644 spec/javascripts/diffs/components/no_changes_spec.js delete mode 100644 spec/javascripts/error_tracking/components/error_tracking_list_spec.js delete mode 100644 spec/javascripts/error_tracking/store/mutation_spec.js delete mode 100644 spec/javascripts/filtered_search/filtered_search_token_keys_spec.js delete mode 100644 spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js delete mode 100644 spec/javascripts/filtered_search/stores/recent_searches_store_spec.js delete mode 100644 spec/javascripts/frequent_items/store/getters_spec.js delete mode 100644 spec/javascripts/ide/lib/common/disposable_spec.js delete mode 100644 spec/javascripts/ide/lib/diff/diff_spec.js delete mode 100644 spec/javascripts/ide/lib/editor_options_spec.js delete mode 100644 spec/javascripts/ide/lib/files_spec.js delete mode 100644 spec/javascripts/ide/stores/modules/commit/mutations_spec.js delete mode 100644 spec/javascripts/ide/stores/modules/file_templates/getters_spec.js delete mode 100644 spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js delete mode 100644 spec/javascripts/ide/stores/modules/pane/getters_spec.js delete mode 100644 spec/javascripts/ide/stores/modules/pane/mutations_spec.js delete mode 100644 spec/javascripts/ide/stores/modules/pipelines/getters_spec.js delete mode 100644 spec/javascripts/ide/stores/mutations/branch_spec.js delete mode 100644 spec/javascripts/ide/stores/mutations/merge_request_spec.js delete mode 100644 spec/javascripts/image_diff/view_types_spec.js delete mode 100644 spec/javascripts/import_projects/store/getters_spec.js delete mode 100644 spec/javascripts/import_projects/store/mutations_spec.js delete mode 100644 spec/javascripts/jobs/components/empty_state_spec.js delete mode 100644 spec/javascripts/jobs/components/erased_block_spec.js delete mode 100644 spec/javascripts/jobs/components/sidebar_detail_row_spec.js delete mode 100644 spec/javascripts/jobs/components/stuck_block_spec.js delete mode 100644 spec/javascripts/jobs/store/getters_spec.js delete mode 100644 spec/javascripts/jobs/store/mutations_spec.js delete mode 100644 spec/javascripts/labels_select_spec.js delete mode 100644 spec/javascripts/lib/utils/cache_spec.js delete mode 100644 spec/javascripts/lib/utils/grammar_spec.js delete mode 100644 spec/javascripts/lib/utils/image_utility_spec.js delete mode 100644 spec/javascripts/lib/utils/number_utility_spec.js delete mode 100644 spec/javascripts/lib/utils/text_utility_spec.js delete mode 100644 spec/javascripts/locale/ensure_single_line_spec.js delete mode 100644 spec/javascripts/locale/sprintf_spec.js delete mode 100644 spec/javascripts/notebook/lib/highlight_spec.js delete mode 100644 spec/javascripts/notes/components/discussion_reply_placeholder_spec.js delete mode 100644 spec/javascripts/notes/components/discussion_resolve_button_spec.js delete mode 100644 spec/javascripts/notes/components/note_attachment_spec.js delete mode 100644 spec/javascripts/notes/components/note_edited_text_spec.js delete mode 100644 spec/javascripts/performance_bar/services/performance_bar_service_spec.js delete mode 100644 spec/javascripts/pipelines/blank_state_spec.js delete mode 100644 spec/javascripts/pipelines/empty_state_spec.js delete mode 100644 spec/javascripts/pipelines/pipeline_store_spec.js delete mode 100644 spec/javascripts/pipelines/pipelines_store_spec.js delete mode 100644 spec/javascripts/registry/getters_spec.js delete mode 100644 spec/javascripts/reports/components/report_link_spec.js delete mode 100644 spec/javascripts/reports/store/utils_spec.js delete mode 100644 spec/javascripts/sidebar/confidential_edit_buttons_spec.js delete mode 100644 spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js delete mode 100644 spec/javascripts/sidebar/lock/edit_form_spec.js delete mode 100644 spec/javascripts/u2f/util_spec.js delete mode 100644 spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js delete mode 100644 spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js delete mode 100644 spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js delete mode 100644 spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js delete mode 100644 spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js delete mode 100644 spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js delete mode 100644 spec/javascripts/vue_shared/components/callout_spec.js delete mode 100644 spec/javascripts/vue_shared/components/code_block_spec.js delete mode 100644 spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js delete mode 100644 spec/javascripts/vue_shared/components/identicon_spec.js delete mode 100644 spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js delete mode 100644 spec/javascripts/vue_shared/components/pagination_links_spec.js delete mode 100644 spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js delete mode 100644 spec/javascripts/vuex_shared/modules/modal/mutations_spec.js (limited to 'spec') diff --git a/spec/frontend/behaviors/secret_values_spec.js b/spec/frontend/behaviors/secret_values_spec.js new file mode 100644 index 00000000000..5aaab093c0c --- /dev/null +++ b/spec/frontend/behaviors/secret_values_spec.js @@ -0,0 +1,230 @@ +import SecretValues from '~/behaviors/secret_values'; + +function generateValueMarkup( + secret, + valueClass = 'js-secret-value', + placeholderClass = 'js-secret-value-placeholder', +) { + return ` +
    + *** +
    + + `; +} + +function generateFixtureMarkup(secrets, isRevealed, valueClass, placeholderClass) { + return ` +
    + ${secrets.map(secret => generateValueMarkup(secret, valueClass, placeholderClass)).join('')} + +
    + `; +} + +function setupSecretFixture( + secrets, + isRevealed, + valueClass = 'js-secret-value', + placeholderClass = 'js-secret-value-placeholder', +) { + const wrapper = document.createElement('div'); + wrapper.innerHTML = generateFixtureMarkup(secrets, isRevealed, valueClass, placeholderClass); + + const secretValues = new SecretValues({ + container: wrapper.querySelector('.js-secret-container'), + valueSelector: `.${valueClass}`, + placeholderSelector: `.${placeholderClass}`, + }); + secretValues.init(); + + return wrapper; +} + +describe('setupSecretValues', () => { + describe('with a single secret', () => { + const secrets = ['mysecret123']; + + it('should have correct "Reveal" label when values are hidden', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Reveal value'); + }); + + it('should have correct "Hide" label when values are shown', () => { + const wrapper = setupSecretFixture(secrets, true); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Hide value'); + }); + + it('should have value hidden initially', () => { + const wrapper = setupSecretFixture(secrets, false); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + expect(values.length).toEqual(1); + expect(values[0].classList.contains('hide')).toEqual(true); + expect(placeholders.length).toEqual(1); + expect(placeholders[0].classList.contains('hide')).toEqual(false); + }); + + it('should toggle value and placeholder', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + revealButton.click(); + + expect(values.length).toEqual(1); + expect(values[0].classList.contains('hide')).toEqual(false); + expect(placeholders.length).toEqual(1); + expect(placeholders[0].classList.contains('hide')).toEqual(true); + + revealButton.click(); + + expect(values.length).toEqual(1); + expect(values[0].classList.contains('hide')).toEqual(true); + expect(placeholders.length).toEqual(1); + expect(placeholders[0].classList.contains('hide')).toEqual(false); + }); + }); + + describe('with a multiple secrets', () => { + const secrets = ['mysecret123', 'happygoat456', 'tanuki789']; + + it('should have correct "Reveal" label when values are hidden', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Reveal values'); + }); + + it('should have correct "Hide" label when values are shown', () => { + const wrapper = setupSecretFixture(secrets, true); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Hide values'); + }); + + it('should have all values hidden initially', () => { + const wrapper = setupSecretFixture(secrets, false); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + expect(values.length).toEqual(3); + values.forEach(value => { + expect(value.classList.contains('hide')).toEqual(true); + }); + + expect(placeholders.length).toEqual(3); + placeholders.forEach(placeholder => { + expect(placeholder.classList.contains('hide')).toEqual(false); + }); + }); + + it('should toggle values and placeholders', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + revealButton.click(); + + expect(values.length).toEqual(3); + values.forEach(value => { + expect(value.classList.contains('hide')).toEqual(false); + }); + + expect(placeholders.length).toEqual(3); + placeholders.forEach(placeholder => { + expect(placeholder.classList.contains('hide')).toEqual(true); + }); + + revealButton.click(); + + expect(values.length).toEqual(3); + values.forEach(value => { + expect(value.classList.contains('hide')).toEqual(true); + }); + + expect(placeholders.length).toEqual(3); + placeholders.forEach(placeholder => { + expect(placeholder.classList.contains('hide')).toEqual(false); + }); + }); + }); + + describe('with dynamic secrets', () => { + const secrets = ['mysecret123', 'happygoat456', 'tanuki789']; + + it('should toggle values and placeholders', () => { + const wrapper = setupSecretFixture(secrets, false); + // Insert the new dynamic row + wrapper + .querySelector('.js-secret-container') + .insertAdjacentHTML('afterbegin', generateValueMarkup('foobarbazdynamic')); + + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + revealButton.click(); + + expect(values.length).toEqual(4); + values.forEach(value => { + expect(value.classList.contains('hide')).toEqual(false); + }); + + expect(placeholders.length).toEqual(4); + placeholders.forEach(placeholder => { + expect(placeholder.classList.contains('hide')).toEqual(true); + }); + + revealButton.click(); + + expect(values.length).toEqual(4); + values.forEach(value => { + expect(value.classList.contains('hide')).toEqual(true); + }); + + expect(placeholders.length).toEqual(4); + placeholders.forEach(placeholder => { + expect(placeholder.classList.contains('hide')).toEqual(false); + }); + }); + }); + + describe('selector options', () => { + const secrets = ['mysecret123']; + + it('should respect `valueSelector` and `placeholderSelector` options', () => { + const valueClass = 'js-some-custom-placeholder-selector'; + const placeholderClass = 'js-some-custom-value-selector'; + + const wrapper = setupSecretFixture(secrets, false, valueClass, placeholderClass); + const values = wrapper.querySelectorAll(`.${valueClass}`); + const placeholders = wrapper.querySelectorAll(`.${placeholderClass}`); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(values.length).toEqual(1); + expect(placeholders.length).toEqual(1); + + revealButton.click(); + + expect(values.length).toEqual(1); + expect(values[0].classList.contains('hide')).toEqual(false); + expect(placeholders.length).toEqual(1); + expect(placeholders[0].classList.contains('hide')).toEqual(true); + }); + }); +}); diff --git a/spec/frontend/blob/blob_fork_suggestion_spec.js b/spec/frontend/blob/blob_fork_suggestion_spec.js new file mode 100644 index 00000000000..9b81b7e6f92 --- /dev/null +++ b/spec/frontend/blob/blob_fork_suggestion_spec.js @@ -0,0 +1,39 @@ +import BlobForkSuggestion from '~/blob/blob_fork_suggestion'; + +describe('BlobForkSuggestion', () => { + let blobForkSuggestion; + + const openButton = document.createElement('div'); + const forkButton = document.createElement('a'); + const cancelButton = document.createElement('div'); + const suggestionSection = document.createElement('div'); + const actionTextPiece = document.createElement('div'); + + beforeEach(() => { + blobForkSuggestion = new BlobForkSuggestion({ + openButtons: openButton, + forkButtons: forkButton, + cancelButtons: cancelButton, + suggestionSections: suggestionSection, + actionTextPieces: actionTextPiece, + }).init(); + }); + + afterEach(() => { + blobForkSuggestion.destroy(); + }); + + it('showSuggestionSection', () => { + blobForkSuggestion.showSuggestionSection('/foo', 'foo'); + + expect(suggestionSection.classList.contains('hidden')).toEqual(false); + expect(forkButton.getAttribute('href')).toEqual('/foo'); + expect(actionTextPiece.textContent).toEqual('foo'); + }); + + it('hideSuggestionSection', () => { + blobForkSuggestion.hideSuggestionSection(); + + expect(suggestionSection.classList.contains('hidden')).toEqual(true); + }); +}); diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js new file mode 100644 index 00000000000..3257a3fb8a3 --- /dev/null +++ b/spec/frontend/boards/modal_store_spec.js @@ -0,0 +1,134 @@ +/* global ListIssue */ + +import '~/vue_shared/models/label'; +import '~/vue_shared/models/assignee'; +import '~/boards/models/issue'; +import '~/boards/models/list'; +import Store from '~/boards/stores/modal_store'; + +describe('Modal store', () => { + let issue; + let issue2; + + beforeEach(() => { + // Set up default state + Store.store.issues = []; + Store.store.selectedIssues = []; + + issue = new ListIssue({ + title: 'Testing', + id: 1, + iid: 1, + confidential: false, + labels: [], + assignees: [], + }); + issue2 = new ListIssue({ + title: 'Testing', + id: 1, + iid: 2, + confidential: false, + labels: [], + assignees: [], + }); + Store.store.issues.push(issue); + Store.store.issues.push(issue2); + }); + + it('returns selected count', () => { + expect(Store.selectedCount()).toBe(0); + }); + + it('toggles the issue as selected', () => { + Store.toggleIssue(issue); + + expect(issue.selected).toBe(true); + expect(Store.selectedCount()).toBe(1); + }); + + it('toggles the issue as un-selected', () => { + Store.toggleIssue(issue); + Store.toggleIssue(issue); + + expect(issue.selected).toBe(false); + expect(Store.selectedCount()).toBe(0); + }); + + it('toggles all issues as selected', () => { + Store.toggleAll(); + + expect(issue.selected).toBe(true); + expect(issue2.selected).toBe(true); + expect(Store.selectedCount()).toBe(2); + }); + + it('toggles all issues as un-selected', () => { + Store.toggleAll(); + Store.toggleAll(); + + expect(issue.selected).toBe(false); + expect(issue2.selected).toBe(false); + expect(Store.selectedCount()).toBe(0); + }); + + it('toggles all if a single issue is selected', () => { + Store.toggleIssue(issue); + Store.toggleAll(); + + expect(issue.selected).toBe(true); + expect(issue2.selected).toBe(true); + expect(Store.selectedCount()).toBe(2); + }); + + it('adds issue to selected array', () => { + issue.selected = true; + Store.addSelectedIssue(issue); + + expect(Store.selectedCount()).toBe(1); + }); + + it('removes issue from selected array', () => { + Store.addSelectedIssue(issue); + Store.removeSelectedIssue(issue); + + expect(Store.selectedCount()).toBe(0); + }); + + it('returns selected issue index if present', () => { + Store.toggleIssue(issue); + + expect(Store.selectedIssueIndex(issue)).toBe(0); + }); + + it('returns -1 if issue is not selected', () => { + expect(Store.selectedIssueIndex(issue)).toBe(-1); + }); + + it('finds the selected issue', () => { + Store.toggleIssue(issue); + + expect(Store.findSelectedIssue(issue)).toBe(issue); + }); + + it('does not find a selected issue', () => { + expect(Store.findSelectedIssue(issue)).toBe(undefined); + }); + + it('does not remove from selected issue if tab is not all', () => { + Store.store.activeTab = 'selected'; + + Store.toggleIssue(issue); + Store.toggleIssue(issue); + + expect(Store.store.selectedIssues.length).toBe(1); + expect(Store.selectedCount()).toBe(0); + }); + + it('gets selected issue array with only selected issues', () => { + Store.toggleIssue(issue); + Store.toggleIssue(issue2); + Store.toggleIssue(issue2); + + expect(Store.getSelectedIssues().length).toBe(1); + }); +}); diff --git a/spec/frontend/cycle_analytics/limit_warning_component_spec.js b/spec/frontend/cycle_analytics/limit_warning_component_spec.js new file mode 100644 index 00000000000..13e9fe00a00 --- /dev/null +++ b/spec/frontend/cycle_analytics/limit_warning_component_spec.js @@ -0,0 +1,42 @@ +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; +import limitWarningComp from '~/cycle_analytics/components/limit_warning_component.vue'; + +Vue.use(Translate); + +describe('Limit warning component', () => { + let component; + let LimitWarningComponent; + + beforeEach(() => { + LimitWarningComponent = Vue.extend(limitWarningComp); + }); + + it('should not render if count is not exactly than 50', () => { + component = new LimitWarningComponent({ + propsData: { + count: 5, + }, + }).$mount(); + + expect(component.$el.textContent.trim()).toBe(''); + + component = new LimitWarningComponent({ + propsData: { + count: 55, + }, + }).$mount(); + + expect(component.$el.textContent.trim()).toBe(''); + }); + + it('should render if count is exactly 50', () => { + component = new LimitWarningComponent({ + propsData: { + count: 50, + }, + }).$mount(); + + expect(component.$el.textContent.trim()).toBe('Showing 50 events'); + }); +}); diff --git a/spec/frontend/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js new file mode 100644 index 00000000000..984b3026209 --- /dev/null +++ b/spec/frontend/diffs/components/diff_stats_spec.js @@ -0,0 +1,33 @@ +import { shallowMount } from '@vue/test-utils'; +import DiffStats from '~/diffs/components/diff_stats.vue'; + +describe('diff_stats', () => { + it('does not render a group if diffFileLengths is not passed in', () => { + const wrapper = shallowMount(DiffStats, { + propsData: { + addedLines: 1, + removedLines: 2, + }, + }); + const groups = wrapper.findAll('.diff-stats-group'); + + expect(groups.length).toBe(2); + }); + + it('shows amount of files changed, lines added and lines removed when passed all props', () => { + const wrapper = shallowMount(DiffStats, { + propsData: { + addedLines: 100, + removedLines: 200, + diffFilesLength: 300, + }, + }); + const additions = wrapper.find('icon-stub[name="file-addition"]').element.parentNode; + const deletions = wrapper.find('icon-stub[name="file-deletion"]').element.parentNode; + const filesChanged = wrapper.find('icon-stub[name="doc-code"]').element.parentNode; + + expect(additions.textContent).toContain('100'); + expect(deletions.textContent).toContain('200'); + expect(filesChanged.textContent).toContain('300'); + }); +}); diff --git a/spec/frontend/diffs/components/edit_button_spec.js b/spec/frontend/diffs/components/edit_button_spec.js new file mode 100644 index 00000000000..ccdae4cb312 --- /dev/null +++ b/spec/frontend/diffs/components/edit_button_spec.js @@ -0,0 +1,61 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import EditButton from '~/diffs/components/edit_button.vue'; + +const localVue = createLocalVue(); +const editPath = 'test-path'; + +describe('EditButton', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(EditButton, { + localVue, + sync: false, + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('has correct href attribute', () => { + createComponent({ + editPath, + canCurrentUserFork: false, + }); + + expect(wrapper.attributes('href')).toBe(editPath); + }); + + it('emits a show fork message event if current user can fork', () => { + createComponent({ + editPath, + canCurrentUserFork: true, + }); + wrapper.trigger('click'); + + expect(wrapper.emitted('showForkMessage')).toBeTruthy(); + }); + + it('doesnt emit a show fork message event if current user cannot fork', () => { + createComponent({ + editPath, + canCurrentUserFork: false, + }); + wrapper.trigger('click'); + + expect(wrapper.emitted('showForkMessage')).toBeFalsy(); + }); + + it('doesnt emit a show fork message event if current user can modify blob', () => { + createComponent({ + editPath, + canCurrentUserFork: true, + canModifyBlob: true, + }); + wrapper.trigger('click'); + + expect(wrapper.emitted('showForkMessage')).toBeFalsy(); + }); +}); diff --git a/spec/frontend/diffs/components/hidden_files_warning_spec.js b/spec/frontend/diffs/components/hidden_files_warning_spec.js new file mode 100644 index 00000000000..5bf5ddd27bd --- /dev/null +++ b/spec/frontend/diffs/components/hidden_files_warning_spec.js @@ -0,0 +1,48 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue'; + +const localVue = createLocalVue(); +const propsData = { + total: '10', + visible: 5, + plainDiffPath: 'plain-diff-path', + emailPatchPath: 'email-patch-path', +}; + +describe('HiddenFilesWarning', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMount(HiddenFilesWarning, { + localVue, + sync: false, + propsData, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('has a correct plain diff URL', () => { + const plainDiffLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Plain diff')[0]; + + expect(plainDiffLink.attributes('href')).toBe(propsData.plainDiffPath); + }); + + it('has a correct email patch URL', () => { + const emailPatchLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Email patch')[0]; + + expect(emailPatchLink.attributes('href')).toBe(propsData.emailPatchPath); + }); + + it('has a correct visible/total files text', () => { + const filesText = wrapper.find('strong'); + + expect(filesText.text()).toBe('5 of 10'); + }); +}); diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js new file mode 100644 index 00000000000..e45d34bf9d5 --- /dev/null +++ b/spec/frontend/diffs/components/no_changes_spec.js @@ -0,0 +1,40 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { createStore } from '~/mr_notes/stores'; +import NoChanges from '~/diffs/components/no_changes.vue'; + +describe('Diff no changes empty state', () => { + let vm; + + function createComponent(extendStore = () => {}) { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = createStore(); + extendStore(store); + + vm = shallowMount(localVue.extend(NoChanges), { + localVue, + store, + propsData: { + changesEmptyStateIllustration: '', + }, + }); + } + + afterEach(() => { + vm.destroy(); + }); + + it('prevents XSS', () => { + createComponent(store => { + // eslint-disable-next-line no-param-reassign + store.state.notes.noteableData = { + source_branch: '', + target_branch: '', + }; + }); + + expect(vm.contains('script')).toBe(false); + }); +}); diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js new file mode 100644 index 00000000000..503af3920a8 --- /dev/null +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -0,0 +1,118 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; +import { GlButton, GlEmptyState, GlLoadingIcon, GlTable, GlLink } from '@gitlab/ui'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('ErrorTrackingList', () => { + let store; + let wrapper; + let actions; + + function mountComponent({ errorTrackingEnabled = true } = {}) { + wrapper = shallowMount(ErrorTrackingList, { + localVue, + store, + propsData: { + indexPath: '/path', + enableErrorTrackingLink: '/link', + errorTrackingEnabled, + illustrationPath: 'illustration/path', + }, + stubs: { + 'gl-link': GlLink, + }, + }); + } + + beforeEach(() => { + actions = { + getErrorList: () => {}, + startPolling: () => {}, + restartPolling: jasmine.createSpy('restartPolling'), + }; + + const state = { + errors: [], + loading: true, + }; + + store = new Vuex.Store({ + actions, + state, + }); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('loading', () => { + beforeEach(() => { + mountComponent(); + }); + + it('shows spinner', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy(); + expect(wrapper.find(GlTable).exists()).toBeFalsy(); + expect(wrapper.find(GlButton).exists()).toBeFalsy(); + }); + }); + + describe('results', () => { + beforeEach(() => { + store.state.loading = false; + + mountComponent(); + }); + + it('shows table', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); + expect(wrapper.find(GlTable).exists()).toBeTruthy(); + expect(wrapper.find(GlButton).exists()).toBeTruthy(); + }); + }); + + describe('no results', () => { + beforeEach(() => { + store.state.loading = false; + + mountComponent(); + }); + + it('shows empty table', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); + expect(wrapper.find(GlTable).exists()).toBeTruthy(); + expect(wrapper.find(GlButton).exists()).toBeTruthy(); + }); + + it('shows a message prompting to refresh', () => { + const refreshLink = wrapper.vm.$refs.empty.querySelector('a'); + + expect(refreshLink.textContent.trim()).toContain('Check again'); + }); + + it('restarts polling', () => { + wrapper.find('.js-try-again').trigger('click'); + + expect(actions.restartPolling).toHaveBeenCalled(); + }); + }); + + describe('error tracking feature disabled', () => { + beforeEach(() => { + mountComponent({ errorTrackingEnabled: false }); + }); + + it('shows empty state', () => { + expect(wrapper.find(GlEmptyState).exists()).toBeTruthy(); + expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); + expect(wrapper.find(GlTable).exists()).toBeFalsy(); + expect(wrapper.find(GlButton).exists()).toBeFalsy(); + }); + }); +}); diff --git a/spec/frontend/error_tracking/store/mutation_spec.js b/spec/frontend/error_tracking/store/mutation_spec.js new file mode 100644 index 00000000000..8117104bdbc --- /dev/null +++ b/spec/frontend/error_tracking/store/mutation_spec.js @@ -0,0 +1,36 @@ +import mutations from '~/error_tracking/store/mutations'; +import * as types from '~/error_tracking/store/mutation_types'; + +describe('Error tracking mutations', () => { + describe('SET_ERRORS', () => { + let state; + + beforeEach(() => { + state = { errors: [] }; + }); + + it('camelizes response', () => { + const errors = [ + { + title: 'the title', + external_url: 'localhost:3456', + count: 100, + userCount: 10, + }, + ]; + + mutations[types.SET_ERRORS](state, errors); + + expect(state).toEqual({ + errors: [ + { + title: 'the title', + externalUrl: 'localhost:3456', + count: 100, + userCount: 10, + }, + ], + }); + }); + }); +}); diff --git a/spec/frontend/filtered_search/filtered_search_token_keys_spec.js b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js new file mode 100644 index 00000000000..d1fea18dea8 --- /dev/null +++ b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js @@ -0,0 +1,141 @@ +import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; + +describe('Filtered Search Token Keys', () => { + const tokenKeys = [ + { + key: 'author', + type: 'string', + param: 'username', + symbol: '@', + icon: 'pencil', + tag: '@author', + }, + ]; + + const conditions = [ + { + url: 'assignee_id=0', + tokenKey: 'assignee', + value: 'none', + }, + ]; + + describe('get', () => { + it('should return tokenKeys', () => { + expect(new FilteredSearchTokenKeys().get()).not.toBeNull(); + }); + + it('should return tokenKeys as an array', () => { + expect(new FilteredSearchTokenKeys().get() instanceof Array).toBe(true); + }); + }); + + describe('getKeys', () => { + it('should return keys', () => { + const getKeys = new FilteredSearchTokenKeys(tokenKeys).getKeys(); + const keys = new FilteredSearchTokenKeys(tokenKeys).get().map(i => i.key); + + keys.forEach((key, i) => { + expect(key).toEqual(getKeys[i]); + }); + }); + }); + + describe('getConditions', () => { + it('should return conditions', () => { + expect(new FilteredSearchTokenKeys().getConditions()).not.toBeNull(); + }); + + it('should return conditions as an array', () => { + expect(new FilteredSearchTokenKeys().getConditions() instanceof Array).toBe(true); + }); + }); + + describe('searchByKey', () => { + it('should return null when key not found', () => { + const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKey('notakey'); + + expect(tokenKey).toBeNull(); + }); + + it('should return tokenKey when found by key', () => { + const result = new FilteredSearchTokenKeys(tokenKeys).searchByKey(tokenKeys[0].key); + + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchBySymbol', () => { + it('should return null when symbol not found', () => { + const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchBySymbol('notasymbol'); + + expect(tokenKey).toBeNull(); + }); + + it('should return tokenKey when found by symbol', () => { + const result = new FilteredSearchTokenKeys(tokenKeys).searchBySymbol(tokenKeys[0].symbol); + + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchByKeyParam', () => { + it('should return null when key param not found', () => { + const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam('notakeyparam'); + + expect(tokenKey).toBeNull(); + }); + + it('should return tokenKey when found by key param', () => { + const result = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam( + `${tokenKeys[0].key}_${tokenKeys[0].param}`, + ); + + expect(result).toEqual(tokenKeys[0]); + }); + + it('should return alternative tokenKey when found by key param', () => { + const result = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam( + `${tokenKeys[0].key}_${tokenKeys[0].param}`, + ); + + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchByConditionUrl', () => { + it('should return null when condition url not found', () => { + const condition = new FilteredSearchTokenKeys([], [], conditions).searchByConditionUrl(null); + + expect(condition).toBeNull(); + }); + + it('should return condition when found by url', () => { + const result = new FilteredSearchTokenKeys([], [], conditions).searchByConditionUrl( + conditions[0].url, + ); + + expect(result).toBe(conditions[0]); + }); + }); + + describe('searchByConditionKeyValue', () => { + it('should return null when condition tokenKey and value not found', () => { + const condition = new FilteredSearchTokenKeys([], [], conditions).searchByConditionKeyValue( + null, + null, + ); + + expect(condition).toBeNull(); + }); + + it('should return condition when found by tokenKey and value', () => { + const result = new FilteredSearchTokenKeys([], [], conditions).searchByConditionKeyValue( + conditions[0].tokenKey, + conditions[0].value, + ); + + expect(result).toEqual(conditions[0]); + }); + }); +}); diff --git a/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js new file mode 100644 index 00000000000..ea7c146fa4f --- /dev/null +++ b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js @@ -0,0 +1,18 @@ +import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error'; + +describe('RecentSearchesServiceError', () => { + let recentSearchesServiceError; + + beforeEach(() => { + recentSearchesServiceError = new RecentSearchesServiceError(); + }); + + it('instantiates an instance of RecentSearchesServiceError and not an Error', () => { + expect(recentSearchesServiceError).toEqual(jasmine.any(RecentSearchesServiceError)); + expect(recentSearchesServiceError.name).toBe('RecentSearchesServiceError'); + }); + + it('should set a default message', () => { + expect(recentSearchesServiceError.message).toBe('Recent Searches Service is unavailable'); + }); +}); diff --git a/spec/frontend/filtered_search/stores/recent_searches_store_spec.js b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js new file mode 100644 index 00000000000..56bb82ae941 --- /dev/null +++ b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js @@ -0,0 +1,53 @@ +import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store'; + +describe('RecentSearchesStore', () => { + let store; + + beforeEach(() => { + store = new RecentSearchesStore(); + }); + + describe('addRecentSearch', () => { + it('should add to the front of the list', () => { + store.addRecentSearch('foo'); + store.addRecentSearch('bar'); + + expect(store.state.recentSearches).toEqual(['bar', 'foo']); + }); + + it('should deduplicate', () => { + store.addRecentSearch('foo'); + store.addRecentSearch('bar'); + store.addRecentSearch('foo'); + + expect(store.state.recentSearches).toEqual(['foo', 'bar']); + }); + + it('only keeps track of 5 items', () => { + store.addRecentSearch('1'); + store.addRecentSearch('2'); + store.addRecentSearch('3'); + store.addRecentSearch('4'); + store.addRecentSearch('5'); + store.addRecentSearch('6'); + store.addRecentSearch('7'); + + expect(store.state.recentSearches).toEqual(['7', '6', '5', '4', '3']); + }); + }); + + describe('setRecentSearches', () => { + it('should override list', () => { + store.setRecentSearches(['foo', 'bar']); + store.setRecentSearches(['baz', 'qux']); + + expect(store.state.recentSearches).toEqual(['baz', 'qux']); + }); + + it('only keeps track of 5 items', () => { + store.setRecentSearches(['1', '2', '3', '4', '5', '6', '7']); + + expect(store.state.recentSearches).toEqual(['1', '2', '3', '4', '5']); + }); + }); +}); diff --git a/spec/frontend/frequent_items/store/getters_spec.js b/spec/frontend/frequent_items/store/getters_spec.js new file mode 100644 index 00000000000..1cd12eb6832 --- /dev/null +++ b/spec/frontend/frequent_items/store/getters_spec.js @@ -0,0 +1,24 @@ +import state from '~/frequent_items/store/state'; +import * as getters from '~/frequent_items/store/getters'; + +describe('Frequent Items Dropdown Store Getters', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('hasSearchQuery', () => { + it('should return `true` when search query is present', () => { + mockedState.searchQuery = 'test'; + + expect(getters.hasSearchQuery(mockedState)).toBe(true); + }); + + it('should return `false` when search query is empty', () => { + mockedState.searchQuery = ''; + + expect(getters.hasSearchQuery(mockedState)).toBe(false); + }); + }); +}); diff --git a/spec/frontend/ide/lib/common/disposable_spec.js b/spec/frontend/ide/lib/common/disposable_spec.js new file mode 100644 index 00000000000..af12ca15369 --- /dev/null +++ b/spec/frontend/ide/lib/common/disposable_spec.js @@ -0,0 +1,44 @@ +import Disposable from '~/ide/lib/common/disposable'; + +describe('Multi-file editor library disposable class', () => { + let instance; + let disposableClass; + + beforeEach(() => { + instance = new Disposable(); + + disposableClass = { + dispose: jasmine.createSpy('dispose'), + }; + }); + + afterEach(() => { + instance.dispose(); + }); + + describe('add', () => { + it('adds disposable classes', () => { + instance.add(disposableClass); + + expect(instance.disposers.size).toBe(1); + }); + }); + + describe('dispose', () => { + beforeEach(() => { + instance.add(disposableClass); + }); + + it('calls dispose on all cached disposers', () => { + instance.dispose(); + + expect(disposableClass.dispose).toHaveBeenCalled(); + }); + + it('clears cached disposers', () => { + instance.dispose(); + + expect(instance.disposers.size).toBe(0); + }); + }); +}); diff --git a/spec/frontend/ide/lib/diff/diff_spec.js b/spec/frontend/ide/lib/diff/diff_spec.js new file mode 100644 index 00000000000..57f3ac3d365 --- /dev/null +++ b/spec/frontend/ide/lib/diff/diff_spec.js @@ -0,0 +1,80 @@ +import { computeDiff } from '~/ide/lib/diff/diff'; + +describe('Multi-file editor library diff calculator', () => { + describe('computeDiff', () => { + it('returns empty array if no changes', () => { + const diff = computeDiff('123', '123'); + + expect(diff).toEqual([]); + }); + + describe('modified', () => { + it('', () => { + const diff = computeDiff('123', '1234')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeTruthy(); + expect(diff.removed).toBeUndefined(); + }); + + it('', () => { + const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeTruthy(); + expect(diff.removed).toBeUndefined(); + expect(diff.lineNumber).toBe(2); + }); + }); + + describe('added', () => { + it('', () => { + const diff = computeDiff('123', '123\n123')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeUndefined(); + expect(diff.removed).toBeUndefined(); + }); + + it('', () => { + const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeUndefined(); + expect(diff.removed).toBeUndefined(); + expect(diff.lineNumber).toBe(3); + }); + }); + + describe('removed', () => { + it('', () => { + const diff = computeDiff('123', '')[0]; + + expect(diff.added).toBeUndefined(); + expect(diff.modified).toBeUndefined(); + expect(diff.removed).toBeTruthy(); + }); + + it('', () => { + const diff = computeDiff('123\n123\n123', '123\n123')[0]; + + expect(diff.added).toBeUndefined(); + expect(diff.modified).toBeTruthy(); + expect(diff.removed).toBeTruthy(); + expect(diff.lineNumber).toBe(2); + }); + }); + + it('includes line number of change', () => { + const diff = computeDiff('123', '')[0]; + + expect(diff.lineNumber).toBe(1); + }); + + it('includes end line number of change', () => { + const diff = computeDiff('123', '')[0]; + + expect(diff.endLineNumber).toBe(1); + }); + }); +}); diff --git a/spec/frontend/ide/lib/editor_options_spec.js b/spec/frontend/ide/lib/editor_options_spec.js new file mode 100644 index 00000000000..d149a883166 --- /dev/null +++ b/spec/frontend/ide/lib/editor_options_spec.js @@ -0,0 +1,11 @@ +import editorOptions from '~/ide/lib/editor_options'; + +describe('Multi-file editor library editor options', () => { + it('returns an array', () => { + expect(editorOptions).toEqual(jasmine.any(Array)); + }); + + it('contains readOnly option', () => { + expect(editorOptions[0].readOnly).toBeDefined(); + }); +}); diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js new file mode 100644 index 00000000000..fe791aa2b74 --- /dev/null +++ b/spec/frontend/ide/lib/files_spec.js @@ -0,0 +1,77 @@ +import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils'; +import { decorateFiles, splitParent } from '~/ide/lib/files'; +import { decorateData } from '~/ide/stores/utils'; + +const TEST_BRANCH_ID = 'lorem-ipsum'; +const TEST_PROJECT_ID = 10; + +const createEntries = paths => { + const createEntry = (acc, { path, type, children }) => { + // Sometimes we need to end the url with a '/' + const createUrl = base => (type === 'tree' ? `${base}/` : base); + + const { name, parent } = splitParent(path); + const parentEntry = acc[parent]; + + acc[path] = { + ...decorateData({ + projectId: TEST_PROJECT_ID, + branchId: TEST_BRANCH_ID, + id: path, + name, + path, + url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${path}`), + type, + previewMode: viewerInformationForPath(path), + parentPath: parent, + parentTreeUrl: parentEntry + ? parentEntry.url + : createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}`), + }), + tree: children.map(childName => jasmine.objectContaining({ name: childName })), + }; + + return acc; + }; + + const entries = paths.reduce(createEntry, {}); + + // Wrap entries in jasmine.objectContaining. + // We couldn't do this earlier because we still need to select properties from parent entries. + return Object.keys(entries).reduce((acc, key) => { + acc[key] = jasmine.objectContaining(entries[key]); + + return acc; + }, {}); +}; + +describe('IDE lib decorate files', () => { + it('creates entries and treeList', () => { + const data = ['app/assets/apples/foo.js', 'app/bugs.js', 'README.md']; + const expectedEntries = createEntries([ + { path: 'app', type: 'tree', children: ['assets', 'bugs.js'] }, + { path: 'app/assets', type: 'tree', children: ['apples'] }, + { path: 'app/assets/apples', type: 'tree', children: ['foo.js'] }, + { path: 'app/assets/apples/foo.js', type: 'blob', children: [] }, + { path: 'app/bugs.js', type: 'blob', children: [] }, + { path: 'README.md', type: 'blob', children: [] }, + ]); + + const { entries, treeList } = decorateFiles({ + data, + branchId: TEST_BRANCH_ID, + projectId: TEST_PROJECT_ID, + }); + + // Here we test the keys and then each key/value individually because `expect(entries).toEqual(expectedEntries)` + // was taking a very long time for some reason. Probably due to large objects and nested `jasmine.objectContaining`. + const entryKeys = Object.keys(entries); + + expect(entryKeys).toEqual(Object.keys(expectedEntries)); + entryKeys.forEach(key => { + expect(entries[key]).toEqual(expectedEntries[key]); + }); + + expect(treeList).toEqual([expectedEntries.app, expectedEntries['README.md']]); + }); +}); diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js new file mode 100644 index 00000000000..5de7a281d34 --- /dev/null +++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js @@ -0,0 +1,42 @@ +import commitState from '~/ide/stores/modules/commit/state'; +import mutations from '~/ide/stores/modules/commit/mutations'; + +describe('IDE commit module mutations', () => { + let state; + + beforeEach(() => { + state = commitState(); + }); + + describe('UPDATE_COMMIT_MESSAGE', () => { + it('updates commitMessage', () => { + mutations.UPDATE_COMMIT_MESSAGE(state, 'testing'); + + expect(state.commitMessage).toBe('testing'); + }); + }); + + describe('UPDATE_COMMIT_ACTION', () => { + it('updates commitAction', () => { + mutations.UPDATE_COMMIT_ACTION(state, 'testing'); + + expect(state.commitAction).toBe('testing'); + }); + }); + + describe('UPDATE_NEW_BRANCH_NAME', () => { + it('updates newBranchName', () => { + mutations.UPDATE_NEW_BRANCH_NAME(state, 'testing'); + + expect(state.newBranchName).toBe('testing'); + }); + }); + + describe('UPDATE_LOADING', () => { + it('updates submitCommitLoading', () => { + mutations.UPDATE_LOADING(state, true); + + expect(state.submitCommitLoading).toBeTruthy(); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/file_templates/getters_spec.js b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js new file mode 100644 index 00000000000..17cb457881f --- /dev/null +++ b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js @@ -0,0 +1,59 @@ +import createState from '~/ide/stores/state'; +import { activityBarViews } from '~/ide/constants'; +import * as getters from '~/ide/stores/modules/file_templates/getters'; + +describe('IDE file templates getters', () => { + describe('templateTypes', () => { + it('returns list of template types', () => { + expect(getters.templateTypes().length).toBe(4); + }); + }); + + describe('showFileTemplatesBar', () => { + let rootState; + + beforeEach(() => { + rootState = createState(); + }); + + it('returns true if template is found and currentActivityView is edit', () => { + rootState.currentActivityView = activityBarViews.edit; + + expect( + getters.showFileTemplatesBar( + null, + { + templateTypes: getters.templateTypes(), + }, + rootState, + )('LICENSE'), + ).toBe(true); + }); + + it('returns false if template is found and currentActivityView is not edit', () => { + rootState.currentActivityView = activityBarViews.commit; + + expect( + getters.showFileTemplatesBar( + null, + { + templateTypes: getters.templateTypes(), + }, + rootState, + )('LICENSE'), + ).toBe(false); + }); + + it('returns undefined if not found', () => { + expect( + getters.showFileTemplatesBar( + null, + { + templateTypes: getters.templateTypes(), + }, + rootState, + )('test'), + ).toBe(undefined); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js new file mode 100644 index 00000000000..8e0e3ae99a1 --- /dev/null +++ b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js @@ -0,0 +1,69 @@ +import createState from '~/ide/stores/modules/file_templates/state'; +import * as types from '~/ide/stores/modules/file_templates/mutation_types'; +import mutations from '~/ide/stores/modules/file_templates/mutations'; + +describe('IDE file templates mutations', () => { + let state; + + beforeEach(() => { + state = createState(); + }); + + describe(types.REQUEST_TEMPLATE_TYPES, () => { + it('sets isLoading', () => { + mutations[types.REQUEST_TEMPLATE_TYPES](state); + + expect(state.isLoading).toBe(true); + }); + }); + + describe(types.RECEIVE_TEMPLATE_TYPES_ERROR, () => { + it('sets isLoading', () => { + state.isLoading = true; + + mutations[types.RECEIVE_TEMPLATE_TYPES_ERROR](state); + + expect(state.isLoading).toBe(false); + }); + }); + + describe(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, () => { + it('sets isLoading to false', () => { + state.isLoading = true; + + mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, []); + + expect(state.isLoading).toBe(false); + }); + + it('sets templates', () => { + mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, ['test']); + + expect(state.templates).toEqual(['test']); + }); + }); + + describe(types.SET_SELECTED_TEMPLATE_TYPE, () => { + it('sets selectedTemplateType', () => { + mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type'); + + expect(state.selectedTemplateType).toBe('type'); + }); + + it('clears templates', () => { + state.templates = ['test']; + + mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type'); + + expect(state.templates).toEqual([]); + }); + }); + + describe(types.SET_UPDATE_SUCCESS, () => { + it('sets updateSuccess', () => { + mutations[types.SET_UPDATE_SUCCESS](state, true); + + expect(state.updateSuccess).toBe(true); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/pane/getters_spec.js b/spec/frontend/ide/stores/modules/pane/getters_spec.js new file mode 100644 index 00000000000..8a213323de0 --- /dev/null +++ b/spec/frontend/ide/stores/modules/pane/getters_spec.js @@ -0,0 +1,55 @@ +import * as getters from '~/ide/stores/modules/pane/getters'; +import state from '~/ide/stores/modules/pane/state'; + +describe('IDE pane module getters', () => { + const TEST_VIEW = 'test-view'; + const TEST_KEEP_ALIVE_VIEWS = { + [TEST_VIEW]: true, + }; + + describe('isActiveView', () => { + it('returns true if given view matches currentView', () => { + const result = getters.isActiveView({ currentView: 'A' })('A'); + + expect(result).toBe(true); + }); + + it('returns false if given view does not match currentView', () => { + const result = getters.isActiveView({ currentView: 'A' })('B'); + + expect(result).toBe(false); + }); + }); + + describe('isAliveView', () => { + it('returns true if given view is in keepAliveViews', () => { + const result = getters.isAliveView({ keepAliveViews: TEST_KEEP_ALIVE_VIEWS }, {})(TEST_VIEW); + + expect(result).toBe(true); + }); + + it('returns true if given view is active view and open', () => { + const result = getters.isAliveView( + { ...state(), isOpen: true }, + { isActiveView: () => true }, + )(TEST_VIEW); + + expect(result).toBe(true); + }); + + it('returns false if given view is active view and closed', () => { + const result = getters.isAliveView(state(), { isActiveView: () => true })(TEST_VIEW); + + expect(result).toBe(false); + }); + + it('returns false if given view is not activeView', () => { + const result = getters.isAliveView( + { ...state(), isOpen: true }, + { isActiveView: () => false }, + )(TEST_VIEW); + + expect(result).toBe(false); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/pane/mutations_spec.js b/spec/frontend/ide/stores/modules/pane/mutations_spec.js new file mode 100644 index 00000000000..b5fcd35912e --- /dev/null +++ b/spec/frontend/ide/stores/modules/pane/mutations_spec.js @@ -0,0 +1,42 @@ +import state from '~/ide/stores/modules/pane/state'; +import mutations from '~/ide/stores/modules/pane/mutations'; +import * as types from '~/ide/stores/modules/pane/mutation_types'; + +describe('IDE pane module mutations', () => { + const TEST_VIEW = 'test-view'; + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('SET_OPEN', () => { + it('sets isOpen', () => { + mockedState.isOpen = false; + + mutations[types.SET_OPEN](mockedState, true); + + expect(mockedState.isOpen).toBe(true); + }); + }); + + describe('SET_CURRENT_VIEW', () => { + it('sets currentView', () => { + mockedState.currentView = null; + + mutations[types.SET_CURRENT_VIEW](mockedState, TEST_VIEW); + + expect(mockedState.currentView).toEqual(TEST_VIEW); + }); + }); + + describe('KEEP_ALIVE_VIEW', () => { + it('adds entry to keepAliveViews', () => { + mutations[types.KEEP_ALIVE_VIEW](mockedState, TEST_VIEW); + + expect(mockedState.keepAliveViews).toEqual({ + [TEST_VIEW]: true, + }); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/pipelines/getters_spec.js b/spec/frontend/ide/stores/modules/pipelines/getters_spec.js new file mode 100644 index 00000000000..4514896b5ea --- /dev/null +++ b/spec/frontend/ide/stores/modules/pipelines/getters_spec.js @@ -0,0 +1,40 @@ +import * as getters from '~/ide/stores/modules/pipelines/getters'; +import state from '~/ide/stores/modules/pipelines/state'; + +describe('IDE pipeline getters', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('hasLatestPipeline', () => { + it('returns false when loading is true', () => { + mockedState.isLoadingPipeline = true; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns false when pipelines is null', () => { + mockedState.latestPipeline = null; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns false when loading is true & pipelines is null', () => { + mockedState.latestPipeline = null; + mockedState.isLoadingPipeline = true; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns true when loading is false & pipelines is an object', () => { + mockedState.latestPipeline = { + id: 1, + }; + mockedState.isLoadingPipeline = false; + + expect(getters.hasLatestPipeline(mockedState)).toBe(true); + }); + }); +}); diff --git a/spec/frontend/ide/stores/mutations/branch_spec.js b/spec/frontend/ide/stores/mutations/branch_spec.js new file mode 100644 index 00000000000..29eb859ddaf --- /dev/null +++ b/spec/frontend/ide/stores/mutations/branch_spec.js @@ -0,0 +1,40 @@ +import mutations from '~/ide/stores/mutations/branch'; +import state from '~/ide/stores/state'; + +describe('Multi-file store branch mutations', () => { + let localState; + + beforeEach(() => { + localState = state(); + }); + + describe('SET_CURRENT_BRANCH', () => { + it('sets currentBranch', () => { + mutations.SET_CURRENT_BRANCH(localState, 'master'); + + expect(localState.currentBranchId).toBe('master'); + }); + }); + + describe('SET_BRANCH_COMMIT', () => { + it('sets the last commit on current project', () => { + localState.projects = { + Example: { + branches: { + master: {}, + }, + }, + }; + + mutations.SET_BRANCH_COMMIT(localState, { + projectId: 'Example', + branchId: 'master', + commit: { + title: 'Example commit', + }, + }); + + expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit'); + }); + }); +}); diff --git a/spec/frontend/ide/stores/mutations/merge_request_spec.js b/spec/frontend/ide/stores/mutations/merge_request_spec.js new file mode 100644 index 00000000000..e30ca22022f --- /dev/null +++ b/spec/frontend/ide/stores/mutations/merge_request_spec.js @@ -0,0 +1,67 @@ +import mutations from '~/ide/stores/mutations/merge_request'; +import state from '~/ide/stores/state'; + +describe('IDE store merge request mutations', () => { + let localState; + + beforeEach(() => { + localState = state(); + localState.projects = { abcproject: { mergeRequests: {} } }; + + mutations.SET_MERGE_REQUEST(localState, { + projectPath: 'abcproject', + mergeRequestId: 1, + mergeRequest: { + title: 'mr', + }, + }); + }); + + describe('SET_CURRENT_MERGE_REQUEST', () => { + it('sets current merge request', () => { + mutations.SET_CURRENT_MERGE_REQUEST(localState, 2); + + expect(localState.currentMergeRequestId).toBe(2); + }); + }); + + describe('SET_MERGE_REQUEST', () => { + it('setsmerge request data', () => { + const newMr = localState.projects.abcproject.mergeRequests[1]; + + expect(newMr.title).toBe('mr'); + expect(newMr.active).toBeTruthy(); + }); + }); + + describe('SET_MERGE_REQUEST_CHANGES', () => { + it('sets merge request changes', () => { + mutations.SET_MERGE_REQUEST_CHANGES(localState, { + projectPath: 'abcproject', + mergeRequestId: 1, + changes: { + diff: 'abc', + }, + }); + + const newMr = localState.projects.abcproject.mergeRequests[1]; + + expect(newMr.changes.diff).toBe('abc'); + }); + }); + + describe('SET_MERGE_REQUEST_VERSIONS', () => { + it('sets merge request versions', () => { + mutations.SET_MERGE_REQUEST_VERSIONS(localState, { + projectPath: 'abcproject', + mergeRequestId: 1, + versions: [{ id: 123 }], + }); + + const newMr = localState.projects.abcproject.mergeRequests[1]; + + expect(newMr.versions.length).toBe(1); + expect(newMr.versions[0].id).toBe(123); + }); + }); +}); diff --git a/spec/frontend/image_diff/view_types_spec.js b/spec/frontend/image_diff/view_types_spec.js new file mode 100644 index 00000000000..e9639f46497 --- /dev/null +++ b/spec/frontend/image_diff/view_types_spec.js @@ -0,0 +1,24 @@ +import { viewTypes, isValidViewType } from '~/image_diff/view_types'; + +describe('viewTypes', () => { + describe('isValidViewType', () => { + it('should return true for TWO_UP', () => { + expect(isValidViewType(viewTypes.TWO_UP)).toEqual(true); + }); + + it('should return true for SWIPE', () => { + expect(isValidViewType(viewTypes.SWIPE)).toEqual(true); + }); + + it('should return true for ONION_SKIN', () => { + expect(isValidViewType(viewTypes.ONION_SKIN)).toEqual(true); + }); + + it('should return false for non view types', () => { + expect(isValidViewType('some-view-type')).toEqual(false); + expect(isValidViewType(null)).toEqual(false); + expect(isValidViewType(undefined)).toEqual(false); + expect(isValidViewType('')).toEqual(false); + }); + }); +}); diff --git a/spec/frontend/import_projects/store/getters_spec.js b/spec/frontend/import_projects/store/getters_spec.js new file mode 100644 index 00000000000..e5e4a95f473 --- /dev/null +++ b/spec/frontend/import_projects/store/getters_spec.js @@ -0,0 +1,83 @@ +import { + namespaceSelectOptions, + isImportingAnyRepo, + hasProviderRepos, + hasImportedProjects, +} from '~/import_projects/store/getters'; +import state from '~/import_projects/store/state'; + +describe('import_projects store getters', () => { + let localState; + + beforeEach(() => { + localState = state(); + }); + + describe('namespaceSelectOptions', () => { + const namespaces = [{ fullPath: 'namespace-0' }, { fullPath: 'namespace-1' }]; + const defaultTargetNamespace = 'current-user'; + + it('returns an options array with a "Users" and "Groups" optgroups', () => { + localState.namespaces = namespaces; + localState.defaultTargetNamespace = defaultTargetNamespace; + + const optionsArray = namespaceSelectOptions(localState); + const groupsGroup = optionsArray[0]; + const usersGroup = optionsArray[1]; + + expect(groupsGroup.text).toBe('Groups'); + expect(usersGroup.text).toBe('Users'); + + groupsGroup.children.forEach((child, index) => { + expect(child.id).toBe(namespaces[index].fullPath); + expect(child.text).toBe(namespaces[index].fullPath); + }); + + expect(usersGroup.children.length).toBe(1); + expect(usersGroup.children[0].id).toBe(defaultTargetNamespace); + expect(usersGroup.children[0].text).toBe(defaultTargetNamespace); + }); + }); + + describe('isImportingAnyRepo', () => { + it('returns true if there are any reposBeingImported', () => { + localState.reposBeingImported = new Array(1); + + expect(isImportingAnyRepo(localState)).toBe(true); + }); + + it('returns false if there are no reposBeingImported', () => { + localState.reposBeingImported = []; + + expect(isImportingAnyRepo(localState)).toBe(false); + }); + }); + + describe('hasProviderRepos', () => { + it('returns true if there are any providerRepos', () => { + localState.providerRepos = new Array(1); + + expect(hasProviderRepos(localState)).toBe(true); + }); + + it('returns false if there are no providerRepos', () => { + localState.providerRepos = []; + + expect(hasProviderRepos(localState)).toBe(false); + }); + }); + + describe('hasImportedProjects', () => { + it('returns true if there are any importedProjects', () => { + localState.importedProjects = new Array(1); + + expect(hasImportedProjects(localState)).toBe(true); + }); + + it('returns false if there are no importedProjects', () => { + localState.importedProjects = []; + + expect(hasImportedProjects(localState)).toBe(false); + }); + }); +}); diff --git a/spec/frontend/import_projects/store/mutations_spec.js b/spec/frontend/import_projects/store/mutations_spec.js new file mode 100644 index 00000000000..8db8e9819ba --- /dev/null +++ b/spec/frontend/import_projects/store/mutations_spec.js @@ -0,0 +1,34 @@ +import * as types from '~/import_projects/store/mutation_types'; +import mutations from '~/import_projects/store/mutations'; + +describe('import_projects store mutations', () => { + describe(types.RECEIVE_IMPORT_SUCCESS, () => { + it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => { + const repoId = 1; + const state = { + reposBeingImported: [repoId], + providerRepos: [{ id: repoId }], + importedProjects: [], + }; + const importedProject = { id: repoId }; + + mutations[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }); + + expect(state.reposBeingImported.includes(repoId)).toBe(false); + expect(state.providerRepos.some(repo => repo.id === repoId)).toBe(false); + expect(state.importedProjects.some(repo => repo.id === repoId)).toBe(true); + }); + }); + + describe(types.RECEIVE_JOBS_SUCCESS, () => { + it('updates importStatus of existing importedProjects', () => { + const repoId = 1; + const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] }; + const updatedProjects = [{ id: repoId, importStatus: 'finished' }]; + + mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects); + + expect(state.importedProjects[0].importStatus).toBe(updatedProjects[0].importStatus); + }); + }); +}); diff --git a/spec/frontend/jobs/components/empty_state_spec.js b/spec/frontend/jobs/components/empty_state_spec.js new file mode 100644 index 00000000000..a2df79bdda0 --- /dev/null +++ b/spec/frontend/jobs/components/empty_state_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import component from '~/jobs/components/empty_state.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Empty State', () => { + const Component = Vue.extend(component); + let vm; + + const props = { + illustrationPath: 'illustrations/pending_job_empty.svg', + illustrationSizeClass: 'svg-430', + title: 'This job has not started yet', + }; + + const content = 'This job is in pending state and is waiting to be picked by a runner'; + + afterEach(() => { + vm.$destroy(); + }); + + describe('renders image and title', () => { + beforeEach(() => { + vm = mountComponent(Component, { + ...props, + content, + }); + }); + + it('renders img with provided path and size', () => { + expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(props.illustrationPath); + expect(vm.$el.querySelector('.svg-content').classList).toContain(props.illustrationSizeClass); + }); + + it('renders provided title', () => { + expect(vm.$el.querySelector('.js-job-empty-state-title').textContent.trim()).toEqual( + props.title, + ); + }); + }); + + describe('with content', () => { + it('renders content', () => { + vm = mountComponent(Component, { + ...props, + content, + }); + + expect(vm.$el.querySelector('.js-job-empty-state-content').textContent.trim()).toEqual( + content, + ); + }); + }); + + describe('without content', () => { + it('does not render content', () => { + vm = mountComponent(Component, { + ...props, + }); + + expect(vm.$el.querySelector('.js-job-empty-state-content')).toBeNull(); + }); + }); + + describe('with action', () => { + it('renders action', () => { + vm = mountComponent(Component, { + ...props, + content, + action: { + path: 'runner', + button_title: 'Check runner', + method: 'post', + }, + }); + + expect(vm.$el.querySelector('.js-job-empty-state-action').getAttribute('href')).toEqual( + 'runner', + ); + }); + }); + + describe('without action', () => { + it('does not render action', () => { + vm = mountComponent(Component, { + ...props, + content, + action: null, + }); + + expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull(); + }); + }); +}); diff --git a/spec/frontend/jobs/components/erased_block_spec.js b/spec/frontend/jobs/components/erased_block_spec.js new file mode 100644 index 00000000000..8e0433d3fb7 --- /dev/null +++ b/spec/frontend/jobs/components/erased_block_spec.js @@ -0,0 +1,56 @@ +import Vue from 'vue'; +import { getTimeago } from '~/lib/utils/datetime_utility'; +import component from '~/jobs/components/erased_block.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Erased block', () => { + const Component = Vue.extend(component); + let vm; + + const erasedAt = '2016-11-07T11:11:16.525Z'; + const timeago = getTimeago(); + const formatedDate = timeago.format(erasedAt); + + afterEach(() => { + vm.$destroy(); + }); + + describe('with job erased by user', () => { + beforeEach(() => { + vm = mountComponent(Component, { + user: { + username: 'root', + web_url: 'gitlab.com/root', + }, + erasedAt, + }); + }); + + it('renders username and link', () => { + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('gitlab.com/root'); + + expect(vm.$el.textContent).toContain('Job has been erased by'); + expect(vm.$el.textContent).toContain('root'); + }); + + it('renders erasedAt', () => { + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); + + describe('with erased job', () => { + beforeEach(() => { + vm = mountComponent(Component, { + erasedAt, + }); + }); + + it('renders username and link', () => { + expect(vm.$el.textContent).toContain('Job has been erased'); + }); + + it('renders erasedAt', () => { + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); +}); diff --git a/spec/frontend/jobs/components/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/sidebar_detail_row_spec.js new file mode 100644 index 00000000000..42d11266dad --- /dev/null +++ b/spec/frontend/jobs/components/sidebar_detail_row_spec.js @@ -0,0 +1,61 @@ +import Vue from 'vue'; +import sidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue'; + +describe('Sidebar detail row', () => { + let SidebarDetailRow; + let vm; + + beforeEach(() => { + SidebarDetailRow = Vue.extend(sidebarDetailRow); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render no title', () => { + vm = new SidebarDetailRow({ + propsData: { + value: 'this is the value', + }, + }).$mount(); + + expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual('this is the value'); + }); + + beforeEach(() => { + vm = new SidebarDetailRow({ + propsData: { + title: 'this is the title', + value: 'this is the value', + }, + }).$mount(); + }); + + it('should render provided title and value', () => { + expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual( + 'this is the title: this is the value', + ); + }); + + describe('when helpUrl not provided', () => { + it('should not render help', () => { + expect(vm.$el.querySelector('.help-button')).toBeNull(); + }); + }); + + describe('when helpUrl provided', () => { + beforeEach(() => { + vm = new SidebarDetailRow({ + propsData: { + helpUrl: 'help url', + value: 'foo', + }, + }).$mount(); + }); + + it('should render help', () => { + expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url'); + }); + }); +}); diff --git a/spec/frontend/jobs/components/stuck_block_spec.js b/spec/frontend/jobs/components/stuck_block_spec.js new file mode 100644 index 00000000000..c320793b2be --- /dev/null +++ b/spec/frontend/jobs/components/stuck_block_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; +import component from '~/jobs/components/stuck_block.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Stuck Block Job component', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('with no runners for project', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: true, + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders only information about project not having runners', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).not.toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); + + describe('with tags', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: false, + tags: ['docker', 'gitlab-org'], + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders information about the tags not being set', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).not.toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); + }); + + it('renders tags', () => { + expect(vm.$el.textContent).toContain('docker'); + expect(vm.$el.textContent).toContain('gitlab-org'); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); + + describe('without active runners', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: false, + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders information about project not having runners', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).not.toBeNull(); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); +}); diff --git a/spec/frontend/jobs/store/getters_spec.js b/spec/frontend/jobs/store/getters_spec.js new file mode 100644 index 00000000000..379114c3737 --- /dev/null +++ b/spec/frontend/jobs/store/getters_spec.js @@ -0,0 +1,243 @@ +import * as getters from '~/jobs/store/getters'; +import state from '~/jobs/store/state'; + +describe('Job Store Getters', () => { + let localState; + + beforeEach(() => { + localState = state(); + }); + + describe('headerTime', () => { + describe('when the job has started key', () => { + it('returns started key', () => { + const started = '2018-08-31T16:20:49.023Z'; + localState.job.started = started; + + expect(getters.headerTime(localState)).toEqual(started); + }); + }); + + describe('when the job does not have started key', () => { + it('returns created_at key', () => { + const created = '2018-08-31T16:20:49.023Z'; + localState.job.created_at = created; + + expect(getters.headerTime(localState)).toEqual(created); + }); + }); + }); + + describe('shouldRenderCalloutMessage', () => { + describe('with status and callout message', () => { + it('returns true', () => { + localState.job.callout_message = 'Callout message'; + localState.job.status = { icon: 'passed' }; + + expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true); + }); + }); + + describe('without status & with callout message', () => { + it('returns false', () => { + localState.job.callout_message = 'Callout message'; + + expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); + }); + }); + + describe('with status & without callout message', () => { + it('returns false', () => { + localState.job.status = { icon: 'passed' }; + + expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); + }); + }); + }); + + describe('shouldRenderTriggeredLabel', () => { + describe('when started equals null', () => { + it('returns false', () => { + localState.job.started = null; + + expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false); + }); + }); + + describe('when started equals string', () => { + it('returns true', () => { + localState.job.started = '2018-08-31T16:20:49.023Z'; + + expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true); + }); + }); + }); + + describe('hasEnvironment', () => { + describe('without `deployment_status`', () => { + it('returns false', () => { + expect(getters.hasEnvironment(localState)).toEqual(false); + }); + }); + + describe('with an empty object for `deployment_status`', () => { + it('returns false', () => { + localState.job.deployment_status = {}; + + expect(getters.hasEnvironment(localState)).toEqual(false); + }); + }); + + describe('when `deployment_status` is defined and not empty', () => { + it('returns true', () => { + localState.job.deployment_status = { + status: 'creating', + environment: { + last_deployment: {}, + }, + }; + + expect(getters.hasEnvironment(localState)).toEqual(true); + }); + }); + }); + + describe('hasTrace', () => { + describe('when has_trace is true', () => { + it('returns true', () => { + localState.job.has_trace = true; + localState.job.status = {}; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when job is running', () => { + it('returns true', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'running' }; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when has_trace is false and job is not running', () => { + it('returns false', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'pending' }; + + expect(getters.hasTrace(localState)).toEqual(false); + }); + }); + }); + + describe('emptyStateIllustration', () => { + describe('with defined illustration', () => { + it('returns the state illustration object', () => { + localState.job.status = { + illustration: { + path: 'foo', + }, + }; + + expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' }); + }); + }); + + describe('when illustration is not defined', () => { + it('returns an empty object', () => { + expect(getters.emptyStateIllustration(localState)).toEqual({}); + }); + }); + }); + + describe('shouldRenderSharedRunnerLimitWarning', () => { + describe('without runners information', () => { + it('returns false', () => { + expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false); + }); + }); + + describe('with runners information', () => { + describe('when used quota is less than limit', () => { + it('returns false', () => { + localState.job.runners = { + quota: { + used: 33, + limit: 2000, + }, + available: true, + online: true, + }; + + expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false); + }); + }); + + describe('when used quota is equal to limit', () => { + it('returns true', () => { + localState.job.runners = { + quota: { + used: 2000, + limit: 2000, + }, + available: true, + online: true, + }; + + expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true); + }); + }); + + describe('when used quota is bigger than limit', () => { + it('returns true', () => { + localState.job.runners = { + quota: { + used: 2002, + limit: 2000, + }, + available: true, + online: true, + }; + + expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true); + }); + }); + }); + }); + + describe('hasRunnersForProject', () => { + describe('with available and offline runners', () => { + it('returns true', () => { + localState.job.runners = { + available: true, + online: false, + }; + + expect(getters.hasRunnersForProject(localState)).toEqual(true); + }); + }); + + describe('with non available runners', () => { + it('returns false', () => { + localState.job.runners = { + available: false, + online: false, + }; + + expect(getters.hasRunnersForProject(localState)).toEqual(false); + }); + }); + + describe('with online runners', () => { + it('returns false', () => { + localState.job.runners = { + available: false, + online: true, + }; + + expect(getters.hasRunnersForProject(localState)).toEqual(false); + }); + }); + }); +}); diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js new file mode 100644 index 00000000000..d7908efcf13 --- /dev/null +++ b/spec/frontend/jobs/store/mutations_spec.js @@ -0,0 +1,230 @@ +import state from '~/jobs/store/state'; +import mutations from '~/jobs/store/mutations'; +import * as types from '~/jobs/store/mutation_types'; + +describe('Jobs Store Mutations', () => { + let stateCopy; + + const html = + 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png
    I'; + + beforeEach(() => { + stateCopy = state(); + }); + + describe('SET_JOB_ENDPOINT', () => { + it('should set jobEndpoint', () => { + mutations[types.SET_JOB_ENDPOINT](stateCopy, 'job/21312321.json'); + + expect(stateCopy.jobEndpoint).toEqual('job/21312321.json'); + }); + }); + + describe('HIDE_SIDEBAR', () => { + it('should set isSidebarOpen to false', () => { + mutations[types.HIDE_SIDEBAR](stateCopy); + + expect(stateCopy.isSidebarOpen).toEqual(false); + }); + }); + + describe('SHOW_SIDEBAR', () => { + it('should set isSidebarOpen to true', () => { + mutations[types.SHOW_SIDEBAR](stateCopy); + + expect(stateCopy.isSidebarOpen).toEqual(true); + }); + }); + + describe('RECEIVE_TRACE_SUCCESS', () => { + describe('when trace has state', () => { + it('sets traceState', () => { + const stateLog = + 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0='; + mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { + state: stateLog, + }); + + expect(stateCopy.traceState).toEqual(stateLog); + }); + }); + + describe('when traceSize is smaller than the total size', () => { + it('sets isTraceSizeVisible to true', () => { + mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { total: 51184600, size: 1231 }); + + expect(stateCopy.isTraceSizeVisible).toEqual(true); + }); + }); + + describe('when traceSize is bigger than the total size', () => { + it('sets isTraceSizeVisible to false', () => { + const copy = Object.assign({}, stateCopy, { traceSize: 5118460, size: 2321312 }); + + mutations[types.RECEIVE_TRACE_SUCCESS](copy, { total: 511846 }); + + expect(copy.isTraceSizeVisible).toEqual(false); + }); + }); + + it('sets trace, trace size and isTraceComplete', () => { + mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { + append: true, + html, + size: 511846, + complete: true, + }); + + expect(stateCopy.trace).toEqual(html); + expect(stateCopy.traceSize).toEqual(511846); + expect(stateCopy.isTraceComplete).toEqual(true); + }); + }); + + describe('STOP_POLLING_TRACE', () => { + it('sets isTraceComplete to true', () => { + mutations[types.STOP_POLLING_TRACE](stateCopy); + + expect(stateCopy.isTraceComplete).toEqual(true); + }); + }); + + describe('RECEIVE_TRACE_ERROR', () => { + it('resets trace state and sets error to true', () => { + mutations[types.RECEIVE_TRACE_ERROR](stateCopy); + + expect(stateCopy.isTraceComplete).toEqual(true); + }); + }); + + describe('REQUEST_JOB', () => { + it('sets isLoading to true', () => { + mutations[types.REQUEST_JOB](stateCopy); + + expect(stateCopy.isLoading).toEqual(true); + }); + }); + + describe('RECEIVE_JOB_SUCCESS', () => { + it('sets is loading to false', () => { + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); + + expect(stateCopy.isLoading).toEqual(false); + }); + + it('sets hasError to false', () => { + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); + + expect(stateCopy.hasError).toEqual(false); + }); + + it('sets job data', () => { + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); + + expect(stateCopy.job).toEqual({ id: 1312321 }); + }); + + it('sets selectedStage when the selectedStage is empty', () => { + expect(stateCopy.selectedStage).toEqual(''); + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); + + expect(stateCopy.selectedStage).toEqual('deploy'); + }); + + it('does not set selectedStage when the selectedStage is not More', () => { + stateCopy.selectedStage = 'notify'; + + expect(stateCopy.selectedStage).toEqual('notify'); + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); + + expect(stateCopy.selectedStage).toEqual('notify'); + }); + }); + + describe('RECEIVE_JOB_ERROR', () => { + it('resets job data', () => { + mutations[types.RECEIVE_JOB_ERROR](stateCopy); + + expect(stateCopy.isLoading).toEqual(false); + expect(stateCopy.job).toEqual({}); + }); + }); + + describe('REQUEST_STAGES', () => { + it('sets isLoadingStages to true', () => { + mutations[types.REQUEST_STAGES](stateCopy); + + expect(stateCopy.isLoadingStages).toEqual(true); + }); + }); + + describe('RECEIVE_STAGES_SUCCESS', () => { + beforeEach(() => { + mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]); + }); + + it('sets isLoadingStages to false', () => { + expect(stateCopy.isLoadingStages).toEqual(false); + }); + + it('sets stages', () => { + expect(stateCopy.stages).toEqual([{ name: 'build' }]); + }); + }); + + describe('RECEIVE_STAGES_ERROR', () => { + beforeEach(() => { + mutations[types.RECEIVE_STAGES_ERROR](stateCopy); + }); + + it('sets isLoadingStages to false', () => { + expect(stateCopy.isLoadingStages).toEqual(false); + }); + + it('resets stages', () => { + expect(stateCopy.stages).toEqual([]); + }); + }); + + describe('REQUEST_JOBS_FOR_STAGE', () => { + it('sets isLoadingStages to true', () => { + mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); + + expect(stateCopy.isLoadingJobs).toEqual(true); + }); + + it('sets selectedStage', () => { + mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); + + expect(stateCopy.selectedStage).toEqual('deploy'); + }); + }); + + describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => { + beforeEach(() => { + mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]); + }); + + it('sets isLoadingJobs to false', () => { + expect(stateCopy.isLoadingJobs).toEqual(false); + }); + + it('sets jobs', () => { + expect(stateCopy.jobs).toEqual([{ name: 'karma' }]); + }); + }); + + describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => { + beforeEach(() => { + mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy); + }); + + it('sets isLoadingJobs to false', () => { + expect(stateCopy.isLoadingJobs).toEqual(false); + }); + + it('resets jobs', () => { + expect(stateCopy.jobs).toEqual([]); + }); + }); +}); diff --git a/spec/frontend/labels_select_spec.js b/spec/frontend/labels_select_spec.js new file mode 100644 index 00000000000..acfdc885032 --- /dev/null +++ b/spec/frontend/labels_select_spec.js @@ -0,0 +1,52 @@ +import $ from 'jquery'; +import LabelsSelect from '~/labels_select'; + +const mockUrl = '/foo/bar/url'; + +const mockLabels = [ + { + id: 26, + title: 'Foo Label', + description: 'Foobar', + color: '#BADA55', + text_color: '#FFFFFF', + }, +]; + +describe('LabelsSelect', () => { + describe('getLabelTemplate', () => { + const label = mockLabels[0]; + let $labelEl; + + beforeEach(() => { + $labelEl = $( + LabelsSelect.getLabelTemplate({ + labels: mockLabels, + issueUpdateURL: mockUrl, + }), + ); + }); + + it('generated label item template has correct label URL', () => { + expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label'); + }); + + it('generated label item template has correct label title', () => { + expect($labelEl.find('span.label').text()).toBe(label.title); + }); + + it('generated label item template has label description as title attribute', () => { + expect($labelEl.find('span.label').attr('title')).toBe(label.description); + }); + + it('generated label item template has correct label styles', () => { + expect($labelEl.find('span.label').attr('style')).toBe( + `background-color: ${label.color}; color: ${label.text_color};`, + ); + }); + + it('generated label item has a badge class', () => { + expect($labelEl.find('span').hasClass('badge')).toEqual(true); + }); + }); +}); diff --git a/spec/frontend/lib/utils/cache_spec.js b/spec/frontend/lib/utils/cache_spec.js new file mode 100644 index 00000000000..2fe02a7592c --- /dev/null +++ b/spec/frontend/lib/utils/cache_spec.js @@ -0,0 +1,65 @@ +import Cache from '~/lib/utils/cache'; + +describe('Cache', () => { + const dummyKey = 'just some key'; + const dummyValue = 'more than a value'; + let cache; + + beforeEach(() => { + cache = new Cache(); + }); + + describe('get', () => { + it('return cached data', () => { + cache.internalStorage[dummyKey] = dummyValue; + + expect(cache.get(dummyKey)).toBe(dummyValue); + }); + + it('returns undefined for missing data', () => { + expect(cache.internalStorage[dummyKey]).toBe(undefined); + expect(cache.get(dummyKey)).toBe(undefined); + }); + }); + + describe('hasData', () => { + it('return true for cached data', () => { + cache.internalStorage[dummyKey] = dummyValue; + + expect(cache.hasData(dummyKey)).toBe(true); + }); + + it('returns false for missing data', () => { + expect(cache.internalStorage[dummyKey]).toBe(undefined); + expect(cache.hasData(dummyKey)).toBe(false); + }); + }); + + describe('remove', () => { + it('removes data from cache', () => { + cache.internalStorage[dummyKey] = dummyValue; + + cache.remove(dummyKey); + + expect(cache.internalStorage[dummyKey]).toBe(undefined); + }); + + it('does nothing for missing data', () => { + expect(cache.internalStorage[dummyKey]).toBe(undefined); + + cache.remove(dummyKey); + + expect(cache.internalStorage[dummyKey]).toBe(undefined); + }); + + it('does not remove wrong data', () => { + cache.internalStorage[dummyKey] = dummyValue; + cache.internalStorage[dummyKey + dummyKey] = dummyValue + dummyValue; + + cache.remove(dummyKey); + + expect(cache.internalStorage[dummyKey]).toBe(undefined); + expect(cache.internalStorage[dummyKey + dummyKey]).toBe(dummyValue + dummyValue); + }); + }); +}); diff --git a/spec/frontend/lib/utils/grammar_spec.js b/spec/frontend/lib/utils/grammar_spec.js new file mode 100644 index 00000000000..377b2ffb48c --- /dev/null +++ b/spec/frontend/lib/utils/grammar_spec.js @@ -0,0 +1,35 @@ +import * as grammar from '~/lib/utils/grammar'; + +describe('utils/grammar', () => { + describe('toNounSeriesText', () => { + it('with empty items returns empty string', () => { + expect(grammar.toNounSeriesText([])).toBe(''); + }); + + it('with single item returns item', () => { + const items = ['Lorem Ipsum']; + + expect(grammar.toNounSeriesText(items)).toBe(items[0]); + }); + + it('with 2 items returns item1 and item2', () => { + const items = ['Dolar', 'Sit Amit']; + + expect(grammar.toNounSeriesText(items)).toBe(`${items[0]} and ${items[1]}`); + }); + + it('with 3 items returns comma separated series', () => { + const items = ['Lorem', 'Ipsum', 'dolar']; + const expected = 'Lorem, Ipsum, and dolar'; + + expect(grammar.toNounSeriesText(items)).toBe(expected); + }); + + it('with 6 items returns comma separated series', () => { + const items = ['Lorem', 'ipsum', 'dolar', 'sit', 'amit', 'consectetur']; + const expected = 'Lorem, ipsum, dolar, sit, amit, and consectetur'; + + expect(grammar.toNounSeriesText(items)).toBe(expected); + }); + }); +}); diff --git a/spec/frontend/lib/utils/image_utility_spec.js b/spec/frontend/lib/utils/image_utility_spec.js new file mode 100644 index 00000000000..a7eff419fba --- /dev/null +++ b/spec/frontend/lib/utils/image_utility_spec.js @@ -0,0 +1,32 @@ +import { isImageLoaded } from '~/lib/utils/image_utility'; + +describe('imageUtility', () => { + describe('isImageLoaded', () => { + it('should return false when image.complete is false', () => { + const element = { + complete: false, + naturalHeight: 100, + }; + + expect(isImageLoaded(element)).toEqual(false); + }); + + it('should return false when naturalHeight = 0', () => { + const element = { + complete: true, + naturalHeight: 0, + }; + + expect(isImageLoaded(element)).toEqual(false); + }); + + it('should return true when image.complete and naturalHeight != 0', () => { + const element = { + complete: true, + naturalHeight: 100, + }; + + expect(isImageLoaded(element)).toEqual(true); + }); + }); +}); diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js new file mode 100644 index 00000000000..818404bad81 --- /dev/null +++ b/spec/frontend/lib/utils/number_utility_spec.js @@ -0,0 +1,101 @@ +import { + formatRelevantDigits, + bytesToKiB, + bytesToMiB, + bytesToGiB, + numberToHumanSize, + sum, +} from '~/lib/utils/number_utils'; + +describe('Number Utils', () => { + describe('formatRelevantDigits', () => { + it('returns an empty string when the number is NaN', () => { + expect(formatRelevantDigits('fail')).toBe(''); + }); + + it('returns 4 decimals when there is 4 plus digits to the left', () => { + const formattedNumber = formatRelevantDigits('1000.1234567'); + const rightFromDecimal = formattedNumber.split('.')[1]; + const leftFromDecimal = formattedNumber.split('.')[0]; + + expect(rightFromDecimal.length).toBe(4); + expect(leftFromDecimal.length).toBe(4); + }); + + it('returns 3 decimals when there is 1 digit to the left', () => { + const formattedNumber = formatRelevantDigits('0.1234567'); + const rightFromDecimal = formattedNumber.split('.')[1]; + const leftFromDecimal = formattedNumber.split('.')[0]; + + expect(rightFromDecimal.length).toBe(3); + expect(leftFromDecimal.length).toBe(1); + }); + + it('returns 2 decimals when there is 2 digits to the left', () => { + const formattedNumber = formatRelevantDigits('10.1234567'); + const rightFromDecimal = formattedNumber.split('.')[1]; + const leftFromDecimal = formattedNumber.split('.')[0]; + + expect(rightFromDecimal.length).toBe(2); + expect(leftFromDecimal.length).toBe(2); + }); + + it('returns 1 decimal when there is 3 digits to the left', () => { + const formattedNumber = formatRelevantDigits('100.1234567'); + const rightFromDecimal = formattedNumber.split('.')[1]; + const leftFromDecimal = formattedNumber.split('.')[0]; + + expect(rightFromDecimal.length).toBe(1); + expect(leftFromDecimal.length).toBe(3); + }); + }); + + describe('bytesToKiB', () => { + it('calculates KiB for the given bytes', () => { + expect(bytesToKiB(1024)).toEqual(1); + expect(bytesToKiB(1000)).toEqual(0.9765625); + }); + }); + + describe('bytesToMiB', () => { + it('calculates MiB for the given bytes', () => { + expect(bytesToMiB(1048576)).toEqual(1); + expect(bytesToMiB(1000000)).toEqual(0.95367431640625); + }); + }); + + describe('bytesToGiB', () => { + it('calculates GiB for the given bytes', () => { + expect(bytesToGiB(1073741824)).toEqual(1); + expect(bytesToGiB(10737418240)).toEqual(10); + }); + }); + + describe('numberToHumanSize', () => { + it('should return bytes', () => { + expect(numberToHumanSize(654)).toEqual('654 bytes'); + }); + + it('should return KiB', () => { + expect(numberToHumanSize(1079)).toEqual('1.05 KiB'); + }); + + it('should return MiB', () => { + expect(numberToHumanSize(10485764)).toEqual('10.00 MiB'); + }); + + it('should return GiB', () => { + expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB'); + }); + }); + + describe('sum', () => { + it('should add up two values', () => { + expect(sum(1, 2)).toEqual(3); + }); + + it('should add up all the values in an array when passed to a reducer', () => { + expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15); + }); + }); +}); diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js new file mode 100644 index 00000000000..0a266b19ea5 --- /dev/null +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -0,0 +1,154 @@ +import * as textUtils from '~/lib/utils/text_utility'; + +describe('text_utility', () => { + describe('addDelimiter', () => { + it('should add a delimiter to the given string', () => { + expect(textUtils.addDelimiter('1234')).toEqual('1,234'); + expect(textUtils.addDelimiter('222222')).toEqual('222,222'); + }); + + it('should not add a delimiter if string contains no numbers', () => { + expect(textUtils.addDelimiter('aaaa')).toEqual('aaaa'); + }); + }); + + describe('highCountTrim', () => { + it('returns 99+ for count >= 100', () => { + expect(textUtils.highCountTrim(105)).toBe('99+'); + expect(textUtils.highCountTrim(100)).toBe('99+'); + }); + + it('returns exact number for count < 100', () => { + expect(textUtils.highCountTrim(45)).toBe(45); + }); + }); + + describe('capitalizeFirstCharacter', () => { + it('returns string with first letter capitalized', () => { + expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab'); + expect(textUtils.highCountTrim(105)).toBe('99+'); + expect(textUtils.highCountTrim(100)).toBe('99+'); + }); + }); + + describe('humanize', () => { + it('should remove underscores and uppercase the first letter', () => { + expect(textUtils.humanize('foo_bar')).toEqual('Foo bar'); + }); + }); + + describe('pluralize', () => { + it('should pluralize given string', () => { + expect(textUtils.pluralize('test', 2)).toBe('tests'); + }); + + it('should pluralize when count is 0', () => { + expect(textUtils.pluralize('test', 0)).toBe('tests'); + }); + + it('should not pluralize when count is 1', () => { + expect(textUtils.pluralize('test', 1)).toBe('test'); + }); + }); + + describe('dasherize', () => { + it('should replace underscores with dashes', () => { + expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo'); + }); + }); + + describe('slugify', () => { + it('should remove accents and convert to lower case', () => { + expect(textUtils.slugify('João')).toEqual('joão'); + }); + }); + + describe('slugifyWithHyphens', () => { + it('should replaces whitespaces with hyphens and convert to lower case', () => { + expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string'); + }); + }); + + describe('stripHtml', () => { + it('replaces html tag with the default replacement', () => { + expect(textUtils.stripHtml('This is a text with

    html

    .')).toEqual( + 'This is a text with html.', + ); + }); + + it('replaces html tags with the provided replacement', () => { + expect(textUtils.stripHtml('This is a text with

    html

    .', ' ')).toEqual( + 'This is a text with html .', + ); + }); + + it('passes through with null string input', () => { + expect(textUtils.stripHtml(null, ' ')).toEqual(null); + }); + + it('passes through with undefined string input', () => { + expect(textUtils.stripHtml(undefined, ' ')).toEqual(undefined); + }); + }); + + describe('convertToCamelCase', () => { + it('converts snake_case string to camelCase string', () => { + expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase'); + }); + }); + + describe('convertToSentenceCase', () => { + it('converts Sentence Case to Sentence case', () => { + expect(textUtils.convertToSentenceCase('Hello World')).toBe('Hello world'); + }); + }); + + describe('truncateSha', () => { + it('shortens SHAs to 8 characters', () => { + expect(textUtils.truncateSha('verylongsha')).toBe('verylong'); + }); + + it('leaves short SHAs as is', () => { + expect(textUtils.truncateSha('shortsha')).toBe('shortsha'); + }); + }); + + describe('splitCamelCase', () => { + it('separates a PascalCase word to two', () => { + expect(textUtils.splitCamelCase('HelloWorld')).toBe('Hello World'); + }); + }); + + describe('getFirstCharacterCapitalized', () => { + it('returns the first character capitalized, if first character is alphabetic', () => { + expect(textUtils.getFirstCharacterCapitalized('loremIpsumDolar')).toEqual('L'); + expect(textUtils.getFirstCharacterCapitalized('Sit amit !')).toEqual('S'); + }); + + it('returns the first character, if first character is non-alphabetic', () => { + expect(textUtils.getFirstCharacterCapitalized(' lorem')).toEqual(' '); + expect(textUtils.getFirstCharacterCapitalized('%#!')).toEqual('%'); + }); + + it('returns an empty string, if string is falsey', () => { + expect(textUtils.getFirstCharacterCapitalized('')).toEqual(''); + expect(textUtils.getFirstCharacterCapitalized(null)).toEqual(''); + }); + }); + + describe('truncatePathMiddleToLength', () => { + it('does not truncate text', () => { + expect(textUtils.truncatePathMiddleToLength('app/test', 50)).toEqual('app/test'); + }); + + it('truncates middle of the path', () => { + expect(textUtils.truncatePathMiddleToLength('app/test/diff', 13)).toEqual('app/…/diff'); + }); + + it('truncates multiple times in the middle of the path', () => { + expect(textUtils.truncatePathMiddleToLength('app/test/merge_request/diff', 13)).toEqual( + 'app/…/…/diff', + ); + }); + }); +}); diff --git a/spec/frontend/locale/ensure_single_line_spec.js b/spec/frontend/locale/ensure_single_line_spec.js new file mode 100644 index 00000000000..20b04cab9c8 --- /dev/null +++ b/spec/frontend/locale/ensure_single_line_spec.js @@ -0,0 +1,38 @@ +import ensureSingleLine from '~/locale/ensure_single_line'; + +describe('locale', () => { + describe('ensureSingleLine', () => { + it('should remove newlines at the start of the string', () => { + const result = 'Test'; + + expect(ensureSingleLine(`\n${result}`)).toBe(result); + expect(ensureSingleLine(`\t\n\t${result}`)).toBe(result); + expect(ensureSingleLine(`\r\n${result}`)).toBe(result); + expect(ensureSingleLine(`\r\n ${result}`)).toBe(result); + expect(ensureSingleLine(`\r ${result}`)).toBe(result); + expect(ensureSingleLine(` \n ${result}`)).toBe(result); + }); + + it('should remove newlines at the end of the string', () => { + const result = 'Test'; + + expect(ensureSingleLine(`${result}\n`)).toBe(result); + expect(ensureSingleLine(`${result}\t\n\t`)).toBe(result); + expect(ensureSingleLine(`${result}\r\n`)).toBe(result); + expect(ensureSingleLine(`${result}\r`)).toBe(result); + expect(ensureSingleLine(`${result} \r`)).toBe(result); + expect(ensureSingleLine(`${result} \r\n `)).toBe(result); + }); + + it('should replace newlines in the middle of the string with a single space', () => { + const result = 'Test'; + + expect(ensureSingleLine(`${result}\n${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result}\t\n\t${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result}\r\n${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result}\r${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result} \r${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result} \r\n ${result}`)).toBe(`${result} ${result}`); + }); + }); +}); diff --git a/spec/frontend/locale/sprintf_spec.js b/spec/frontend/locale/sprintf_spec.js new file mode 100644 index 00000000000..52e903b819f --- /dev/null +++ b/spec/frontend/locale/sprintf_spec.js @@ -0,0 +1,74 @@ +import sprintf from '~/locale/sprintf'; + +describe('locale', () => { + describe('sprintf', () => { + it('does not modify string without parameters', () => { + const input = 'No parameters'; + + const output = sprintf(input); + + expect(output).toBe(input); + }); + + it('ignores extraneous parameters', () => { + const input = 'No parameters'; + + const output = sprintf(input, { ignore: 'this' }); + + expect(output).toBe(input); + }); + + it('ignores extraneous placeholders', () => { + const input = 'No %{parameters}'; + + const output = sprintf(input); + + expect(output).toBe(input); + }); + + it('replaces parameters', () => { + const input = '%{name} has %{count} parameters'; + const parameters = { + name: 'this', + count: 2, + }; + + const output = sprintf(input, parameters); + + expect(output).toBe('this has 2 parameters'); + }); + + it('replaces multiple occurrences', () => { + const input = 'to %{verb} or not to %{verb}'; + const parameters = { + verb: 'be', + }; + + const output = sprintf(input, parameters); + + expect(output).toBe('to be or not to be'); + }); + + it('escapes parameters', () => { + const input = 'contains %{userContent}'; + const parameters = { + userContent: '', + }; + + const output = sprintf(input, parameters); + + expect(output).toBe('contains <script>alert("malicious!")</script>'); + }); + + it('does not escape parameters for escapeParameters = false', () => { + const input = 'contains %{safeContent}'; + const parameters = { + safeContent: 'bold attempt', + }; + + const output = sprintf(input, parameters, false); + + expect(output).toBe('contains bold attempt'); + }); + }); +}); diff --git a/spec/frontend/notebook/lib/highlight_spec.js b/spec/frontend/notebook/lib/highlight_spec.js new file mode 100644 index 00000000000..d71c5718858 --- /dev/null +++ b/spec/frontend/notebook/lib/highlight_spec.js @@ -0,0 +1,15 @@ +import Prism from '~/notebook/lib/highlight'; + +describe('Highlight library', () => { + it('imports python language', () => { + expect(Prism.languages.python).toBeDefined(); + }); + + it('uses custom CSS classes', () => { + const el = document.createElement('div'); + el.innerHTML = Prism.highlight('console.log("a");', Prism.languages.javascript); + + expect(el.querySelector('.s')).not.toBeNull(); + expect(el.querySelector('.nf')).not.toBeNull(); + }); +}); diff --git a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js new file mode 100644 index 00000000000..07a366cf339 --- /dev/null +++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js @@ -0,0 +1,34 @@ +import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; + +const localVue = createLocalVue(); + +describe('ReplyPlaceholder', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallowMount(ReplyPlaceholder, { + localVue, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('emits onClick even on button click', () => { + const button = wrapper.find({ ref: 'button' }); + + button.trigger('click'); + + expect(wrapper.emitted()).toEqual({ + onClick: [[]], + }); + }); + + it('should render reply button', () => { + const button = wrapper.find({ ref: 'button' }); + + expect(button.text()).toEqual('Reply...'); + }); +}); diff --git a/spec/frontend/notes/components/discussion_resolve_button_spec.js b/spec/frontend/notes/components/discussion_resolve_button_spec.js new file mode 100644 index 00000000000..5024f40ec5d --- /dev/null +++ b/spec/frontend/notes/components/discussion_resolve_button_spec.js @@ -0,0 +1,74 @@ +import resolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; + +const buttonTitle = 'Resolve discussion'; + +describe('resolveDiscussionButton', () => { + let wrapper; + let localVue; + + const factory = options => { + localVue = createLocalVue(); + wrapper = shallowMount(resolveDiscussionButton, { + localVue, + ...options, + }); + }; + + beforeEach(() => { + factory({ + propsData: { + isResolving: false, + buttonTitle, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should emit a onClick event on button click', () => { + const button = wrapper.find({ ref: 'button' }); + + button.trigger('click'); + + expect(wrapper.emitted()).toEqual({ + onClick: [[]], + }); + }); + + it('should contain the provided button title', () => { + const button = wrapper.find({ ref: 'button' }); + + expect(button.text()).toContain(buttonTitle); + }); + + it('should show a loading spinner while resolving', () => { + factory({ + propsData: { + isResolving: true, + buttonTitle, + }, + }); + + const button = wrapper.find({ ref: 'isResolvingIcon' }); + + expect(button.exists()).toEqual(true); + }); + + it('should only show a loading spinner while resolving', () => { + factory({ + propsData: { + isResolving: false, + buttonTitle, + }, + }); + + const button = wrapper.find({ ref: 'isResolvingIcon' }); + + localVue.nextTick(() => { + expect(button.exists()).toEqual(false); + }); + }); +}); diff --git a/spec/frontend/notes/components/note_attachment_spec.js b/spec/frontend/notes/components/note_attachment_spec.js new file mode 100644 index 00000000000..b14a518b622 --- /dev/null +++ b/spec/frontend/notes/components/note_attachment_spec.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import noteAttachment from '~/notes/components/note_attachment.vue'; + +describe('issue note attachment', () => { + it('should render properly', () => { + const props = { + attachment: { + filename: 'dk.png', + image: true, + url: '/dk.png', + }, + }; + + const Component = Vue.extend(noteAttachment); + const vm = new Component({ + propsData: props, + }).$mount(); + + expect(vm.$el.classList.contains('note-attachment')).toBeTruthy(); + expect(vm.$el.querySelector('img').src).toContain(props.attachment.url); + expect(vm.$el.querySelector('a').href).toContain(props.attachment.url); + }); +}); diff --git a/spec/frontend/notes/components/note_edited_text_spec.js b/spec/frontend/notes/components/note_edited_text_spec.js new file mode 100644 index 00000000000..e4c8d954d50 --- /dev/null +++ b/spec/frontend/notes/components/note_edited_text_spec.js @@ -0,0 +1,47 @@ +import Vue from 'vue'; +import noteEditedText from '~/notes/components/note_edited_text.vue'; + +describe('note_edited_text', () => { + let vm; + let props; + + beforeEach(() => { + const Component = Vue.extend(noteEditedText); + props = { + actionText: 'Edited', + className: 'foo-bar', + editedAt: '2017-08-04T09:52:31.062Z', + editedBy: { + avatar_url: 'path', + id: 1, + name: 'Root', + path: '/root', + state: 'active', + username: 'root', + }, + }; + + vm = new Component({ + propsData: props, + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render block with provided className', () => { + expect(vm.$el.className).toEqual(props.className); + }); + + it('should render provided actionText', () => { + expect(vm.$el.textContent).toContain(props.actionText); + }); + + it('should render provided user information', () => { + const authorLink = vm.$el.querySelector('.js-user-link'); + + expect(authorLink.getAttribute('href')).toEqual(props.editedBy.path); + expect(authorLink.textContent.trim()).toEqual(props.editedBy.name); + }); +}); diff --git a/spec/frontend/performance_bar/services/performance_bar_service_spec.js b/spec/frontend/performance_bar/services/performance_bar_service_spec.js new file mode 100644 index 00000000000..cfec4b779e4 --- /dev/null +++ b/spec/frontend/performance_bar/services/performance_bar_service_spec.js @@ -0,0 +1,68 @@ +import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; + +describe('PerformanceBarService', () => { + describe('callbackParams', () => { + describe('fireCallback', () => { + function fireCallback(response, peekUrl) { + return PerformanceBarService.callbackParams(response, peekUrl)[0]; + } + + it('returns false when the request URL is the peek URL', () => { + expect( + fireCallback({ headers: { 'x-request-id': '123' }, url: '/peek' }, '/peek'), + ).toBeFalsy(); + }); + + it('returns false when there is no request ID', () => { + expect(fireCallback({ headers: {}, url: '/request' }, '/peek')).toBeFalsy(); + }); + + it('returns false when the request is an API request', () => { + expect( + fireCallback({ headers: { 'x-request-id': '123' }, url: '/api/' }, '/peek'), + ).toBeFalsy(); + }); + + it('returns false when the response is from the cache', () => { + expect( + fireCallback( + { headers: { 'x-request-id': '123', 'x-gitlab-from-cache': 'true' }, url: '/request' }, + '/peek', + ), + ).toBeFalsy(); + }); + + it('returns true when all conditions are met', () => { + expect( + fireCallback({ headers: { 'x-request-id': '123' }, url: '/request' }, '/peek'), + ).toBeTruthy(); + }); + }); + + describe('requestId', () => { + function requestId(response, peekUrl) { + return PerformanceBarService.callbackParams(response, peekUrl)[1]; + } + + it('gets the request ID from the headers', () => { + expect(requestId({ headers: { 'x-request-id': '123' } }, '/peek')).toEqual('123'); + }); + }); + + describe('requestUrl', () => { + function requestUrl(response, peekUrl) { + return PerformanceBarService.callbackParams(response, peekUrl)[2]; + } + + it('gets the request URL from the response object', () => { + expect(requestUrl({ headers: {}, url: '/request' }, '/peek')).toEqual('/request'); + }); + + it('gets the request URL from response.config if present', () => { + expect( + requestUrl({ headers: {}, config: { url: '/config-url' }, url: '/request' }, '/peek'), + ).toEqual('/config-url'); + }); + }); + }); +}); diff --git a/spec/frontend/pipelines/blank_state_spec.js b/spec/frontend/pipelines/blank_state_spec.js new file mode 100644 index 00000000000..033bd5ccb73 --- /dev/null +++ b/spec/frontend/pipelines/blank_state_spec.js @@ -0,0 +1,25 @@ +import Vue from 'vue'; +import component from '~/pipelines/components/blank_state.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Pipelines Blank State', () => { + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(component); + + vm = mountComponent(Component, { + svgPath: 'foo', + message: 'Blank State', + }); + }); + + it('should render svg', () => { + expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toEqual('foo'); + }); + + it('should render message', () => { + expect(vm.$el.querySelector('h4').textContent.trim()).toEqual('Blank State'); + }); +}); diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js new file mode 100644 index 00000000000..f12950b8fce --- /dev/null +++ b/spec/frontend/pipelines/empty_state_spec.js @@ -0,0 +1,58 @@ +import Vue from 'vue'; +import emptyStateComp from '~/pipelines/components/empty_state.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Pipelines Empty State', () => { + let component; + let EmptyStateComponent; + + beforeEach(() => { + EmptyStateComponent = Vue.extend(emptyStateComp); + + component = mountComponent(EmptyStateComponent, { + helpPagePath: 'foo', + emptyStateSvgPath: 'foo', + canSetCi: true, + }); + }); + + afterEach(() => { + component.$destroy(); + }); + + it('should render empty state SVG', () => { + expect(component.$el.querySelector('.svg-content svg')).toBeDefined(); + }); + + it('should render empty state information', () => { + expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence'); + + expect( + component.$el + .querySelector('p') + .innerHTML.trim() + .replace(/\n+\s+/m, ' ') + .replace(/\s\s+/g, ' '), + ).toContain('Continuous Integration can help catch bugs by running your tests automatically,'); + + expect( + component.$el + .querySelector('p') + .innerHTML.trim() + .replace(/\n+\s+/m, ' ') + .replace(/\s\s+/g, ' '), + ).toContain( + 'while Continuous Deployment can help you deliver code to your product environment', + ); + }); + + it('should render a link with provided help path', () => { + expect(component.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual( + 'foo', + ); + + expect(component.$el.querySelector('.js-get-started-pipelines').textContent).toContain( + 'Get started with Pipelines', + ); + }); +}); diff --git a/spec/frontend/pipelines/pipeline_store_spec.js b/spec/frontend/pipelines/pipeline_store_spec.js new file mode 100644 index 00000000000..1d5754d1f05 --- /dev/null +++ b/spec/frontend/pipelines/pipeline_store_spec.js @@ -0,0 +1,27 @@ +import PipelineStore from '~/pipelines/stores/pipeline_store'; + +describe('Pipeline Store', () => { + let store; + + beforeEach(() => { + store = new PipelineStore(); + }); + + it('should set defaults', () => { + expect(store.state.pipeline).toEqual({}); + }); + + describe('storePipeline', () => { + it('should store empty object if none is provided', () => { + store.storePipeline(); + + expect(store.state.pipeline).toEqual({}); + }); + + it('should store received object', () => { + store.storePipeline({ foo: 'bar' }); + + expect(store.state.pipeline).toEqual({ foo: 'bar' }); + }); + }); +}); diff --git a/spec/frontend/pipelines/pipelines_store_spec.js b/spec/frontend/pipelines/pipelines_store_spec.js new file mode 100644 index 00000000000..ce21f788ed5 --- /dev/null +++ b/spec/frontend/pipelines/pipelines_store_spec.js @@ -0,0 +1,77 @@ +import PipelineStore from '~/pipelines/stores/pipelines_store'; + +describe('Pipelines Store', () => { + let store; + + beforeEach(() => { + store = new PipelineStore(); + }); + + it('should be initialized with an empty state', () => { + expect(store.state.pipelines).toEqual([]); + expect(store.state.count).toEqual({}); + expect(store.state.pageInfo).toEqual({}); + }); + + describe('storePipelines', () => { + it('should use the default parameter if none is provided', () => { + store.storePipelines(); + + expect(store.state.pipelines).toEqual([]); + }); + + it('should store the provided array', () => { + const array = [{ id: 1, status: 'running' }, { id: 2, status: 'success' }]; + store.storePipelines(array); + + expect(store.state.pipelines).toEqual(array); + }); + }); + + describe('storeCount', () => { + it('should use the default parameter if none is provided', () => { + store.storeCount(); + + expect(store.state.count).toEqual({}); + }); + + it('should store the provided count', () => { + const count = { all: 20, finished: 10 }; + store.storeCount(count); + + expect(store.state.count).toEqual(count); + }); + }); + + describe('storePagination', () => { + it('should use the default parameter if none is provided', () => { + store.storePagination(); + + expect(store.state.pageInfo).toEqual({}); + }); + + it('should store pagination information normalized and parsed', () => { + const pagination = { + 'X-nExt-pAge': '2', + 'X-page': '1', + 'X-Per-Page': '1', + 'X-Prev-Page': '2', + 'X-TOTAL': '37', + 'X-Total-Pages': '2', + }; + + const expectedResult = { + perPage: 1, + page: 1, + total: 37, + totalPages: 2, + nextPage: 2, + previousPage: 2, + }; + + store.storePagination(pagination); + + expect(store.state.pageInfo).toEqual(expectedResult); + }); + }); +}); diff --git a/spec/frontend/registry/getters_spec.js b/spec/frontend/registry/getters_spec.js new file mode 100644 index 00000000000..839aa718997 --- /dev/null +++ b/spec/frontend/registry/getters_spec.js @@ -0,0 +1,46 @@ +import * as getters from '~/registry/stores/getters'; + +describe('Getters Registry Store', () => { + let state; + + beforeEach(() => { + state = { + isLoading: false, + endpoint: '/root/empty-project/container_registry.json', + repos: [ + { + canDelete: true, + destroyPath: 'bar', + id: '134', + isLoading: false, + list: [], + location: 'foo', + name: 'gitlab-org/omnibus-gitlab/foo', + tagsPath: 'foo', + }, + { + canDelete: true, + destroyPath: 'bar', + id: '123', + isLoading: false, + list: [], + location: 'foo', + name: 'gitlab-org/omnibus-gitlab', + tagsPath: 'foo', + }, + ], + }; + }); + + describe('isLoading', () => { + it('should return the isLoading property', () => { + expect(getters.isLoading(state)).toEqual(state.isLoading); + }); + }); + + describe('repos', () => { + it('should return the repos', () => { + expect(getters.repos(state)).toEqual(state.repos); + }); + }); +}); diff --git a/spec/frontend/reports/components/report_link_spec.js b/spec/frontend/reports/components/report_link_spec.js new file mode 100644 index 00000000000..f879899e9c5 --- /dev/null +++ b/spec/frontend/reports/components/report_link_spec.js @@ -0,0 +1,69 @@ +import Vue from 'vue'; +import component from '~/reports/components/report_link.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('report link', () => { + let vm; + + const Component = Vue.extend(component); + + afterEach(() => { + vm.$destroy(); + }); + + describe('With url', () => { + it('renders link', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: '/Gemfile.lock', + }, + }); + + expect(vm.$el.textContent.trim()).toContain('in'); + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('/Gemfile.lock'); + expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Gemfile.lock'); + }); + }); + + describe('Without url', () => { + it('does not render link', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + }, + }); + + expect(vm.$el.querySelector('a')).toBeNull(); + expect(vm.$el.textContent.trim()).toContain('in'); + expect(vm.$el.textContent.trim()).toContain('Gemfile.lock'); + }); + }); + + describe('with line', () => { + it('renders line number', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', + line: 22, + }, + }); + + expect(vm.$el.querySelector('a').textContent.trim()).toContain('Gemfile.lock:22'); + }); + }); + + describe('without line', () => { + it('does not render line number', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', + }, + }); + + expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(':22'); + }); + }); +}); diff --git a/spec/frontend/reports/store/utils_spec.js b/spec/frontend/reports/store/utils_spec.js new file mode 100644 index 00000000000..1679d120db2 --- /dev/null +++ b/spec/frontend/reports/store/utils_spec.js @@ -0,0 +1,138 @@ +import * as utils from '~/reports/store/utils'; +import { + STATUS_FAILED, + STATUS_SUCCESS, + ICON_WARNING, + ICON_SUCCESS, + ICON_NOTFOUND, +} from '~/reports/constants'; + +describe('Reports store utils', () => { + describe('summaryTextbuilder', () => { + it('should render text for no changed results in multiple tests', () => { + const name = 'Test summary'; + const data = { total: 10 }; + const result = utils.summaryTextBuilder(name, data); + + expect(result).toBe('Test summary contained no changed test results out of 10 total tests'); + }); + + it('should render text for no changed results in one test', () => { + const name = 'Test summary'; + const data = { total: 1 }; + const result = utils.summaryTextBuilder(name, data); + + expect(result).toBe('Test summary contained no changed test results out of 1 total test'); + }); + + it('should render text for multiple failed results', () => { + const name = 'Test summary'; + const data = { failed: 3, total: 10 }; + const result = utils.summaryTextBuilder(name, data); + + expect(result).toBe('Test summary contained 3 failed test results out of 10 total tests'); + }); + + it('should render text for multiple fixed results', () => { + const name = 'Test summary'; + const data = { resolved: 4, total: 10 }; + const result = utils.summaryTextBuilder(name, data); + + expect(result).toBe('Test summary contained 4 fixed test results out of 10 total tests'); + }); + + it('should render text for multiple fixed, and multiple failed results', () => { + const name = 'Test summary'; + const data = { failed: 3, resolved: 4, total: 10 }; + const result = utils.summaryTextBuilder(name, data); + + expect(result).toBe( + 'Test summary contained 3 failed test results and 4 fixed test results out of 10 total tests', + ); + }); + + it('should render text for a singular fixed, and a singular failed result', () => { + const name = 'Test summary'; + const data = { failed: 1, resolved: 1, total: 10 }; + const result = utils.summaryTextBuilder(name, data); + + expect(result).toBe( + 'Test summary contained 1 failed test result and 1 fixed test result out of 10 total tests', + ); + }); + }); + + describe('reportTextBuilder', () => { + it('should render text for no changed results in multiple tests', () => { + const name = 'Rspec'; + const data = { total: 10 }; + const result = utils.reportTextBuilder(name, data); + + expect(result).toBe('Rspec found no changed test results out of 10 total tests'); + }); + + it('should render text for no changed results in one test', () => { + const name = 'Rspec'; + const data = { total: 1 }; + const result = utils.reportTextBuilder(name, data); + + expect(result).toBe('Rspec found no changed test results out of 1 total test'); + }); + + it('should render text for multiple failed results', () => { + const name = 'Rspec'; + const data = { failed: 3, total: 10 }; + const result = utils.reportTextBuilder(name, data); + + expect(result).toBe('Rspec found 3 failed test results out of 10 total tests'); + }); + + it('should render text for multiple fixed results', () => { + const name = 'Rspec'; + const data = { resolved: 4, total: 10 }; + const result = utils.reportTextBuilder(name, data); + + expect(result).toBe('Rspec found 4 fixed test results out of 10 total tests'); + }); + + it('should render text for multiple fixed, and multiple failed results', () => { + const name = 'Rspec'; + const data = { failed: 3, resolved: 4, total: 10 }; + const result = utils.reportTextBuilder(name, data); + + expect(result).toBe( + 'Rspec found 3 failed test results and 4 fixed test results out of 10 total tests', + ); + }); + + it('should render text for a singular fixed, and a singular failed result', () => { + const name = 'Rspec'; + const data = { failed: 1, resolved: 1, total: 10 }; + const result = utils.reportTextBuilder(name, data); + + expect(result).toBe( + 'Rspec found 1 failed test result and 1 fixed test result out of 10 total tests', + ); + }); + }); + + describe('statusIcon', () => { + describe('with failed status', () => { + it('returns ICON_WARNING', () => { + expect(utils.statusIcon(STATUS_FAILED)).toEqual(ICON_WARNING); + }); + }); + + describe('with success status', () => { + it('returns ICON_SUCCESS', () => { + expect(utils.statusIcon(STATUS_SUCCESS)).toEqual(ICON_SUCCESS); + }); + }); + + describe('without a status', () => { + it('returns ICON_NOTFOUND', () => { + expect(utils.statusIcon()).toEqual(ICON_NOTFOUND); + }); + }); + }); +}); diff --git a/spec/frontend/sidebar/confidential_edit_buttons_spec.js b/spec/frontend/sidebar/confidential_edit_buttons_spec.js new file mode 100644 index 00000000000..32da9f83112 --- /dev/null +++ b/spec/frontend/sidebar/confidential_edit_buttons_spec.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import editFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue'; + +describe('Edit Form Buttons', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editFormButtons); + const toggleForm = () => {}; + const updateConfidentialAttribute = () => {}; + + vm1 = new Component({ + propsData: { + isConfidential: true, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + }); + + it('renders on or off text based on confidentiality', () => { + expect(vm1.$el.innerHTML.includes('Turn Off')).toBe(true); + + expect(vm2.$el.innerHTML.includes('Turn On')).toBe(true); + }); +}); diff --git a/spec/frontend/sidebar/confidential_edit_form_buttons_spec.js b/spec/frontend/sidebar/confidential_edit_form_buttons_spec.js new file mode 100644 index 00000000000..369088cb258 --- /dev/null +++ b/spec/frontend/sidebar/confidential_edit_form_buttons_spec.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import editForm from '~/sidebar/components/confidential/edit_form.vue'; + +describe('Edit Form Dropdown', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editForm); + const toggleForm = () => {}; + const updateConfidentialAttribute = () => {}; + + vm1 = new Component({ + propsData: { + isConfidential: true, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + }); + + it('renders on the appropriate warning text', () => { + expect(vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.')).toBe(true); + + expect(vm2.$el.innerHTML.includes('You are going to turn on the confidentiality.')).toBe(true); + }); +}); diff --git a/spec/frontend/sidebar/lock/edit_form_spec.js b/spec/frontend/sidebar/lock/edit_form_spec.js new file mode 100644 index 00000000000..ec10a999a40 --- /dev/null +++ b/spec/frontend/sidebar/lock/edit_form_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import editForm from '~/sidebar/components/lock/edit_form.vue'; + +describe('EditForm', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editForm); + const toggleForm = () => {}; + const updateLockedAttribute = () => {}; + + vm1 = new Component({ + propsData: { + isLocked: true, + toggleForm, + updateLockedAttribute, + issuableType: 'issue', + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isLocked: false, + toggleForm, + updateLockedAttribute, + issuableType: 'merge_request', + }, + }).$mount(); + }); + + it('renders on the appropriate warning text', () => { + expect(vm1.$el.innerHTML.includes('Unlock this issue?')).toBe(true); + + expect(vm2.$el.innerHTML.includes('Lock this merge request?')).toBe(true); + }); +}); diff --git a/spec/frontend/u2f/util_spec.js b/spec/frontend/u2f/util_spec.js new file mode 100644 index 00000000000..32cd6891384 --- /dev/null +++ b/spec/frontend/u2f/util_spec.js @@ -0,0 +1,61 @@ +import { canInjectU2fApi } from '~/u2f/util'; + +describe('U2F Utils', () => { + describe('canInjectU2fApi', () => { + it('returns false for Chrome < 41', () => { + const userAgent = + 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.28 Safari/537.36'; + + expect(canInjectU2fApi(userAgent)).toBe(false); + }); + + it('returns true for Chrome >= 41', () => { + const userAgent = + 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'; + + expect(canInjectU2fApi(userAgent)).toBe(true); + }); + + it('returns false for Opera < 40', () => { + const userAgent = + 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36 OPR/32.0.1948.25'; + + expect(canInjectU2fApi(userAgent)).toBe(false); + }); + + it('returns true for Opera >= 40', () => { + const userAgent = + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 OPR/43.0.2442.991'; + + expect(canInjectU2fApi(userAgent)).toBe(true); + }); + + it('returns false for Safari', () => { + const userAgent = + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4'; + + expect(canInjectU2fApi(userAgent)).toBe(false); + }); + + it('returns false for Chrome on Android', () => { + const userAgent = + 'Mozilla/5.0 (Linux; Android 7.0; VS988 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3145.0 Mobile Safari/537.36'; + + expect(canInjectU2fApi(userAgent)).toBe(false); + }); + + it('returns false for Chrome on iOS', () => { + const userAgent = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1'; + + expect(canInjectU2fApi(userAgent)).toBe(false); + }); + + it('returns false for Safari on iOS', () => { + const userAgent = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A356 Safari/604.1'; + + expect(canInjectU2fApi(userAgent)).toBe(false); + }); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js new file mode 100644 index 00000000000..16c8c939a6f --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js @@ -0,0 +1,51 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import MrWidgetContainer from '~/vue_merge_request_widget/components/mr_widget_container.vue'; + +const BODY_HTML = '
    Hello World
    '; +const FOOTER_HTML = ''; + +describe('MrWidgetContainer', () => { + let wrapper; + + const factory = (options = {}) => { + const localVue = createLocalVue(); + + wrapper = shallowMount(localVue.extend(MrWidgetContainer), { + localVue, + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('has layout', () => { + factory(); + + expect(wrapper.is('.mr-widget-heading')).toBe(true); + expect(wrapper.contains('.mr-widget-content')).toBe(true); + }); + + it('accepts default slot', () => { + factory({ + slots: { + default: BODY_HTML, + }, + }); + + expect(wrapper.contains('.mr-widget-content .test-body')).toBe(true); + }); + + it('accepts footer slot', () => { + factory({ + slots: { + default: BODY_HTML, + footer: FOOTER_HTML, + }, + }); + + expect(wrapper.contains('.mr-widget-content .test-body')).toBe(true); + expect(wrapper.contains('.test-footer')).toBe(true); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js new file mode 100644 index 00000000000..f7c2376eebf --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js @@ -0,0 +1,30 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue'; +import Icon from '~/vue_shared/components/icon.vue'; + +const TEST_ICON = 'commit'; + +describe('MrWidgetIcon', () => { + let wrapper; + + beforeEach(() => { + const localVue = createLocalVue(); + + wrapper = shallowMount(localVue.extend(MrWidgetIcon), { + propsData: { + name: TEST_ICON, + }, + sync: false, + localVue, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders icon and container', () => { + expect(wrapper.is('.circle-icon-container')).toBe(true); + expect(wrapper.find(Icon).props('name')).toEqual(TEST_ICON); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js new file mode 100644 index 00000000000..994d6255324 --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js @@ -0,0 +1,85 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; + +const localVue = createLocalVue(); +const testCommitMessage = 'Test commit message'; +const testLabel = 'Test label'; +const testInputId = 'test-input-id'; + +describe('Commits edit component', () => { + let wrapper; + + const createComponent = (slots = {}) => { + wrapper = shallowMount(localVue.extend(CommitEdit), { + localVue, + sync: false, + propsData: { + value: testCommitMessage, + label: testLabel, + inputId: testInputId, + }, + slots: { + ...slots, + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findTextarea = () => wrapper.find('.form-control'); + + it('has a correct label', () => { + const labelElement = wrapper.find('.col-form-label'); + + expect(labelElement.text()).toBe(testLabel); + }); + + describe('textarea', () => { + it('has a correct ID', () => { + expect(findTextarea().attributes('id')).toBe(testInputId); + }); + + it('has a correct value', () => { + expect(findTextarea().element.value).toBe(testCommitMessage); + }); + + it('emits an input event and receives changed value', () => { + const changedCommitMessage = 'Changed commit message'; + + findTextarea().element.value = changedCommitMessage; + findTextarea().trigger('input'); + + expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]); + expect(findTextarea().element.value).toBe(changedCommitMessage); + }); + }); + + describe('when slots are present', () => { + beforeEach(() => { + createComponent({ + header: `
    ${testCommitMessage}
    `, + checkbox: ``, + }); + }); + + it('renders header slot correctly', () => { + const headerSlotElement = wrapper.find('.test-header'); + + expect(headerSlotElement.exists()).toBe(true); + expect(headerSlotElement.text()).toBe(testCommitMessage); + }); + + it('renders checkbox slot correctly', () => { + const checkboxSlotElement = wrapper.find('.test-checkbox'); + + expect(checkboxSlotElement.exists()).toBe(true); + expect(checkboxSlotElement.text()).toBe(testLabel); + }); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js new file mode 100644 index 00000000000..daf1cc2d98b --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js @@ -0,0 +1,61 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlDropdownItem } from '@gitlab/ui'; +import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; + +const localVue = createLocalVue(); +const commits = [ + { + title: 'Commit 1', + short_id: '78d5b7', + message: 'Update test.txt', + }, + { + title: 'Commit 2', + short_id: '34cbe28b', + message: 'Fixed test', + }, + { + title: 'Commit 3', + short_id: 'fa42932a', + message: 'Added changelog', + }, +]; + +describe('Commits message dropdown component', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMount(localVue.extend(CommitMessageDropdown), { + localVue, + sync: false, + propsData: { + commits, + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findDropdownElements = () => wrapper.findAll(GlDropdownItem); + const findFirstDropdownElement = () => findDropdownElements().at(0); + + it('should have 3 elements in dropdown list', () => { + expect(findDropdownElements().length).toBe(3); + }); + + it('should have correct message for the first dropdown list element', () => { + expect(findFirstDropdownElement().text()).toBe('78d5b7 Commit 1'); + }); + + it('should emit a commit title on selecting commit', () => { + findFirstDropdownElement().vm.$emit('click'); + + expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js new file mode 100644 index 00000000000..9ee2f88c78d --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js @@ -0,0 +1,136 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; +import Icon from '~/vue_shared/components/icon.vue'; + +const localVue = createLocalVue(); + +describe('Commits header component', () => { + let wrapper; + + const createComponent = props => { + wrapper = shallowMount(localVue.extend(CommitsHeader), { + localVue, + sync: false, + propsData: { + isSquashEnabled: false, + targetBranch: 'master', + commitsCount: 5, + isFastForwardEnabled: false, + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count'); + const findCommitToggle = () => wrapper.find('.commit-edit-toggle'); + const findIcon = () => wrapper.find(Icon); + const findCommitsCountMessage = () => wrapper.find('.commits-count-message'); + const findTargetBranchMessage = () => wrapper.find('.label-branch'); + const findModifyButton = () => wrapper.find('.modify-message-button'); + + describe('when fast-forward is enabled', () => { + beforeEach(() => { + createComponent({ + isFastForwardEnabled: true, + isSquashEnabled: true, + }); + }); + + it('has commits count message showing 1 commit', () => { + expect(findCommitsCountMessage().text()).toBe('1 commit'); + }); + + it('has button with modify commit message', () => { + expect(findModifyButton().text()).toBe('Modify commit message'); + }); + + it('does not have merge commit part of the message', () => { + expect(findHeaderWrapper().text()).not.toContain('1 merge commit'); + }); + }); + + describe('when collapsed', () => { + it('toggle has aria-label equal to Expand', () => { + createComponent(); + + expect(findCommitToggle().attributes('aria-label')).toBe('Expand'); + }); + + it('has a chevron-right icon', () => { + createComponent(); + wrapper.setData({ expanded: false }); + + expect(findIcon().props('name')).toBe('chevron-right'); + }); + + describe('when squash is disabled', () => { + beforeEach(() => { + createComponent(); + }); + + it('has commits count message showing correct amount of commits', () => { + expect(findCommitsCountMessage().text()).toBe('5 commits'); + }); + + it('has button with modify merge commit message', () => { + expect(findModifyButton().text()).toBe('Modify merge commit'); + }); + }); + + describe('when squash is enabled', () => { + beforeEach(() => { + createComponent({ isSquashEnabled: true }); + }); + + it('has commits count message showing one commit when squash is enabled', () => { + expect(findCommitsCountMessage().text()).toBe('1 commit'); + }); + + it('has button with modify commit messages text', () => { + expect(findModifyButton().text()).toBe('Modify commit messages'); + }); + }); + + it('has correct target branch displayed', () => { + createComponent(); + + expect(findTargetBranchMessage().text()).toBe('master'); + }); + + it('does has merge commit part of the message', () => { + expect(findHeaderWrapper().text()).toContain('1 merge commit'); + }); + }); + + describe('when expanded', () => { + beforeEach(() => { + createComponent(); + wrapper.setData({ expanded: true }); + }); + + it('toggle has aria-label equal to collapse', done => { + wrapper.vm.$nextTick(() => { + expect(findCommitToggle().attributes('aria-label')).toBe('Collapse'); + done(); + }); + }); + + it('has a chevron-down icon', done => { + wrapper.vm.$nextTick(() => { + expect(findIcon().props('name')).toBe('chevron-down'); + done(); + }); + }); + + it('has a collapse text', done => { + wrapper.vm.$nextTick(() => { + expect(findHeaderWrapper().text()).toBe('Collapse'); + done(); + }); + }); + }); +}); diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js new file mode 100644 index 00000000000..b356ea85cad --- /dev/null +++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js @@ -0,0 +1,103 @@ +import getStateKey from '~/vue_merge_request_widget/stores/get_state_key'; + +describe('getStateKey', () => { + it('should return proper state name', () => { + const context = { + mergeStatus: 'checked', + mergeWhenPipelineSucceeds: false, + canMerge: true, + onlyAllowMergeIfPipelineSucceeds: false, + isPipelineFailed: false, + hasMergeableDiscussionsState: false, + isPipelineBlocked: false, + canBeMerged: false, + }; + const data = { + project_archived: false, + branch_missing: false, + commits_count: 2, + has_conflicts: false, + work_in_progress: false, + }; + const bound = getStateKey.bind(context, data); + + expect(bound()).toEqual(null); + + context.canBeMerged = true; + + expect(bound()).toEqual('readyToMerge'); + + context.canMerge = false; + + expect(bound()).toEqual('notAllowedToMerge'); + + context.mergeWhenPipelineSucceeds = true; + + expect(bound()).toEqual('mergeWhenPipelineSucceeds'); + + context.isSHAMismatch = true; + + expect(bound()).toEqual('shaMismatch'); + + context.isPipelineBlocked = true; + + expect(bound()).toEqual('pipelineBlocked'); + + context.hasMergeableDiscussionsState = true; + + expect(bound()).toEqual('unresolvedDiscussions'); + + context.onlyAllowMergeIfPipelineSucceeds = true; + context.isPipelineFailed = true; + + expect(bound()).toEqual('pipelineFailed'); + + data.work_in_progress = true; + + expect(bound()).toEqual('workInProgress'); + + data.has_conflicts = true; + + expect(bound()).toEqual('conflicts'); + + context.mergeStatus = 'unchecked'; + + expect(bound()).toEqual('checking'); + + data.commits_count = 0; + + expect(bound()).toEqual('nothingToMerge'); + + data.branch_missing = true; + + expect(bound()).toEqual('missingBranch'); + + data.project_archived = true; + + expect(bound()).toEqual('archived'); + }); + + it('returns rebased state key', () => { + const context = { + mergeStatus: 'checked', + mergeWhenPipelineSucceeds: false, + canMerge: true, + onlyAllowMergeIfPipelineSucceeds: true, + isPipelineFailed: true, + hasMergeableDiscussionsState: false, + isPipelineBlocked: false, + canBeMerged: false, + shouldBeRebased: true, + }; + const data = { + project_archived: false, + branch_missing: false, + commits_count: 2, + has_conflicts: false, + work_in_progress: false, + }; + const bound = getStateKey.bind(context, data); + + expect(bound()).toEqual('rebase'); + }); +}); diff --git a/spec/frontend/vue_shared/components/callout_spec.js b/spec/frontend/vue_shared/components/callout_spec.js new file mode 100644 index 00000000000..91208dfb31a --- /dev/null +++ b/spec/frontend/vue_shared/components/callout_spec.js @@ -0,0 +1,66 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Callout from '~/vue_shared/components/callout.vue'; + +const TEST_MESSAGE = 'This is a callout message!'; +const TEST_SLOT = ''; + +const localVue = createLocalVue(); + +describe('Callout Component', () => { + let wrapper; + + const factory = options => { + wrapper = shallowMount(localVue.extend(Callout), { + localVue, + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('should render the appropriate variant of callout', () => { + factory({ + propsData: { + category: 'info', + message: TEST_MESSAGE, + }, + }); + + expect(wrapper.classes()).toEqual(['bs-callout', 'bs-callout-info']); + + expect(wrapper.element.tagName).toEqual('DIV'); + }); + + it('should render accessibility attributes', () => { + factory({ + propsData: { + message: TEST_MESSAGE, + }, + }); + + expect(wrapper.attributes('role')).toEqual('alert'); + expect(wrapper.attributes('aria-live')).toEqual('assertive'); + }); + + it('should render the provided message', () => { + factory({ + propsData: { + message: TEST_MESSAGE, + }, + }); + + expect(wrapper.element.innerHTML.trim()).toEqual(TEST_MESSAGE); + }); + + it('should render the provided slot', () => { + factory({ + slots: { + default: TEST_SLOT, + }, + }); + + expect(wrapper.element.innerHTML.trim()).toEqual(TEST_SLOT); + }); +}); diff --git a/spec/frontend/vue_shared/components/code_block_spec.js b/spec/frontend/vue_shared/components/code_block_spec.js new file mode 100644 index 00000000000..6b91a20ff76 --- /dev/null +++ b/spec/frontend/vue_shared/components/code_block_spec.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import component from '~/vue_shared/components/code_block.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Code Block', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + it('renders a code block with the provided code', () => { + const code = + "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in \u003ctop (required)\u003e'"; + + vm = mountComponent(Component, { + code, + }); + + expect(vm.$el.querySelector('code').textContent).toEqual(code); + }); + + it('escapes XSS injections', () => { + const code = 'CCC<img src=x onerror=alert(document.domain)>'; + + vm = mountComponent(Component, { + code, + }); + + expect(vm.$el.querySelector('code').textContent).toEqual(code); + }); +}); diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js new file mode 100644 index 00000000000..c4358f0d9cb --- /dev/null +++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js @@ -0,0 +1,23 @@ +import { shallowMount } from '@vue/test-utils'; +import ModeChanged from '~/vue_shared/components/diff_viewer/viewers/mode_changed.vue'; + +describe('Diff viewer mode changed component', () => { + let vm; + + beforeEach(() => { + vm = shallowMount(ModeChanged, { + propsData: { + aMode: '123', + bMode: '321', + }, + }); + }); + + afterEach(() => { + vm.destroy(); + }); + + it('renders aMode & bMode', () => { + expect(vm.text()).toContain('File mode changed from 123 to 321'); + }); +}); diff --git a/spec/frontend/vue_shared/components/identicon_spec.js b/spec/frontend/vue_shared/components/identicon_spec.js new file mode 100644 index 00000000000..0b3dbb61c96 --- /dev/null +++ b/spec/frontend/vue_shared/components/identicon_spec.js @@ -0,0 +1,65 @@ +import Vue from 'vue'; +import identiconComponent from '~/vue_shared/components/identicon.vue'; + +const createComponent = sizeClass => { + const Component = Vue.extend(identiconComponent); + + return new Component({ + propsData: { + entityId: 1, + entityName: 'entity-name', + sizeClass, + }, + }).$mount(); +}; + +describe('IdenticonComponent', () => { + describe('computed', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('identiconBackgroundClass', () => { + it('should return bg class based on entityId', () => { + vm.entityId = 4; + + expect(vm.identiconBackgroundClass).toBeDefined(); + expect(vm.identiconBackgroundClass).toBe('bg5'); + }); + }); + + describe('identiconTitle', () => { + it('should return first letter of entity title in uppercase', () => { + vm.entityName = 'dummy-group'; + + expect(vm.identiconTitle).toBeDefined(); + expect(vm.identiconTitle).toBe('D'); + }); + }); + }); + + describe('template', () => { + it('should render identicon', () => { + const vm = createComponent(); + + expect(vm.$el.nodeName).toBe('DIV'); + expect(vm.$el.classList.contains('identicon')).toBeTruthy(); + expect(vm.$el.classList.contains('s40')).toBeTruthy(); + expect(vm.$el.classList.contains('bg2')).toBeTruthy(); + vm.$destroy(); + }); + + it('should render identicon with provided sizing class', () => { + const vm = createComponent('s32'); + + expect(vm.$el.classList.contains('s32')).toBeTruthy(); + vm.$destroy(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js b/spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js new file mode 100644 index 00000000000..2388660b0c2 --- /dev/null +++ b/spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js @@ -0,0 +1,13 @@ +import * as domUtils from '~/vue_shared/components/lib/utils/dom_utils'; + +describe('domUtils', () => { + describe('pixeliseValue', () => { + it('should add px to a given Number', () => { + expect(domUtils.pixeliseValue(12)).toEqual('12px'); + }); + + it('should not add px to 0', () => { + expect(domUtils.pixeliseValue(0)).toEqual(''); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js new file mode 100644 index 00000000000..d0cb3731050 --- /dev/null +++ b/spec/frontend/vue_shared/components/pagination_links_spec.js @@ -0,0 +1,59 @@ +import Vue from 'vue'; +import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; +import { s__ } from '~/locale'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Pagination links component', () => { + const paginationLinksComponent = Vue.extend(PaginationLinks); + const change = page => page; + const pageInfo = { + page: 3, + perPage: 5, + total: 30, + }; + const translations = { + firstText: s__('Pagination|« First'), + prevText: s__('Pagination|Prev'), + nextText: s__('Pagination|Next'), + lastText: s__('Pagination|Last »'), + }; + + let paginationLinks; + let glPagination; + let destinationComponent; + + beforeEach(() => { + paginationLinks = mountComponent(paginationLinksComponent, { + change, + pageInfo, + }); + [glPagination] = paginationLinks.$children; + [destinationComponent] = glPagination.$children; + }); + + afterEach(() => { + paginationLinks.$destroy(); + }); + + it('should provide translated text to GitLab UI pagination', () => { + Object.entries(translations).forEach(entry => { + expect(destinationComponent[entry[0]]).toBe(entry[1]); + }); + }); + + it('should pass change to GitLab UI pagination', () => { + expect(Object.is(glPagination.change, change)).toBe(true); + }); + + it('should pass page from pageInfo to GitLab UI pagination', () => { + expect(destinationComponent.value).toBe(pageInfo.page); + }); + + it('should pass per page from pageInfo to GitLab UI pagination', () => { + expect(destinationComponent.perPage).toBe(pageInfo.perPage); + }); + + it('should pass total items from pageInfo to GitLab UI pagination', () => { + expect(destinationComponent.totalRows).toBe(pageInfo.total); + }); +}); diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js new file mode 100644 index 00000000000..536bb57b946 --- /dev/null +++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js @@ -0,0 +1,44 @@ +import Vue from 'vue'; +import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; + +describe('Time ago with tooltip component', () => { + let TimeagoTooltip; + let vm; + + beforeEach(() => { + TimeagoTooltip = Vue.extend(timeagoTooltip); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render timeago with a bootstrap tooltip', () => { + vm = new TimeagoTooltip({ + propsData: { + time: '2017-05-08T14:57:39.781Z', + }, + }).$mount(); + + expect(vm.$el.tagName).toEqual('TIME'); + expect(vm.$el.getAttribute('data-original-title')).toEqual( + formatDate('2017-05-08T14:57:39.781Z'), + ); + + const timeago = getTimeago(); + + expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z')); + }); + + it('should render provided html class', () => { + vm = new TimeagoTooltip({ + propsData: { + time: '2017-05-08T14:57:39.781Z', + cssClass: 'foo', + }, + }).$mount(); + + expect(vm.$el.classList.contains('foo')).toEqual(true); + }); +}); diff --git a/spec/frontend/vuex_shared/modules/modal/mutations_spec.js b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js new file mode 100644 index 00000000000..d07f8ba1e65 --- /dev/null +++ b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js @@ -0,0 +1,49 @@ +import mutations from '~/vuex_shared/modules/modal/mutations'; +import * as types from '~/vuex_shared/modules/modal/mutation_types'; + +describe('Vuex ModalModule mutations', () => { + describe(types.SHOW, () => { + it('sets isVisible to true', () => { + const state = { + isVisible: false, + }; + + mutations[types.SHOW](state); + + expect(state).toEqual({ + isVisible: true, + }); + }); + }); + + describe(types.HIDE, () => { + it('sets isVisible to false', () => { + const state = { + isVisible: true, + }; + + mutations[types.HIDE](state); + + expect(state).toEqual({ + isVisible: false, + }); + }); + }); + + describe(types.OPEN, () => { + it('sets data and sets isVisible to true', () => { + const data = { id: 7 }; + const state = { + isVisible: false, + data: null, + }; + + mutations[types.OPEN](state, data); + + expect(state).toEqual({ + isVisible: true, + data, + }); + }); + }); +}); diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/javascripts/behaviors/secret_values_spec.js deleted file mode 100644 index 5aaab093c0c..00000000000 --- a/spec/javascripts/behaviors/secret_values_spec.js +++ /dev/null @@ -1,230 +0,0 @@ -import SecretValues from '~/behaviors/secret_values'; - -function generateValueMarkup( - secret, - valueClass = 'js-secret-value', - placeholderClass = 'js-secret-value-placeholder', -) { - return ` -
    - *** -
    - - `; -} - -function generateFixtureMarkup(secrets, isRevealed, valueClass, placeholderClass) { - return ` -
    - ${secrets.map(secret => generateValueMarkup(secret, valueClass, placeholderClass)).join('')} - -
    - `; -} - -function setupSecretFixture( - secrets, - isRevealed, - valueClass = 'js-secret-value', - placeholderClass = 'js-secret-value-placeholder', -) { - const wrapper = document.createElement('div'); - wrapper.innerHTML = generateFixtureMarkup(secrets, isRevealed, valueClass, placeholderClass); - - const secretValues = new SecretValues({ - container: wrapper.querySelector('.js-secret-container'), - valueSelector: `.${valueClass}`, - placeholderSelector: `.${placeholderClass}`, - }); - secretValues.init(); - - return wrapper; -} - -describe('setupSecretValues', () => { - describe('with a single secret', () => { - const secrets = ['mysecret123']; - - it('should have correct "Reveal" label when values are hidden', () => { - const wrapper = setupSecretFixture(secrets, false); - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - - expect(revealButton.textContent).toEqual('Reveal value'); - }); - - it('should have correct "Hide" label when values are shown', () => { - const wrapper = setupSecretFixture(secrets, true); - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - - expect(revealButton.textContent).toEqual('Hide value'); - }); - - it('should have value hidden initially', () => { - const wrapper = setupSecretFixture(secrets, false); - const values = wrapper.querySelectorAll('.js-secret-value'); - const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); - - expect(values.length).toEqual(1); - expect(values[0].classList.contains('hide')).toEqual(true); - expect(placeholders.length).toEqual(1); - expect(placeholders[0].classList.contains('hide')).toEqual(false); - }); - - it('should toggle value and placeholder', () => { - const wrapper = setupSecretFixture(secrets, false); - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - const values = wrapper.querySelectorAll('.js-secret-value'); - const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); - - revealButton.click(); - - expect(values.length).toEqual(1); - expect(values[0].classList.contains('hide')).toEqual(false); - expect(placeholders.length).toEqual(1); - expect(placeholders[0].classList.contains('hide')).toEqual(true); - - revealButton.click(); - - expect(values.length).toEqual(1); - expect(values[0].classList.contains('hide')).toEqual(true); - expect(placeholders.length).toEqual(1); - expect(placeholders[0].classList.contains('hide')).toEqual(false); - }); - }); - - describe('with a multiple secrets', () => { - const secrets = ['mysecret123', 'happygoat456', 'tanuki789']; - - it('should have correct "Reveal" label when values are hidden', () => { - const wrapper = setupSecretFixture(secrets, false); - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - - expect(revealButton.textContent).toEqual('Reveal values'); - }); - - it('should have correct "Hide" label when values are shown', () => { - const wrapper = setupSecretFixture(secrets, true); - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - - expect(revealButton.textContent).toEqual('Hide values'); - }); - - it('should have all values hidden initially', () => { - const wrapper = setupSecretFixture(secrets, false); - const values = wrapper.querySelectorAll('.js-secret-value'); - const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); - - expect(values.length).toEqual(3); - values.forEach(value => { - expect(value.classList.contains('hide')).toEqual(true); - }); - - expect(placeholders.length).toEqual(3); - placeholders.forEach(placeholder => { - expect(placeholder.classList.contains('hide')).toEqual(false); - }); - }); - - it('should toggle values and placeholders', () => { - const wrapper = setupSecretFixture(secrets, false); - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - const values = wrapper.querySelectorAll('.js-secret-value'); - const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); - - revealButton.click(); - - expect(values.length).toEqual(3); - values.forEach(value => { - expect(value.classList.contains('hide')).toEqual(false); - }); - - expect(placeholders.length).toEqual(3); - placeholders.forEach(placeholder => { - expect(placeholder.classList.contains('hide')).toEqual(true); - }); - - revealButton.click(); - - expect(values.length).toEqual(3); - values.forEach(value => { - expect(value.classList.contains('hide')).toEqual(true); - }); - - expect(placeholders.length).toEqual(3); - placeholders.forEach(placeholder => { - expect(placeholder.classList.contains('hide')).toEqual(false); - }); - }); - }); - - describe('with dynamic secrets', () => { - const secrets = ['mysecret123', 'happygoat456', 'tanuki789']; - - it('should toggle values and placeholders', () => { - const wrapper = setupSecretFixture(secrets, false); - // Insert the new dynamic row - wrapper - .querySelector('.js-secret-container') - .insertAdjacentHTML('afterbegin', generateValueMarkup('foobarbazdynamic')); - - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - const values = wrapper.querySelectorAll('.js-secret-value'); - const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); - - revealButton.click(); - - expect(values.length).toEqual(4); - values.forEach(value => { - expect(value.classList.contains('hide')).toEqual(false); - }); - - expect(placeholders.length).toEqual(4); - placeholders.forEach(placeholder => { - expect(placeholder.classList.contains('hide')).toEqual(true); - }); - - revealButton.click(); - - expect(values.length).toEqual(4); - values.forEach(value => { - expect(value.classList.contains('hide')).toEqual(true); - }); - - expect(placeholders.length).toEqual(4); - placeholders.forEach(placeholder => { - expect(placeholder.classList.contains('hide')).toEqual(false); - }); - }); - }); - - describe('selector options', () => { - const secrets = ['mysecret123']; - - it('should respect `valueSelector` and `placeholderSelector` options', () => { - const valueClass = 'js-some-custom-placeholder-selector'; - const placeholderClass = 'js-some-custom-value-selector'; - - const wrapper = setupSecretFixture(secrets, false, valueClass, placeholderClass); - const values = wrapper.querySelectorAll(`.${valueClass}`); - const placeholders = wrapper.querySelectorAll(`.${placeholderClass}`); - const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); - - expect(values.length).toEqual(1); - expect(placeholders.length).toEqual(1); - - revealButton.click(); - - expect(values.length).toEqual(1); - expect(values[0].classList.contains('hide')).toEqual(false); - expect(placeholders.length).toEqual(1); - expect(placeholders[0].classList.contains('hide')).toEqual(true); - }); - }); -}); diff --git a/spec/javascripts/blob/blob_fork_suggestion_spec.js b/spec/javascripts/blob/blob_fork_suggestion_spec.js deleted file mode 100644 index 9b81b7e6f92..00000000000 --- a/spec/javascripts/blob/blob_fork_suggestion_spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import BlobForkSuggestion from '~/blob/blob_fork_suggestion'; - -describe('BlobForkSuggestion', () => { - let blobForkSuggestion; - - const openButton = document.createElement('div'); - const forkButton = document.createElement('a'); - const cancelButton = document.createElement('div'); - const suggestionSection = document.createElement('div'); - const actionTextPiece = document.createElement('div'); - - beforeEach(() => { - blobForkSuggestion = new BlobForkSuggestion({ - openButtons: openButton, - forkButtons: forkButton, - cancelButtons: cancelButton, - suggestionSections: suggestionSection, - actionTextPieces: actionTextPiece, - }).init(); - }); - - afterEach(() => { - blobForkSuggestion.destroy(); - }); - - it('showSuggestionSection', () => { - blobForkSuggestion.showSuggestionSection('/foo', 'foo'); - - expect(suggestionSection.classList.contains('hidden')).toEqual(false); - expect(forkButton.getAttribute('href')).toEqual('/foo'); - expect(actionTextPiece.textContent).toEqual('foo'); - }); - - it('hideSuggestionSection', () => { - blobForkSuggestion.hideSuggestionSection(); - - expect(suggestionSection.classList.contains('hidden')).toEqual(true); - }); -}); diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js deleted file mode 100644 index 3257a3fb8a3..00000000000 --- a/spec/javascripts/boards/modal_store_spec.js +++ /dev/null @@ -1,134 +0,0 @@ -/* global ListIssue */ - -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; -import '~/boards/models/issue'; -import '~/boards/models/list'; -import Store from '~/boards/stores/modal_store'; - -describe('Modal store', () => { - let issue; - let issue2; - - beforeEach(() => { - // Set up default state - Store.store.issues = []; - Store.store.selectedIssues = []; - - issue = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [], - assignees: [], - }); - issue2 = new ListIssue({ - title: 'Testing', - id: 1, - iid: 2, - confidential: false, - labels: [], - assignees: [], - }); - Store.store.issues.push(issue); - Store.store.issues.push(issue2); - }); - - it('returns selected count', () => { - expect(Store.selectedCount()).toBe(0); - }); - - it('toggles the issue as selected', () => { - Store.toggleIssue(issue); - - expect(issue.selected).toBe(true); - expect(Store.selectedCount()).toBe(1); - }); - - it('toggles the issue as un-selected', () => { - Store.toggleIssue(issue); - Store.toggleIssue(issue); - - expect(issue.selected).toBe(false); - expect(Store.selectedCount()).toBe(0); - }); - - it('toggles all issues as selected', () => { - Store.toggleAll(); - - expect(issue.selected).toBe(true); - expect(issue2.selected).toBe(true); - expect(Store.selectedCount()).toBe(2); - }); - - it('toggles all issues as un-selected', () => { - Store.toggleAll(); - Store.toggleAll(); - - expect(issue.selected).toBe(false); - expect(issue2.selected).toBe(false); - expect(Store.selectedCount()).toBe(0); - }); - - it('toggles all if a single issue is selected', () => { - Store.toggleIssue(issue); - Store.toggleAll(); - - expect(issue.selected).toBe(true); - expect(issue2.selected).toBe(true); - expect(Store.selectedCount()).toBe(2); - }); - - it('adds issue to selected array', () => { - issue.selected = true; - Store.addSelectedIssue(issue); - - expect(Store.selectedCount()).toBe(1); - }); - - it('removes issue from selected array', () => { - Store.addSelectedIssue(issue); - Store.removeSelectedIssue(issue); - - expect(Store.selectedCount()).toBe(0); - }); - - it('returns selected issue index if present', () => { - Store.toggleIssue(issue); - - expect(Store.selectedIssueIndex(issue)).toBe(0); - }); - - it('returns -1 if issue is not selected', () => { - expect(Store.selectedIssueIndex(issue)).toBe(-1); - }); - - it('finds the selected issue', () => { - Store.toggleIssue(issue); - - expect(Store.findSelectedIssue(issue)).toBe(issue); - }); - - it('does not find a selected issue', () => { - expect(Store.findSelectedIssue(issue)).toBe(undefined); - }); - - it('does not remove from selected issue if tab is not all', () => { - Store.store.activeTab = 'selected'; - - Store.toggleIssue(issue); - Store.toggleIssue(issue); - - expect(Store.store.selectedIssues.length).toBe(1); - expect(Store.selectedCount()).toBe(0); - }); - - it('gets selected issue array with only selected issues', () => { - Store.toggleIssue(issue); - Store.toggleIssue(issue2); - Store.toggleIssue(issue2); - - expect(Store.getSelectedIssues().length).toBe(1); - }); -}); diff --git a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js deleted file mode 100644 index 13e9fe00a00..00000000000 --- a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import Vue from 'vue'; -import Translate from '~/vue_shared/translate'; -import limitWarningComp from '~/cycle_analytics/components/limit_warning_component.vue'; - -Vue.use(Translate); - -describe('Limit warning component', () => { - let component; - let LimitWarningComponent; - - beforeEach(() => { - LimitWarningComponent = Vue.extend(limitWarningComp); - }); - - it('should not render if count is not exactly than 50', () => { - component = new LimitWarningComponent({ - propsData: { - count: 5, - }, - }).$mount(); - - expect(component.$el.textContent.trim()).toBe(''); - - component = new LimitWarningComponent({ - propsData: { - count: 55, - }, - }).$mount(); - - expect(component.$el.textContent.trim()).toBe(''); - }); - - it('should render if count is exactly 50', () => { - component = new LimitWarningComponent({ - propsData: { - count: 50, - }, - }).$mount(); - - expect(component.$el.textContent.trim()).toBe('Showing 50 events'); - }); -}); diff --git a/spec/javascripts/diffs/components/diff_stats_spec.js b/spec/javascripts/diffs/components/diff_stats_spec.js deleted file mode 100644 index 984b3026209..00000000000 --- a/spec/javascripts/diffs/components/diff_stats_spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import DiffStats from '~/diffs/components/diff_stats.vue'; - -describe('diff_stats', () => { - it('does not render a group if diffFileLengths is not passed in', () => { - const wrapper = shallowMount(DiffStats, { - propsData: { - addedLines: 1, - removedLines: 2, - }, - }); - const groups = wrapper.findAll('.diff-stats-group'); - - expect(groups.length).toBe(2); - }); - - it('shows amount of files changed, lines added and lines removed when passed all props', () => { - const wrapper = shallowMount(DiffStats, { - propsData: { - addedLines: 100, - removedLines: 200, - diffFilesLength: 300, - }, - }); - const additions = wrapper.find('icon-stub[name="file-addition"]').element.parentNode; - const deletions = wrapper.find('icon-stub[name="file-deletion"]').element.parentNode; - const filesChanged = wrapper.find('icon-stub[name="doc-code"]').element.parentNode; - - expect(additions.textContent).toContain('100'); - expect(deletions.textContent).toContain('200'); - expect(filesChanged.textContent).toContain('300'); - }); -}); diff --git a/spec/javascripts/diffs/components/edit_button_spec.js b/spec/javascripts/diffs/components/edit_button_spec.js deleted file mode 100644 index ccdae4cb312..00000000000 --- a/spec/javascripts/diffs/components/edit_button_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import EditButton from '~/diffs/components/edit_button.vue'; - -const localVue = createLocalVue(); -const editPath = 'test-path'; - -describe('EditButton', () => { - let wrapper; - - const createComponent = (props = {}) => { - wrapper = shallowMount(EditButton, { - localVue, - sync: false, - propsData: { ...props }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('has correct href attribute', () => { - createComponent({ - editPath, - canCurrentUserFork: false, - }); - - expect(wrapper.attributes('href')).toBe(editPath); - }); - - it('emits a show fork message event if current user can fork', () => { - createComponent({ - editPath, - canCurrentUserFork: true, - }); - wrapper.trigger('click'); - - expect(wrapper.emitted('showForkMessage')).toBeTruthy(); - }); - - it('doesnt emit a show fork message event if current user cannot fork', () => { - createComponent({ - editPath, - canCurrentUserFork: false, - }); - wrapper.trigger('click'); - - expect(wrapper.emitted('showForkMessage')).toBeFalsy(); - }); - - it('doesnt emit a show fork message event if current user can modify blob', () => { - createComponent({ - editPath, - canCurrentUserFork: true, - canModifyBlob: true, - }); - wrapper.trigger('click'); - - expect(wrapper.emitted('showForkMessage')).toBeFalsy(); - }); -}); diff --git a/spec/javascripts/diffs/components/hidden_files_warning_spec.js b/spec/javascripts/diffs/components/hidden_files_warning_spec.js deleted file mode 100644 index 5bf5ddd27bd..00000000000 --- a/spec/javascripts/diffs/components/hidden_files_warning_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue'; - -const localVue = createLocalVue(); -const propsData = { - total: '10', - visible: 5, - plainDiffPath: 'plain-diff-path', - emailPatchPath: 'email-patch-path', -}; - -describe('HiddenFilesWarning', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(HiddenFilesWarning, { - localVue, - sync: false, - propsData, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('has a correct plain diff URL', () => { - const plainDiffLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Plain diff')[0]; - - expect(plainDiffLink.attributes('href')).toBe(propsData.plainDiffPath); - }); - - it('has a correct email patch URL', () => { - const emailPatchLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Email patch')[0]; - - expect(emailPatchLink.attributes('href')).toBe(propsData.emailPatchPath); - }); - - it('has a correct visible/total files text', () => { - const filesText = wrapper.find('strong'); - - expect(filesText.text()).toBe('5 of 10'); - }); -}); diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/javascripts/diffs/components/no_changes_spec.js deleted file mode 100644 index e45d34bf9d5..00000000000 --- a/spec/javascripts/diffs/components/no_changes_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { createStore } from '~/mr_notes/stores'; -import NoChanges from '~/diffs/components/no_changes.vue'; - -describe('Diff no changes empty state', () => { - let vm; - - function createComponent(extendStore = () => {}) { - const localVue = createLocalVue(); - localVue.use(Vuex); - - const store = createStore(); - extendStore(store); - - vm = shallowMount(localVue.extend(NoChanges), { - localVue, - store, - propsData: { - changesEmptyStateIllustration: '', - }, - }); - } - - afterEach(() => { - vm.destroy(); - }); - - it('prevents XSS', () => { - createComponent(store => { - // eslint-disable-next-line no-param-reassign - store.state.notes.noteableData = { - source_branch: '', - target_branch: '', - }; - }); - - expect(vm.contains('script')).toBe(false); - }); -}); diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js deleted file mode 100644 index 503af3920a8..00000000000 --- a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js +++ /dev/null @@ -1,118 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import Vuex from 'vuex'; -import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; -import { GlButton, GlEmptyState, GlLoadingIcon, GlTable, GlLink } from '@gitlab/ui'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('ErrorTrackingList', () => { - let store; - let wrapper; - let actions; - - function mountComponent({ errorTrackingEnabled = true } = {}) { - wrapper = shallowMount(ErrorTrackingList, { - localVue, - store, - propsData: { - indexPath: '/path', - enableErrorTrackingLink: '/link', - errorTrackingEnabled, - illustrationPath: 'illustration/path', - }, - stubs: { - 'gl-link': GlLink, - }, - }); - } - - beforeEach(() => { - actions = { - getErrorList: () => {}, - startPolling: () => {}, - restartPolling: jasmine.createSpy('restartPolling'), - }; - - const state = { - errors: [], - loading: true, - }; - - store = new Vuex.Store({ - actions, - state, - }); - }); - - afterEach(() => { - if (wrapper) { - wrapper.destroy(); - } - }); - - describe('loading', () => { - beforeEach(() => { - mountComponent(); - }); - - it('shows spinner', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy(); - expect(wrapper.find(GlTable).exists()).toBeFalsy(); - expect(wrapper.find(GlButton).exists()).toBeFalsy(); - }); - }); - - describe('results', () => { - beforeEach(() => { - store.state.loading = false; - - mountComponent(); - }); - - it('shows table', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); - expect(wrapper.find(GlTable).exists()).toBeTruthy(); - expect(wrapper.find(GlButton).exists()).toBeTruthy(); - }); - }); - - describe('no results', () => { - beforeEach(() => { - store.state.loading = false; - - mountComponent(); - }); - - it('shows empty table', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); - expect(wrapper.find(GlTable).exists()).toBeTruthy(); - expect(wrapper.find(GlButton).exists()).toBeTruthy(); - }); - - it('shows a message prompting to refresh', () => { - const refreshLink = wrapper.vm.$refs.empty.querySelector('a'); - - expect(refreshLink.textContent.trim()).toContain('Check again'); - }); - - it('restarts polling', () => { - wrapper.find('.js-try-again').trigger('click'); - - expect(actions.restartPolling).toHaveBeenCalled(); - }); - }); - - describe('error tracking feature disabled', () => { - beforeEach(() => { - mountComponent({ errorTrackingEnabled: false }); - }); - - it('shows empty state', () => { - expect(wrapper.find(GlEmptyState).exists()).toBeTruthy(); - expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy(); - expect(wrapper.find(GlTable).exists()).toBeFalsy(); - expect(wrapper.find(GlButton).exists()).toBeFalsy(); - }); - }); -}); diff --git a/spec/javascripts/error_tracking/store/mutation_spec.js b/spec/javascripts/error_tracking/store/mutation_spec.js deleted file mode 100644 index 8117104bdbc..00000000000 --- a/spec/javascripts/error_tracking/store/mutation_spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import mutations from '~/error_tracking/store/mutations'; -import * as types from '~/error_tracking/store/mutation_types'; - -describe('Error tracking mutations', () => { - describe('SET_ERRORS', () => { - let state; - - beforeEach(() => { - state = { errors: [] }; - }); - - it('camelizes response', () => { - const errors = [ - { - title: 'the title', - external_url: 'localhost:3456', - count: 100, - userCount: 10, - }, - ]; - - mutations[types.SET_ERRORS](state, errors); - - expect(state).toEqual({ - errors: [ - { - title: 'the title', - externalUrl: 'localhost:3456', - count: 100, - userCount: 10, - }, - ], - }); - }); - }); -}); diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js deleted file mode 100644 index d1fea18dea8..00000000000 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js +++ /dev/null @@ -1,141 +0,0 @@ -import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys'; - -describe('Filtered Search Token Keys', () => { - const tokenKeys = [ - { - key: 'author', - type: 'string', - param: 'username', - symbol: '@', - icon: 'pencil', - tag: '@author', - }, - ]; - - const conditions = [ - { - url: 'assignee_id=0', - tokenKey: 'assignee', - value: 'none', - }, - ]; - - describe('get', () => { - it('should return tokenKeys', () => { - expect(new FilteredSearchTokenKeys().get()).not.toBeNull(); - }); - - it('should return tokenKeys as an array', () => { - expect(new FilteredSearchTokenKeys().get() instanceof Array).toBe(true); - }); - }); - - describe('getKeys', () => { - it('should return keys', () => { - const getKeys = new FilteredSearchTokenKeys(tokenKeys).getKeys(); - const keys = new FilteredSearchTokenKeys(tokenKeys).get().map(i => i.key); - - keys.forEach((key, i) => { - expect(key).toEqual(getKeys[i]); - }); - }); - }); - - describe('getConditions', () => { - it('should return conditions', () => { - expect(new FilteredSearchTokenKeys().getConditions()).not.toBeNull(); - }); - - it('should return conditions as an array', () => { - expect(new FilteredSearchTokenKeys().getConditions() instanceof Array).toBe(true); - }); - }); - - describe('searchByKey', () => { - it('should return null when key not found', () => { - const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKey('notakey'); - - expect(tokenKey).toBeNull(); - }); - - it('should return tokenKey when found by key', () => { - const result = new FilteredSearchTokenKeys(tokenKeys).searchByKey(tokenKeys[0].key); - - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchBySymbol', () => { - it('should return null when symbol not found', () => { - const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchBySymbol('notasymbol'); - - expect(tokenKey).toBeNull(); - }); - - it('should return tokenKey when found by symbol', () => { - const result = new FilteredSearchTokenKeys(tokenKeys).searchBySymbol(tokenKeys[0].symbol); - - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchByKeyParam', () => { - it('should return null when key param not found', () => { - const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam('notakeyparam'); - - expect(tokenKey).toBeNull(); - }); - - it('should return tokenKey when found by key param', () => { - const result = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam( - `${tokenKeys[0].key}_${tokenKeys[0].param}`, - ); - - expect(result).toEqual(tokenKeys[0]); - }); - - it('should return alternative tokenKey when found by key param', () => { - const result = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam( - `${tokenKeys[0].key}_${tokenKeys[0].param}`, - ); - - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchByConditionUrl', () => { - it('should return null when condition url not found', () => { - const condition = new FilteredSearchTokenKeys([], [], conditions).searchByConditionUrl(null); - - expect(condition).toBeNull(); - }); - - it('should return condition when found by url', () => { - const result = new FilteredSearchTokenKeys([], [], conditions).searchByConditionUrl( - conditions[0].url, - ); - - expect(result).toBe(conditions[0]); - }); - }); - - describe('searchByConditionKeyValue', () => { - it('should return null when condition tokenKey and value not found', () => { - const condition = new FilteredSearchTokenKeys([], [], conditions).searchByConditionKeyValue( - null, - null, - ); - - expect(condition).toBeNull(); - }); - - it('should return condition when found by tokenKey and value', () => { - const result = new FilteredSearchTokenKeys([], [], conditions).searchByConditionKeyValue( - conditions[0].tokenKey, - conditions[0].value, - ); - - expect(result).toEqual(conditions[0]); - }); - }); -}); diff --git a/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js b/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js deleted file mode 100644 index ea7c146fa4f..00000000000 --- a/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js +++ /dev/null @@ -1,18 +0,0 @@ -import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error'; - -describe('RecentSearchesServiceError', () => { - let recentSearchesServiceError; - - beforeEach(() => { - recentSearchesServiceError = new RecentSearchesServiceError(); - }); - - it('instantiates an instance of RecentSearchesServiceError and not an Error', () => { - expect(recentSearchesServiceError).toEqual(jasmine.any(RecentSearchesServiceError)); - expect(recentSearchesServiceError.name).toBe('RecentSearchesServiceError'); - }); - - it('should set a default message', () => { - expect(recentSearchesServiceError.message).toBe('Recent Searches Service is unavailable'); - }); -}); diff --git a/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js b/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js deleted file mode 100644 index 56bb82ae941..00000000000 --- a/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js +++ /dev/null @@ -1,53 +0,0 @@ -import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store'; - -describe('RecentSearchesStore', () => { - let store; - - beforeEach(() => { - store = new RecentSearchesStore(); - }); - - describe('addRecentSearch', () => { - it('should add to the front of the list', () => { - store.addRecentSearch('foo'); - store.addRecentSearch('bar'); - - expect(store.state.recentSearches).toEqual(['bar', 'foo']); - }); - - it('should deduplicate', () => { - store.addRecentSearch('foo'); - store.addRecentSearch('bar'); - store.addRecentSearch('foo'); - - expect(store.state.recentSearches).toEqual(['foo', 'bar']); - }); - - it('only keeps track of 5 items', () => { - store.addRecentSearch('1'); - store.addRecentSearch('2'); - store.addRecentSearch('3'); - store.addRecentSearch('4'); - store.addRecentSearch('5'); - store.addRecentSearch('6'); - store.addRecentSearch('7'); - - expect(store.state.recentSearches).toEqual(['7', '6', '5', '4', '3']); - }); - }); - - describe('setRecentSearches', () => { - it('should override list', () => { - store.setRecentSearches(['foo', 'bar']); - store.setRecentSearches(['baz', 'qux']); - - expect(store.state.recentSearches).toEqual(['baz', 'qux']); - }); - - it('only keeps track of 5 items', () => { - store.setRecentSearches(['1', '2', '3', '4', '5', '6', '7']); - - expect(store.state.recentSearches).toEqual(['1', '2', '3', '4', '5']); - }); - }); -}); diff --git a/spec/javascripts/frequent_items/store/getters_spec.js b/spec/javascripts/frequent_items/store/getters_spec.js deleted file mode 100644 index 1cd12eb6832..00000000000 --- a/spec/javascripts/frequent_items/store/getters_spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import state from '~/frequent_items/store/state'; -import * as getters from '~/frequent_items/store/getters'; - -describe('Frequent Items Dropdown Store Getters', () => { - let mockedState; - - beforeEach(() => { - mockedState = state(); - }); - - describe('hasSearchQuery', () => { - it('should return `true` when search query is present', () => { - mockedState.searchQuery = 'test'; - - expect(getters.hasSearchQuery(mockedState)).toBe(true); - }); - - it('should return `false` when search query is empty', () => { - mockedState.searchQuery = ''; - - expect(getters.hasSearchQuery(mockedState)).toBe(false); - }); - }); -}); diff --git a/spec/javascripts/ide/lib/common/disposable_spec.js b/spec/javascripts/ide/lib/common/disposable_spec.js deleted file mode 100644 index af12ca15369..00000000000 --- a/spec/javascripts/ide/lib/common/disposable_spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import Disposable from '~/ide/lib/common/disposable'; - -describe('Multi-file editor library disposable class', () => { - let instance; - let disposableClass; - - beforeEach(() => { - instance = new Disposable(); - - disposableClass = { - dispose: jasmine.createSpy('dispose'), - }; - }); - - afterEach(() => { - instance.dispose(); - }); - - describe('add', () => { - it('adds disposable classes', () => { - instance.add(disposableClass); - - expect(instance.disposers.size).toBe(1); - }); - }); - - describe('dispose', () => { - beforeEach(() => { - instance.add(disposableClass); - }); - - it('calls dispose on all cached disposers', () => { - instance.dispose(); - - expect(disposableClass.dispose).toHaveBeenCalled(); - }); - - it('clears cached disposers', () => { - instance.dispose(); - - expect(instance.disposers.size).toBe(0); - }); - }); -}); diff --git a/spec/javascripts/ide/lib/diff/diff_spec.js b/spec/javascripts/ide/lib/diff/diff_spec.js deleted file mode 100644 index 57f3ac3d365..00000000000 --- a/spec/javascripts/ide/lib/diff/diff_spec.js +++ /dev/null @@ -1,80 +0,0 @@ -import { computeDiff } from '~/ide/lib/diff/diff'; - -describe('Multi-file editor library diff calculator', () => { - describe('computeDiff', () => { - it('returns empty array if no changes', () => { - const diff = computeDiff('123', '123'); - - expect(diff).toEqual([]); - }); - - describe('modified', () => { - it('', () => { - const diff = computeDiff('123', '1234')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeUndefined(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeUndefined(); - expect(diff.lineNumber).toBe(2); - }); - }); - - describe('added', () => { - it('', () => { - const diff = computeDiff('123', '123\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeUndefined(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeUndefined(); - expect(diff.lineNumber).toBe(3); - }); - }); - - describe('removed', () => { - it('', () => { - const diff = computeDiff('123', '')[0]; - - expect(diff.added).toBeUndefined(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeTruthy(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n123')[0]; - - expect(diff.added).toBeUndefined(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeTruthy(); - expect(diff.lineNumber).toBe(2); - }); - }); - - it('includes line number of change', () => { - const diff = computeDiff('123', '')[0]; - - expect(diff.lineNumber).toBe(1); - }); - - it('includes end line number of change', () => { - const diff = computeDiff('123', '')[0]; - - expect(diff.endLineNumber).toBe(1); - }); - }); -}); diff --git a/spec/javascripts/ide/lib/editor_options_spec.js b/spec/javascripts/ide/lib/editor_options_spec.js deleted file mode 100644 index d149a883166..00000000000 --- a/spec/javascripts/ide/lib/editor_options_spec.js +++ /dev/null @@ -1,11 +0,0 @@ -import editorOptions from '~/ide/lib/editor_options'; - -describe('Multi-file editor library editor options', () => { - it('returns an array', () => { - expect(editorOptions).toEqual(jasmine.any(Array)); - }); - - it('contains readOnly option', () => { - expect(editorOptions[0].readOnly).toBeDefined(); - }); -}); diff --git a/spec/javascripts/ide/lib/files_spec.js b/spec/javascripts/ide/lib/files_spec.js deleted file mode 100644 index fe791aa2b74..00000000000 --- a/spec/javascripts/ide/lib/files_spec.js +++ /dev/null @@ -1,77 +0,0 @@ -import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils'; -import { decorateFiles, splitParent } from '~/ide/lib/files'; -import { decorateData } from '~/ide/stores/utils'; - -const TEST_BRANCH_ID = 'lorem-ipsum'; -const TEST_PROJECT_ID = 10; - -const createEntries = paths => { - const createEntry = (acc, { path, type, children }) => { - // Sometimes we need to end the url with a '/' - const createUrl = base => (type === 'tree' ? `${base}/` : base); - - const { name, parent } = splitParent(path); - const parentEntry = acc[parent]; - - acc[path] = { - ...decorateData({ - projectId: TEST_PROJECT_ID, - branchId: TEST_BRANCH_ID, - id: path, - name, - path, - url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${path}`), - type, - previewMode: viewerInformationForPath(path), - parentPath: parent, - parentTreeUrl: parentEntry - ? parentEntry.url - : createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}`), - }), - tree: children.map(childName => jasmine.objectContaining({ name: childName })), - }; - - return acc; - }; - - const entries = paths.reduce(createEntry, {}); - - // Wrap entries in jasmine.objectContaining. - // We couldn't do this earlier because we still need to select properties from parent entries. - return Object.keys(entries).reduce((acc, key) => { - acc[key] = jasmine.objectContaining(entries[key]); - - return acc; - }, {}); -}; - -describe('IDE lib decorate files', () => { - it('creates entries and treeList', () => { - const data = ['app/assets/apples/foo.js', 'app/bugs.js', 'README.md']; - const expectedEntries = createEntries([ - { path: 'app', type: 'tree', children: ['assets', 'bugs.js'] }, - { path: 'app/assets', type: 'tree', children: ['apples'] }, - { path: 'app/assets/apples', type: 'tree', children: ['foo.js'] }, - { path: 'app/assets/apples/foo.js', type: 'blob', children: [] }, - { path: 'app/bugs.js', type: 'blob', children: [] }, - { path: 'README.md', type: 'blob', children: [] }, - ]); - - const { entries, treeList } = decorateFiles({ - data, - branchId: TEST_BRANCH_ID, - projectId: TEST_PROJECT_ID, - }); - - // Here we test the keys and then each key/value individually because `expect(entries).toEqual(expectedEntries)` - // was taking a very long time for some reason. Probably due to large objects and nested `jasmine.objectContaining`. - const entryKeys = Object.keys(entries); - - expect(entryKeys).toEqual(Object.keys(expectedEntries)); - entryKeys.forEach(key => { - expect(entries[key]).toEqual(expectedEntries[key]); - }); - - expect(treeList).toEqual([expectedEntries.app, expectedEntries['README.md']]); - }); -}); diff --git a/spec/javascripts/ide/stores/modules/commit/mutations_spec.js b/spec/javascripts/ide/stores/modules/commit/mutations_spec.js deleted file mode 100644 index 5de7a281d34..00000000000 --- a/spec/javascripts/ide/stores/modules/commit/mutations_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import commitState from '~/ide/stores/modules/commit/state'; -import mutations from '~/ide/stores/modules/commit/mutations'; - -describe('IDE commit module mutations', () => { - let state; - - beforeEach(() => { - state = commitState(); - }); - - describe('UPDATE_COMMIT_MESSAGE', () => { - it('updates commitMessage', () => { - mutations.UPDATE_COMMIT_MESSAGE(state, 'testing'); - - expect(state.commitMessage).toBe('testing'); - }); - }); - - describe('UPDATE_COMMIT_ACTION', () => { - it('updates commitAction', () => { - mutations.UPDATE_COMMIT_ACTION(state, 'testing'); - - expect(state.commitAction).toBe('testing'); - }); - }); - - describe('UPDATE_NEW_BRANCH_NAME', () => { - it('updates newBranchName', () => { - mutations.UPDATE_NEW_BRANCH_NAME(state, 'testing'); - - expect(state.newBranchName).toBe('testing'); - }); - }); - - describe('UPDATE_LOADING', () => { - it('updates submitCommitLoading', () => { - mutations.UPDATE_LOADING(state, true); - - expect(state.submitCommitLoading).toBeTruthy(); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js b/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js deleted file mode 100644 index 17cb457881f..00000000000 --- a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import createState from '~/ide/stores/state'; -import { activityBarViews } from '~/ide/constants'; -import * as getters from '~/ide/stores/modules/file_templates/getters'; - -describe('IDE file templates getters', () => { - describe('templateTypes', () => { - it('returns list of template types', () => { - expect(getters.templateTypes().length).toBe(4); - }); - }); - - describe('showFileTemplatesBar', () => { - let rootState; - - beforeEach(() => { - rootState = createState(); - }); - - it('returns true if template is found and currentActivityView is edit', () => { - rootState.currentActivityView = activityBarViews.edit; - - expect( - getters.showFileTemplatesBar( - null, - { - templateTypes: getters.templateTypes(), - }, - rootState, - )('LICENSE'), - ).toBe(true); - }); - - it('returns false if template is found and currentActivityView is not edit', () => { - rootState.currentActivityView = activityBarViews.commit; - - expect( - getters.showFileTemplatesBar( - null, - { - templateTypes: getters.templateTypes(), - }, - rootState, - )('LICENSE'), - ).toBe(false); - }); - - it('returns undefined if not found', () => { - expect( - getters.showFileTemplatesBar( - null, - { - templateTypes: getters.templateTypes(), - }, - rootState, - )('test'), - ).toBe(undefined); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js b/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js deleted file mode 100644 index 8e0e3ae99a1..00000000000 --- a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js +++ /dev/null @@ -1,69 +0,0 @@ -import createState from '~/ide/stores/modules/file_templates/state'; -import * as types from '~/ide/stores/modules/file_templates/mutation_types'; -import mutations from '~/ide/stores/modules/file_templates/mutations'; - -describe('IDE file templates mutations', () => { - let state; - - beforeEach(() => { - state = createState(); - }); - - describe(types.REQUEST_TEMPLATE_TYPES, () => { - it('sets isLoading', () => { - mutations[types.REQUEST_TEMPLATE_TYPES](state); - - expect(state.isLoading).toBe(true); - }); - }); - - describe(types.RECEIVE_TEMPLATE_TYPES_ERROR, () => { - it('sets isLoading', () => { - state.isLoading = true; - - mutations[types.RECEIVE_TEMPLATE_TYPES_ERROR](state); - - expect(state.isLoading).toBe(false); - }); - }); - - describe(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, () => { - it('sets isLoading to false', () => { - state.isLoading = true; - - mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, []); - - expect(state.isLoading).toBe(false); - }); - - it('sets templates', () => { - mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, ['test']); - - expect(state.templates).toEqual(['test']); - }); - }); - - describe(types.SET_SELECTED_TEMPLATE_TYPE, () => { - it('sets selectedTemplateType', () => { - mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type'); - - expect(state.selectedTemplateType).toBe('type'); - }); - - it('clears templates', () => { - state.templates = ['test']; - - mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type'); - - expect(state.templates).toEqual([]); - }); - }); - - describe(types.SET_UPDATE_SUCCESS, () => { - it('sets updateSuccess', () => { - mutations[types.SET_UPDATE_SUCCESS](state, true); - - expect(state.updateSuccess).toBe(true); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/modules/pane/getters_spec.js b/spec/javascripts/ide/stores/modules/pane/getters_spec.js deleted file mode 100644 index 8a213323de0..00000000000 --- a/spec/javascripts/ide/stores/modules/pane/getters_spec.js +++ /dev/null @@ -1,55 +0,0 @@ -import * as getters from '~/ide/stores/modules/pane/getters'; -import state from '~/ide/stores/modules/pane/state'; - -describe('IDE pane module getters', () => { - const TEST_VIEW = 'test-view'; - const TEST_KEEP_ALIVE_VIEWS = { - [TEST_VIEW]: true, - }; - - describe('isActiveView', () => { - it('returns true if given view matches currentView', () => { - const result = getters.isActiveView({ currentView: 'A' })('A'); - - expect(result).toBe(true); - }); - - it('returns false if given view does not match currentView', () => { - const result = getters.isActiveView({ currentView: 'A' })('B'); - - expect(result).toBe(false); - }); - }); - - describe('isAliveView', () => { - it('returns true if given view is in keepAliveViews', () => { - const result = getters.isAliveView({ keepAliveViews: TEST_KEEP_ALIVE_VIEWS }, {})(TEST_VIEW); - - expect(result).toBe(true); - }); - - it('returns true if given view is active view and open', () => { - const result = getters.isAliveView( - { ...state(), isOpen: true }, - { isActiveView: () => true }, - )(TEST_VIEW); - - expect(result).toBe(true); - }); - - it('returns false if given view is active view and closed', () => { - const result = getters.isAliveView(state(), { isActiveView: () => true })(TEST_VIEW); - - expect(result).toBe(false); - }); - - it('returns false if given view is not activeView', () => { - const result = getters.isAliveView( - { ...state(), isOpen: true }, - { isActiveView: () => false }, - )(TEST_VIEW); - - expect(result).toBe(false); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js b/spec/javascripts/ide/stores/modules/pane/mutations_spec.js deleted file mode 100644 index b5fcd35912e..00000000000 --- a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import state from '~/ide/stores/modules/pane/state'; -import mutations from '~/ide/stores/modules/pane/mutations'; -import * as types from '~/ide/stores/modules/pane/mutation_types'; - -describe('IDE pane module mutations', () => { - const TEST_VIEW = 'test-view'; - let mockedState; - - beforeEach(() => { - mockedState = state(); - }); - - describe('SET_OPEN', () => { - it('sets isOpen', () => { - mockedState.isOpen = false; - - mutations[types.SET_OPEN](mockedState, true); - - expect(mockedState.isOpen).toBe(true); - }); - }); - - describe('SET_CURRENT_VIEW', () => { - it('sets currentView', () => { - mockedState.currentView = null; - - mutations[types.SET_CURRENT_VIEW](mockedState, TEST_VIEW); - - expect(mockedState.currentView).toEqual(TEST_VIEW); - }); - }); - - describe('KEEP_ALIVE_VIEW', () => { - it('adds entry to keepAliveViews', () => { - mutations[types.KEEP_ALIVE_VIEW](mockedState, TEST_VIEW); - - expect(mockedState.keepAliveViews).toEqual({ - [TEST_VIEW]: true, - }); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js deleted file mode 100644 index 4514896b5ea..00000000000 --- a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import * as getters from '~/ide/stores/modules/pipelines/getters'; -import state from '~/ide/stores/modules/pipelines/state'; - -describe('IDE pipeline getters', () => { - let mockedState; - - beforeEach(() => { - mockedState = state(); - }); - - describe('hasLatestPipeline', () => { - it('returns false when loading is true', () => { - mockedState.isLoadingPipeline = true; - - expect(getters.hasLatestPipeline(mockedState)).toBe(false); - }); - - it('returns false when pipelines is null', () => { - mockedState.latestPipeline = null; - - expect(getters.hasLatestPipeline(mockedState)).toBe(false); - }); - - it('returns false when loading is true & pipelines is null', () => { - mockedState.latestPipeline = null; - mockedState.isLoadingPipeline = true; - - expect(getters.hasLatestPipeline(mockedState)).toBe(false); - }); - - it('returns true when loading is false & pipelines is an object', () => { - mockedState.latestPipeline = { - id: 1, - }; - mockedState.isLoadingPipeline = false; - - expect(getters.hasLatestPipeline(mockedState)).toBe(true); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js deleted file mode 100644 index 29eb859ddaf..00000000000 --- a/spec/javascripts/ide/stores/mutations/branch_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import mutations from '~/ide/stores/mutations/branch'; -import state from '~/ide/stores/state'; - -describe('Multi-file store branch mutations', () => { - let localState; - - beforeEach(() => { - localState = state(); - }); - - describe('SET_CURRENT_BRANCH', () => { - it('sets currentBranch', () => { - mutations.SET_CURRENT_BRANCH(localState, 'master'); - - expect(localState.currentBranchId).toBe('master'); - }); - }); - - describe('SET_BRANCH_COMMIT', () => { - it('sets the last commit on current project', () => { - localState.projects = { - Example: { - branches: { - master: {}, - }, - }, - }; - - mutations.SET_BRANCH_COMMIT(localState, { - projectId: 'Example', - branchId: 'master', - commit: { - title: 'Example commit', - }, - }); - - expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit'); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/mutations/merge_request_spec.js b/spec/javascripts/ide/stores/mutations/merge_request_spec.js deleted file mode 100644 index e30ca22022f..00000000000 --- a/spec/javascripts/ide/stores/mutations/merge_request_spec.js +++ /dev/null @@ -1,67 +0,0 @@ -import mutations from '~/ide/stores/mutations/merge_request'; -import state from '~/ide/stores/state'; - -describe('IDE store merge request mutations', () => { - let localState; - - beforeEach(() => { - localState = state(); - localState.projects = { abcproject: { mergeRequests: {} } }; - - mutations.SET_MERGE_REQUEST(localState, { - projectPath: 'abcproject', - mergeRequestId: 1, - mergeRequest: { - title: 'mr', - }, - }); - }); - - describe('SET_CURRENT_MERGE_REQUEST', () => { - it('sets current merge request', () => { - mutations.SET_CURRENT_MERGE_REQUEST(localState, 2); - - expect(localState.currentMergeRequestId).toBe(2); - }); - }); - - describe('SET_MERGE_REQUEST', () => { - it('setsmerge request data', () => { - const newMr = localState.projects.abcproject.mergeRequests[1]; - - expect(newMr.title).toBe('mr'); - expect(newMr.active).toBeTruthy(); - }); - }); - - describe('SET_MERGE_REQUEST_CHANGES', () => { - it('sets merge request changes', () => { - mutations.SET_MERGE_REQUEST_CHANGES(localState, { - projectPath: 'abcproject', - mergeRequestId: 1, - changes: { - diff: 'abc', - }, - }); - - const newMr = localState.projects.abcproject.mergeRequests[1]; - - expect(newMr.changes.diff).toBe('abc'); - }); - }); - - describe('SET_MERGE_REQUEST_VERSIONS', () => { - it('sets merge request versions', () => { - mutations.SET_MERGE_REQUEST_VERSIONS(localState, { - projectPath: 'abcproject', - mergeRequestId: 1, - versions: [{ id: 123 }], - }); - - const newMr = localState.projects.abcproject.mergeRequests[1]; - - expect(newMr.versions.length).toBe(1); - expect(newMr.versions[0].id).toBe(123); - }); - }); -}); diff --git a/spec/javascripts/image_diff/view_types_spec.js b/spec/javascripts/image_diff/view_types_spec.js deleted file mode 100644 index e9639f46497..00000000000 --- a/spec/javascripts/image_diff/view_types_spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import { viewTypes, isValidViewType } from '~/image_diff/view_types'; - -describe('viewTypes', () => { - describe('isValidViewType', () => { - it('should return true for TWO_UP', () => { - expect(isValidViewType(viewTypes.TWO_UP)).toEqual(true); - }); - - it('should return true for SWIPE', () => { - expect(isValidViewType(viewTypes.SWIPE)).toEqual(true); - }); - - it('should return true for ONION_SKIN', () => { - expect(isValidViewType(viewTypes.ONION_SKIN)).toEqual(true); - }); - - it('should return false for non view types', () => { - expect(isValidViewType('some-view-type')).toEqual(false); - expect(isValidViewType(null)).toEqual(false); - expect(isValidViewType(undefined)).toEqual(false); - expect(isValidViewType('')).toEqual(false); - }); - }); -}); diff --git a/spec/javascripts/import_projects/store/getters_spec.js b/spec/javascripts/import_projects/store/getters_spec.js deleted file mode 100644 index e5e4a95f473..00000000000 --- a/spec/javascripts/import_projects/store/getters_spec.js +++ /dev/null @@ -1,83 +0,0 @@ -import { - namespaceSelectOptions, - isImportingAnyRepo, - hasProviderRepos, - hasImportedProjects, -} from '~/import_projects/store/getters'; -import state from '~/import_projects/store/state'; - -describe('import_projects store getters', () => { - let localState; - - beforeEach(() => { - localState = state(); - }); - - describe('namespaceSelectOptions', () => { - const namespaces = [{ fullPath: 'namespace-0' }, { fullPath: 'namespace-1' }]; - const defaultTargetNamespace = 'current-user'; - - it('returns an options array with a "Users" and "Groups" optgroups', () => { - localState.namespaces = namespaces; - localState.defaultTargetNamespace = defaultTargetNamespace; - - const optionsArray = namespaceSelectOptions(localState); - const groupsGroup = optionsArray[0]; - const usersGroup = optionsArray[1]; - - expect(groupsGroup.text).toBe('Groups'); - expect(usersGroup.text).toBe('Users'); - - groupsGroup.children.forEach((child, index) => { - expect(child.id).toBe(namespaces[index].fullPath); - expect(child.text).toBe(namespaces[index].fullPath); - }); - - expect(usersGroup.children.length).toBe(1); - expect(usersGroup.children[0].id).toBe(defaultTargetNamespace); - expect(usersGroup.children[0].text).toBe(defaultTargetNamespace); - }); - }); - - describe('isImportingAnyRepo', () => { - it('returns true if there are any reposBeingImported', () => { - localState.reposBeingImported = new Array(1); - - expect(isImportingAnyRepo(localState)).toBe(true); - }); - - it('returns false if there are no reposBeingImported', () => { - localState.reposBeingImported = []; - - expect(isImportingAnyRepo(localState)).toBe(false); - }); - }); - - describe('hasProviderRepos', () => { - it('returns true if there are any providerRepos', () => { - localState.providerRepos = new Array(1); - - expect(hasProviderRepos(localState)).toBe(true); - }); - - it('returns false if there are no providerRepos', () => { - localState.providerRepos = []; - - expect(hasProviderRepos(localState)).toBe(false); - }); - }); - - describe('hasImportedProjects', () => { - it('returns true if there are any importedProjects', () => { - localState.importedProjects = new Array(1); - - expect(hasImportedProjects(localState)).toBe(true); - }); - - it('returns false if there are no importedProjects', () => { - localState.importedProjects = []; - - expect(hasImportedProjects(localState)).toBe(false); - }); - }); -}); diff --git a/spec/javascripts/import_projects/store/mutations_spec.js b/spec/javascripts/import_projects/store/mutations_spec.js deleted file mode 100644 index 8db8e9819ba..00000000000 --- a/spec/javascripts/import_projects/store/mutations_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import * as types from '~/import_projects/store/mutation_types'; -import mutations from '~/import_projects/store/mutations'; - -describe('import_projects store mutations', () => { - describe(types.RECEIVE_IMPORT_SUCCESS, () => { - it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => { - const repoId = 1; - const state = { - reposBeingImported: [repoId], - providerRepos: [{ id: repoId }], - importedProjects: [], - }; - const importedProject = { id: repoId }; - - mutations[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }); - - expect(state.reposBeingImported.includes(repoId)).toBe(false); - expect(state.providerRepos.some(repo => repo.id === repoId)).toBe(false); - expect(state.importedProjects.some(repo => repo.id === repoId)).toBe(true); - }); - }); - - describe(types.RECEIVE_JOBS_SUCCESS, () => { - it('updates importStatus of existing importedProjects', () => { - const repoId = 1; - const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] }; - const updatedProjects = [{ id: repoId, importStatus: 'finished' }]; - - mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects); - - expect(state.importedProjects[0].importStatus).toBe(updatedProjects[0].importStatus); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js deleted file mode 100644 index a2df79bdda0..00000000000 --- a/spec/javascripts/jobs/components/empty_state_spec.js +++ /dev/null @@ -1,93 +0,0 @@ -import Vue from 'vue'; -import component from '~/jobs/components/empty_state.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Empty State', () => { - const Component = Vue.extend(component); - let vm; - - const props = { - illustrationPath: 'illustrations/pending_job_empty.svg', - illustrationSizeClass: 'svg-430', - title: 'This job has not started yet', - }; - - const content = 'This job is in pending state and is waiting to be picked by a runner'; - - afterEach(() => { - vm.$destroy(); - }); - - describe('renders image and title', () => { - beforeEach(() => { - vm = mountComponent(Component, { - ...props, - content, - }); - }); - - it('renders img with provided path and size', () => { - expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(props.illustrationPath); - expect(vm.$el.querySelector('.svg-content').classList).toContain(props.illustrationSizeClass); - }); - - it('renders provided title', () => { - expect(vm.$el.querySelector('.js-job-empty-state-title').textContent.trim()).toEqual( - props.title, - ); - }); - }); - - describe('with content', () => { - it('renders content', () => { - vm = mountComponent(Component, { - ...props, - content, - }); - - expect(vm.$el.querySelector('.js-job-empty-state-content').textContent.trim()).toEqual( - content, - ); - }); - }); - - describe('without content', () => { - it('does not render content', () => { - vm = mountComponent(Component, { - ...props, - }); - - expect(vm.$el.querySelector('.js-job-empty-state-content')).toBeNull(); - }); - }); - - describe('with action', () => { - it('renders action', () => { - vm = mountComponent(Component, { - ...props, - content, - action: { - path: 'runner', - button_title: 'Check runner', - method: 'post', - }, - }); - - expect(vm.$el.querySelector('.js-job-empty-state-action').getAttribute('href')).toEqual( - 'runner', - ); - }); - }); - - describe('without action', () => { - it('does not render action', () => { - vm = mountComponent(Component, { - ...props, - content, - action: null, - }); - - expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/erased_block_spec.js b/spec/javascripts/jobs/components/erased_block_spec.js deleted file mode 100644 index 8e0433d3fb7..00000000000 --- a/spec/javascripts/jobs/components/erased_block_spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import Vue from 'vue'; -import { getTimeago } from '~/lib/utils/datetime_utility'; -import component from '~/jobs/components/erased_block.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Erased block', () => { - const Component = Vue.extend(component); - let vm; - - const erasedAt = '2016-11-07T11:11:16.525Z'; - const timeago = getTimeago(); - const formatedDate = timeago.format(erasedAt); - - afterEach(() => { - vm.$destroy(); - }); - - describe('with job erased by user', () => { - beforeEach(() => { - vm = mountComponent(Component, { - user: { - username: 'root', - web_url: 'gitlab.com/root', - }, - erasedAt, - }); - }); - - it('renders username and link', () => { - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('gitlab.com/root'); - - expect(vm.$el.textContent).toContain('Job has been erased by'); - expect(vm.$el.textContent).toContain('root'); - }); - - it('renders erasedAt', () => { - expect(vm.$el.textContent).toContain(formatedDate); - }); - }); - - describe('with erased job', () => { - beforeEach(() => { - vm = mountComponent(Component, { - erasedAt, - }); - }); - - it('renders username and link', () => { - expect(vm.$el.textContent).toContain('Job has been erased'); - }); - - it('renders erasedAt', () => { - expect(vm.$el.textContent).toContain(formatedDate); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/sidebar_detail_row_spec.js b/spec/javascripts/jobs/components/sidebar_detail_row_spec.js deleted file mode 100644 index 42d11266dad..00000000000 --- a/spec/javascripts/jobs/components/sidebar_detail_row_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import Vue from 'vue'; -import sidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue'; - -describe('Sidebar detail row', () => { - let SidebarDetailRow; - let vm; - - beforeEach(() => { - SidebarDetailRow = Vue.extend(sidebarDetailRow); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render no title', () => { - vm = new SidebarDetailRow({ - propsData: { - value: 'this is the value', - }, - }).$mount(); - - expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual('this is the value'); - }); - - beforeEach(() => { - vm = new SidebarDetailRow({ - propsData: { - title: 'this is the title', - value: 'this is the value', - }, - }).$mount(); - }); - - it('should render provided title and value', () => { - expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual( - 'this is the title: this is the value', - ); - }); - - describe('when helpUrl not provided', () => { - it('should not render help', () => { - expect(vm.$el.querySelector('.help-button')).toBeNull(); - }); - }); - - describe('when helpUrl provided', () => { - beforeEach(() => { - vm = new SidebarDetailRow({ - propsData: { - helpUrl: 'help url', - value: 'foo', - }, - }).$mount(); - }); - - it('should render help', () => { - expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url'); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/stuck_block_spec.js b/spec/javascripts/jobs/components/stuck_block_spec.js deleted file mode 100644 index c320793b2be..00000000000 --- a/spec/javascripts/jobs/components/stuck_block_spec.js +++ /dev/null @@ -1,81 +0,0 @@ -import Vue from 'vue'; -import component from '~/jobs/components/stuck_block.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Stuck Block Job component', () => { - const Component = Vue.extend(component); - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - describe('with no runners for project', () => { - beforeEach(() => { - vm = mountComponent(Component, { - hasNoRunnersForProject: true, - runnersPath: '/root/project/runners#js-runners-settings', - }); - }); - - it('renders only information about project not having runners', () => { - expect(vm.$el.querySelector('.js-stuck-no-runners')).not.toBeNull(); - expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); - expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); - }); - - it('renders link to runners page', () => { - expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( - '/root/project/runners#js-runners-settings', - ); - }); - }); - - describe('with tags', () => { - beforeEach(() => { - vm = mountComponent(Component, { - hasNoRunnersForProject: false, - tags: ['docker', 'gitlab-org'], - runnersPath: '/root/project/runners#js-runners-settings', - }); - }); - - it('renders information about the tags not being set', () => { - expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); - expect(vm.$el.querySelector('.js-stuck-with-tags')).not.toBeNull(); - expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); - }); - - it('renders tags', () => { - expect(vm.$el.textContent).toContain('docker'); - expect(vm.$el.textContent).toContain('gitlab-org'); - }); - - it('renders link to runners page', () => { - expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( - '/root/project/runners#js-runners-settings', - ); - }); - }); - - describe('without active runners', () => { - beforeEach(() => { - vm = mountComponent(Component, { - hasNoRunnersForProject: false, - runnersPath: '/root/project/runners#js-runners-settings', - }); - }); - - it('renders information about project not having runners', () => { - expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); - expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); - expect(vm.$el.querySelector('.js-stuck-no-active-runner')).not.toBeNull(); - }); - - it('renders link to runners page', () => { - expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( - '/root/project/runners#js-runners-settings', - ); - }); - }); -}); diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js deleted file mode 100644 index 379114c3737..00000000000 --- a/spec/javascripts/jobs/store/getters_spec.js +++ /dev/null @@ -1,243 +0,0 @@ -import * as getters from '~/jobs/store/getters'; -import state from '~/jobs/store/state'; - -describe('Job Store Getters', () => { - let localState; - - beforeEach(() => { - localState = state(); - }); - - describe('headerTime', () => { - describe('when the job has started key', () => { - it('returns started key', () => { - const started = '2018-08-31T16:20:49.023Z'; - localState.job.started = started; - - expect(getters.headerTime(localState)).toEqual(started); - }); - }); - - describe('when the job does not have started key', () => { - it('returns created_at key', () => { - const created = '2018-08-31T16:20:49.023Z'; - localState.job.created_at = created; - - expect(getters.headerTime(localState)).toEqual(created); - }); - }); - }); - - describe('shouldRenderCalloutMessage', () => { - describe('with status and callout message', () => { - it('returns true', () => { - localState.job.callout_message = 'Callout message'; - localState.job.status = { icon: 'passed' }; - - expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true); - }); - }); - - describe('without status & with callout message', () => { - it('returns false', () => { - localState.job.callout_message = 'Callout message'; - - expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); - }); - }); - - describe('with status & without callout message', () => { - it('returns false', () => { - localState.job.status = { icon: 'passed' }; - - expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); - }); - }); - }); - - describe('shouldRenderTriggeredLabel', () => { - describe('when started equals null', () => { - it('returns false', () => { - localState.job.started = null; - - expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false); - }); - }); - - describe('when started equals string', () => { - it('returns true', () => { - localState.job.started = '2018-08-31T16:20:49.023Z'; - - expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true); - }); - }); - }); - - describe('hasEnvironment', () => { - describe('without `deployment_status`', () => { - it('returns false', () => { - expect(getters.hasEnvironment(localState)).toEqual(false); - }); - }); - - describe('with an empty object for `deployment_status`', () => { - it('returns false', () => { - localState.job.deployment_status = {}; - - expect(getters.hasEnvironment(localState)).toEqual(false); - }); - }); - - describe('when `deployment_status` is defined and not empty', () => { - it('returns true', () => { - localState.job.deployment_status = { - status: 'creating', - environment: { - last_deployment: {}, - }, - }; - - expect(getters.hasEnvironment(localState)).toEqual(true); - }); - }); - }); - - describe('hasTrace', () => { - describe('when has_trace is true', () => { - it('returns true', () => { - localState.job.has_trace = true; - localState.job.status = {}; - - expect(getters.hasTrace(localState)).toEqual(true); - }); - }); - - describe('when job is running', () => { - it('returns true', () => { - localState.job.has_trace = false; - localState.job.status = { group: 'running' }; - - expect(getters.hasTrace(localState)).toEqual(true); - }); - }); - - describe('when has_trace is false and job is not running', () => { - it('returns false', () => { - localState.job.has_trace = false; - localState.job.status = { group: 'pending' }; - - expect(getters.hasTrace(localState)).toEqual(false); - }); - }); - }); - - describe('emptyStateIllustration', () => { - describe('with defined illustration', () => { - it('returns the state illustration object', () => { - localState.job.status = { - illustration: { - path: 'foo', - }, - }; - - expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' }); - }); - }); - - describe('when illustration is not defined', () => { - it('returns an empty object', () => { - expect(getters.emptyStateIllustration(localState)).toEqual({}); - }); - }); - }); - - describe('shouldRenderSharedRunnerLimitWarning', () => { - describe('without runners information', () => { - it('returns false', () => { - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false); - }); - }); - - describe('with runners information', () => { - describe('when used quota is less than limit', () => { - it('returns false', () => { - localState.job.runners = { - quota: { - used: 33, - limit: 2000, - }, - available: true, - online: true, - }; - - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false); - }); - }); - - describe('when used quota is equal to limit', () => { - it('returns true', () => { - localState.job.runners = { - quota: { - used: 2000, - limit: 2000, - }, - available: true, - online: true, - }; - - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true); - }); - }); - - describe('when used quota is bigger than limit', () => { - it('returns true', () => { - localState.job.runners = { - quota: { - used: 2002, - limit: 2000, - }, - available: true, - online: true, - }; - - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true); - }); - }); - }); - }); - - describe('hasRunnersForProject', () => { - describe('with available and offline runners', () => { - it('returns true', () => { - localState.job.runners = { - available: true, - online: false, - }; - - expect(getters.hasRunnersForProject(localState)).toEqual(true); - }); - }); - - describe('with non available runners', () => { - it('returns false', () => { - localState.job.runners = { - available: false, - online: false, - }; - - expect(getters.hasRunnersForProject(localState)).toEqual(false); - }); - }); - - describe('with online runners', () => { - it('returns false', () => { - localState.job.runners = { - available: false, - online: true, - }; - - expect(getters.hasRunnersForProject(localState)).toEqual(false); - }); - }); - }); -}); diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/javascripts/jobs/store/mutations_spec.js deleted file mode 100644 index d7908efcf13..00000000000 --- a/spec/javascripts/jobs/store/mutations_spec.js +++ /dev/null @@ -1,230 +0,0 @@ -import state from '~/jobs/store/state'; -import mutations from '~/jobs/store/mutations'; -import * as types from '~/jobs/store/mutation_types'; - -describe('Jobs Store Mutations', () => { - let stateCopy; - - const html = - 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png
    I'; - - beforeEach(() => { - stateCopy = state(); - }); - - describe('SET_JOB_ENDPOINT', () => { - it('should set jobEndpoint', () => { - mutations[types.SET_JOB_ENDPOINT](stateCopy, 'job/21312321.json'); - - expect(stateCopy.jobEndpoint).toEqual('job/21312321.json'); - }); - }); - - describe('HIDE_SIDEBAR', () => { - it('should set isSidebarOpen to false', () => { - mutations[types.HIDE_SIDEBAR](stateCopy); - - expect(stateCopy.isSidebarOpen).toEqual(false); - }); - }); - - describe('SHOW_SIDEBAR', () => { - it('should set isSidebarOpen to true', () => { - mutations[types.SHOW_SIDEBAR](stateCopy); - - expect(stateCopy.isSidebarOpen).toEqual(true); - }); - }); - - describe('RECEIVE_TRACE_SUCCESS', () => { - describe('when trace has state', () => { - it('sets traceState', () => { - const stateLog = - 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0='; - mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { - state: stateLog, - }); - - expect(stateCopy.traceState).toEqual(stateLog); - }); - }); - - describe('when traceSize is smaller than the total size', () => { - it('sets isTraceSizeVisible to true', () => { - mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { total: 51184600, size: 1231 }); - - expect(stateCopy.isTraceSizeVisible).toEqual(true); - }); - }); - - describe('when traceSize is bigger than the total size', () => { - it('sets isTraceSizeVisible to false', () => { - const copy = Object.assign({}, stateCopy, { traceSize: 5118460, size: 2321312 }); - - mutations[types.RECEIVE_TRACE_SUCCESS](copy, { total: 511846 }); - - expect(copy.isTraceSizeVisible).toEqual(false); - }); - }); - - it('sets trace, trace size and isTraceComplete', () => { - mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { - append: true, - html, - size: 511846, - complete: true, - }); - - expect(stateCopy.trace).toEqual(html); - expect(stateCopy.traceSize).toEqual(511846); - expect(stateCopy.isTraceComplete).toEqual(true); - }); - }); - - describe('STOP_POLLING_TRACE', () => { - it('sets isTraceComplete to true', () => { - mutations[types.STOP_POLLING_TRACE](stateCopy); - - expect(stateCopy.isTraceComplete).toEqual(true); - }); - }); - - describe('RECEIVE_TRACE_ERROR', () => { - it('resets trace state and sets error to true', () => { - mutations[types.RECEIVE_TRACE_ERROR](stateCopy); - - expect(stateCopy.isTraceComplete).toEqual(true); - }); - }); - - describe('REQUEST_JOB', () => { - it('sets isLoading to true', () => { - mutations[types.REQUEST_JOB](stateCopy); - - expect(stateCopy.isLoading).toEqual(true); - }); - }); - - describe('RECEIVE_JOB_SUCCESS', () => { - it('sets is loading to false', () => { - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); - - expect(stateCopy.isLoading).toEqual(false); - }); - - it('sets hasError to false', () => { - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); - - expect(stateCopy.hasError).toEqual(false); - }); - - it('sets job data', () => { - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); - - expect(stateCopy.job).toEqual({ id: 1312321 }); - }); - - it('sets selectedStage when the selectedStage is empty', () => { - expect(stateCopy.selectedStage).toEqual(''); - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); - - expect(stateCopy.selectedStage).toEqual('deploy'); - }); - - it('does not set selectedStage when the selectedStage is not More', () => { - stateCopy.selectedStage = 'notify'; - - expect(stateCopy.selectedStage).toEqual('notify'); - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); - - expect(stateCopy.selectedStage).toEqual('notify'); - }); - }); - - describe('RECEIVE_JOB_ERROR', () => { - it('resets job data', () => { - mutations[types.RECEIVE_JOB_ERROR](stateCopy); - - expect(stateCopy.isLoading).toEqual(false); - expect(stateCopy.job).toEqual({}); - }); - }); - - describe('REQUEST_STAGES', () => { - it('sets isLoadingStages to true', () => { - mutations[types.REQUEST_STAGES](stateCopy); - - expect(stateCopy.isLoadingStages).toEqual(true); - }); - }); - - describe('RECEIVE_STAGES_SUCCESS', () => { - beforeEach(() => { - mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]); - }); - - it('sets isLoadingStages to false', () => { - expect(stateCopy.isLoadingStages).toEqual(false); - }); - - it('sets stages', () => { - expect(stateCopy.stages).toEqual([{ name: 'build' }]); - }); - }); - - describe('RECEIVE_STAGES_ERROR', () => { - beforeEach(() => { - mutations[types.RECEIVE_STAGES_ERROR](stateCopy); - }); - - it('sets isLoadingStages to false', () => { - expect(stateCopy.isLoadingStages).toEqual(false); - }); - - it('resets stages', () => { - expect(stateCopy.stages).toEqual([]); - }); - }); - - describe('REQUEST_JOBS_FOR_STAGE', () => { - it('sets isLoadingStages to true', () => { - mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); - - expect(stateCopy.isLoadingJobs).toEqual(true); - }); - - it('sets selectedStage', () => { - mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); - - expect(stateCopy.selectedStage).toEqual('deploy'); - }); - }); - - describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => { - beforeEach(() => { - mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]); - }); - - it('sets isLoadingJobs to false', () => { - expect(stateCopy.isLoadingJobs).toEqual(false); - }); - - it('sets jobs', () => { - expect(stateCopy.jobs).toEqual([{ name: 'karma' }]); - }); - }); - - describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => { - beforeEach(() => { - mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy); - }); - - it('sets isLoadingJobs to false', () => { - expect(stateCopy.isLoadingJobs).toEqual(false); - }); - - it('resets jobs', () => { - expect(stateCopy.jobs).toEqual([]); - }); - }); -}); diff --git a/spec/javascripts/labels_select_spec.js b/spec/javascripts/labels_select_spec.js deleted file mode 100644 index acfdc885032..00000000000 --- a/spec/javascripts/labels_select_spec.js +++ /dev/null @@ -1,52 +0,0 @@ -import $ from 'jquery'; -import LabelsSelect from '~/labels_select'; - -const mockUrl = '/foo/bar/url'; - -const mockLabels = [ - { - id: 26, - title: 'Foo Label', - description: 'Foobar', - color: '#BADA55', - text_color: '#FFFFFF', - }, -]; - -describe('LabelsSelect', () => { - describe('getLabelTemplate', () => { - const label = mockLabels[0]; - let $labelEl; - - beforeEach(() => { - $labelEl = $( - LabelsSelect.getLabelTemplate({ - labels: mockLabels, - issueUpdateURL: mockUrl, - }), - ); - }); - - it('generated label item template has correct label URL', () => { - expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label'); - }); - - it('generated label item template has correct label title', () => { - expect($labelEl.find('span.label').text()).toBe(label.title); - }); - - it('generated label item template has label description as title attribute', () => { - expect($labelEl.find('span.label').attr('title')).toBe(label.description); - }); - - it('generated label item template has correct label styles', () => { - expect($labelEl.find('span.label').attr('style')).toBe( - `background-color: ${label.color}; color: ${label.text_color};`, - ); - }); - - it('generated label item has a badge class', () => { - expect($labelEl.find('span').hasClass('badge')).toEqual(true); - }); - }); -}); diff --git a/spec/javascripts/lib/utils/cache_spec.js b/spec/javascripts/lib/utils/cache_spec.js deleted file mode 100644 index 2fe02a7592c..00000000000 --- a/spec/javascripts/lib/utils/cache_spec.js +++ /dev/null @@ -1,65 +0,0 @@ -import Cache from '~/lib/utils/cache'; - -describe('Cache', () => { - const dummyKey = 'just some key'; - const dummyValue = 'more than a value'; - let cache; - - beforeEach(() => { - cache = new Cache(); - }); - - describe('get', () => { - it('return cached data', () => { - cache.internalStorage[dummyKey] = dummyValue; - - expect(cache.get(dummyKey)).toBe(dummyValue); - }); - - it('returns undefined for missing data', () => { - expect(cache.internalStorage[dummyKey]).toBe(undefined); - expect(cache.get(dummyKey)).toBe(undefined); - }); - }); - - describe('hasData', () => { - it('return true for cached data', () => { - cache.internalStorage[dummyKey] = dummyValue; - - expect(cache.hasData(dummyKey)).toBe(true); - }); - - it('returns false for missing data', () => { - expect(cache.internalStorage[dummyKey]).toBe(undefined); - expect(cache.hasData(dummyKey)).toBe(false); - }); - }); - - describe('remove', () => { - it('removes data from cache', () => { - cache.internalStorage[dummyKey] = dummyValue; - - cache.remove(dummyKey); - - expect(cache.internalStorage[dummyKey]).toBe(undefined); - }); - - it('does nothing for missing data', () => { - expect(cache.internalStorage[dummyKey]).toBe(undefined); - - cache.remove(dummyKey); - - expect(cache.internalStorage[dummyKey]).toBe(undefined); - }); - - it('does not remove wrong data', () => { - cache.internalStorage[dummyKey] = dummyValue; - cache.internalStorage[dummyKey + dummyKey] = dummyValue + dummyValue; - - cache.remove(dummyKey); - - expect(cache.internalStorage[dummyKey]).toBe(undefined); - expect(cache.internalStorage[dummyKey + dummyKey]).toBe(dummyValue + dummyValue); - }); - }); -}); diff --git a/spec/javascripts/lib/utils/grammar_spec.js b/spec/javascripts/lib/utils/grammar_spec.js deleted file mode 100644 index 377b2ffb48c..00000000000 --- a/spec/javascripts/lib/utils/grammar_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -import * as grammar from '~/lib/utils/grammar'; - -describe('utils/grammar', () => { - describe('toNounSeriesText', () => { - it('with empty items returns empty string', () => { - expect(grammar.toNounSeriesText([])).toBe(''); - }); - - it('with single item returns item', () => { - const items = ['Lorem Ipsum']; - - expect(grammar.toNounSeriesText(items)).toBe(items[0]); - }); - - it('with 2 items returns item1 and item2', () => { - const items = ['Dolar', 'Sit Amit']; - - expect(grammar.toNounSeriesText(items)).toBe(`${items[0]} and ${items[1]}`); - }); - - it('with 3 items returns comma separated series', () => { - const items = ['Lorem', 'Ipsum', 'dolar']; - const expected = 'Lorem, Ipsum, and dolar'; - - expect(grammar.toNounSeriesText(items)).toBe(expected); - }); - - it('with 6 items returns comma separated series', () => { - const items = ['Lorem', 'ipsum', 'dolar', 'sit', 'amit', 'consectetur']; - const expected = 'Lorem, ipsum, dolar, sit, amit, and consectetur'; - - expect(grammar.toNounSeriesText(items)).toBe(expected); - }); - }); -}); diff --git a/spec/javascripts/lib/utils/image_utility_spec.js b/spec/javascripts/lib/utils/image_utility_spec.js deleted file mode 100644 index a7eff419fba..00000000000 --- a/spec/javascripts/lib/utils/image_utility_spec.js +++ /dev/null @@ -1,32 +0,0 @@ -import { isImageLoaded } from '~/lib/utils/image_utility'; - -describe('imageUtility', () => { - describe('isImageLoaded', () => { - it('should return false when image.complete is false', () => { - const element = { - complete: false, - naturalHeight: 100, - }; - - expect(isImageLoaded(element)).toEqual(false); - }); - - it('should return false when naturalHeight = 0', () => { - const element = { - complete: true, - naturalHeight: 0, - }; - - expect(isImageLoaded(element)).toEqual(false); - }); - - it('should return true when image.complete and naturalHeight != 0', () => { - const element = { - complete: true, - naturalHeight: 100, - }; - - expect(isImageLoaded(element)).toEqual(true); - }); - }); -}); diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js deleted file mode 100644 index 818404bad81..00000000000 --- a/spec/javascripts/lib/utils/number_utility_spec.js +++ /dev/null @@ -1,101 +0,0 @@ -import { - formatRelevantDigits, - bytesToKiB, - bytesToMiB, - bytesToGiB, - numberToHumanSize, - sum, -} from '~/lib/utils/number_utils'; - -describe('Number Utils', () => { - describe('formatRelevantDigits', () => { - it('returns an empty string when the number is NaN', () => { - expect(formatRelevantDigits('fail')).toBe(''); - }); - - it('returns 4 decimals when there is 4 plus digits to the left', () => { - const formattedNumber = formatRelevantDigits('1000.1234567'); - const rightFromDecimal = formattedNumber.split('.')[1]; - const leftFromDecimal = formattedNumber.split('.')[0]; - - expect(rightFromDecimal.length).toBe(4); - expect(leftFromDecimal.length).toBe(4); - }); - - it('returns 3 decimals when there is 1 digit to the left', () => { - const formattedNumber = formatRelevantDigits('0.1234567'); - const rightFromDecimal = formattedNumber.split('.')[1]; - const leftFromDecimal = formattedNumber.split('.')[0]; - - expect(rightFromDecimal.length).toBe(3); - expect(leftFromDecimal.length).toBe(1); - }); - - it('returns 2 decimals when there is 2 digits to the left', () => { - const formattedNumber = formatRelevantDigits('10.1234567'); - const rightFromDecimal = formattedNumber.split('.')[1]; - const leftFromDecimal = formattedNumber.split('.')[0]; - - expect(rightFromDecimal.length).toBe(2); - expect(leftFromDecimal.length).toBe(2); - }); - - it('returns 1 decimal when there is 3 digits to the left', () => { - const formattedNumber = formatRelevantDigits('100.1234567'); - const rightFromDecimal = formattedNumber.split('.')[1]; - const leftFromDecimal = formattedNumber.split('.')[0]; - - expect(rightFromDecimal.length).toBe(1); - expect(leftFromDecimal.length).toBe(3); - }); - }); - - describe('bytesToKiB', () => { - it('calculates KiB for the given bytes', () => { - expect(bytesToKiB(1024)).toEqual(1); - expect(bytesToKiB(1000)).toEqual(0.9765625); - }); - }); - - describe('bytesToMiB', () => { - it('calculates MiB for the given bytes', () => { - expect(bytesToMiB(1048576)).toEqual(1); - expect(bytesToMiB(1000000)).toEqual(0.95367431640625); - }); - }); - - describe('bytesToGiB', () => { - it('calculates GiB for the given bytes', () => { - expect(bytesToGiB(1073741824)).toEqual(1); - expect(bytesToGiB(10737418240)).toEqual(10); - }); - }); - - describe('numberToHumanSize', () => { - it('should return bytes', () => { - expect(numberToHumanSize(654)).toEqual('654 bytes'); - }); - - it('should return KiB', () => { - expect(numberToHumanSize(1079)).toEqual('1.05 KiB'); - }); - - it('should return MiB', () => { - expect(numberToHumanSize(10485764)).toEqual('10.00 MiB'); - }); - - it('should return GiB', () => { - expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB'); - }); - }); - - describe('sum', () => { - it('should add up two values', () => { - expect(sum(1, 2)).toEqual(3); - }); - - it('should add up all the values in an array when passed to a reducer', () => { - expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15); - }); - }); -}); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js deleted file mode 100644 index 0a266b19ea5..00000000000 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ /dev/null @@ -1,154 +0,0 @@ -import * as textUtils from '~/lib/utils/text_utility'; - -describe('text_utility', () => { - describe('addDelimiter', () => { - it('should add a delimiter to the given string', () => { - expect(textUtils.addDelimiter('1234')).toEqual('1,234'); - expect(textUtils.addDelimiter('222222')).toEqual('222,222'); - }); - - it('should not add a delimiter if string contains no numbers', () => { - expect(textUtils.addDelimiter('aaaa')).toEqual('aaaa'); - }); - }); - - describe('highCountTrim', () => { - it('returns 99+ for count >= 100', () => { - expect(textUtils.highCountTrim(105)).toBe('99+'); - expect(textUtils.highCountTrim(100)).toBe('99+'); - }); - - it('returns exact number for count < 100', () => { - expect(textUtils.highCountTrim(45)).toBe(45); - }); - }); - - describe('capitalizeFirstCharacter', () => { - it('returns string with first letter capitalized', () => { - expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab'); - expect(textUtils.highCountTrim(105)).toBe('99+'); - expect(textUtils.highCountTrim(100)).toBe('99+'); - }); - }); - - describe('humanize', () => { - it('should remove underscores and uppercase the first letter', () => { - expect(textUtils.humanize('foo_bar')).toEqual('Foo bar'); - }); - }); - - describe('pluralize', () => { - it('should pluralize given string', () => { - expect(textUtils.pluralize('test', 2)).toBe('tests'); - }); - - it('should pluralize when count is 0', () => { - expect(textUtils.pluralize('test', 0)).toBe('tests'); - }); - - it('should not pluralize when count is 1', () => { - expect(textUtils.pluralize('test', 1)).toBe('test'); - }); - }); - - describe('dasherize', () => { - it('should replace underscores with dashes', () => { - expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo'); - }); - }); - - describe('slugify', () => { - it('should remove accents and convert to lower case', () => { - expect(textUtils.slugify('João')).toEqual('joão'); - }); - }); - - describe('slugifyWithHyphens', () => { - it('should replaces whitespaces with hyphens and convert to lower case', () => { - expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string'); - }); - }); - - describe('stripHtml', () => { - it('replaces html tag with the default replacement', () => { - expect(textUtils.stripHtml('This is a text with

    html

    .')).toEqual( - 'This is a text with html.', - ); - }); - - it('replaces html tags with the provided replacement', () => { - expect(textUtils.stripHtml('This is a text with

    html

    .', ' ')).toEqual( - 'This is a text with html .', - ); - }); - - it('passes through with null string input', () => { - expect(textUtils.stripHtml(null, ' ')).toEqual(null); - }); - - it('passes through with undefined string input', () => { - expect(textUtils.stripHtml(undefined, ' ')).toEqual(undefined); - }); - }); - - describe('convertToCamelCase', () => { - it('converts snake_case string to camelCase string', () => { - expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase'); - }); - }); - - describe('convertToSentenceCase', () => { - it('converts Sentence Case to Sentence case', () => { - expect(textUtils.convertToSentenceCase('Hello World')).toBe('Hello world'); - }); - }); - - describe('truncateSha', () => { - it('shortens SHAs to 8 characters', () => { - expect(textUtils.truncateSha('verylongsha')).toBe('verylong'); - }); - - it('leaves short SHAs as is', () => { - expect(textUtils.truncateSha('shortsha')).toBe('shortsha'); - }); - }); - - describe('splitCamelCase', () => { - it('separates a PascalCase word to two', () => { - expect(textUtils.splitCamelCase('HelloWorld')).toBe('Hello World'); - }); - }); - - describe('getFirstCharacterCapitalized', () => { - it('returns the first character capitalized, if first character is alphabetic', () => { - expect(textUtils.getFirstCharacterCapitalized('loremIpsumDolar')).toEqual('L'); - expect(textUtils.getFirstCharacterCapitalized('Sit amit !')).toEqual('S'); - }); - - it('returns the first character, if first character is non-alphabetic', () => { - expect(textUtils.getFirstCharacterCapitalized(' lorem')).toEqual(' '); - expect(textUtils.getFirstCharacterCapitalized('%#!')).toEqual('%'); - }); - - it('returns an empty string, if string is falsey', () => { - expect(textUtils.getFirstCharacterCapitalized('')).toEqual(''); - expect(textUtils.getFirstCharacterCapitalized(null)).toEqual(''); - }); - }); - - describe('truncatePathMiddleToLength', () => { - it('does not truncate text', () => { - expect(textUtils.truncatePathMiddleToLength('app/test', 50)).toEqual('app/test'); - }); - - it('truncates middle of the path', () => { - expect(textUtils.truncatePathMiddleToLength('app/test/diff', 13)).toEqual('app/…/diff'); - }); - - it('truncates multiple times in the middle of the path', () => { - expect(textUtils.truncatePathMiddleToLength('app/test/merge_request/diff', 13)).toEqual( - 'app/…/…/diff', - ); - }); - }); -}); diff --git a/spec/javascripts/locale/ensure_single_line_spec.js b/spec/javascripts/locale/ensure_single_line_spec.js deleted file mode 100644 index 20b04cab9c8..00000000000 --- a/spec/javascripts/locale/ensure_single_line_spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import ensureSingleLine from '~/locale/ensure_single_line'; - -describe('locale', () => { - describe('ensureSingleLine', () => { - it('should remove newlines at the start of the string', () => { - const result = 'Test'; - - expect(ensureSingleLine(`\n${result}`)).toBe(result); - expect(ensureSingleLine(`\t\n\t${result}`)).toBe(result); - expect(ensureSingleLine(`\r\n${result}`)).toBe(result); - expect(ensureSingleLine(`\r\n ${result}`)).toBe(result); - expect(ensureSingleLine(`\r ${result}`)).toBe(result); - expect(ensureSingleLine(` \n ${result}`)).toBe(result); - }); - - it('should remove newlines at the end of the string', () => { - const result = 'Test'; - - expect(ensureSingleLine(`${result}\n`)).toBe(result); - expect(ensureSingleLine(`${result}\t\n\t`)).toBe(result); - expect(ensureSingleLine(`${result}\r\n`)).toBe(result); - expect(ensureSingleLine(`${result}\r`)).toBe(result); - expect(ensureSingleLine(`${result} \r`)).toBe(result); - expect(ensureSingleLine(`${result} \r\n `)).toBe(result); - }); - - it('should replace newlines in the middle of the string with a single space', () => { - const result = 'Test'; - - expect(ensureSingleLine(`${result}\n${result}`)).toBe(`${result} ${result}`); - expect(ensureSingleLine(`${result}\t\n\t${result}`)).toBe(`${result} ${result}`); - expect(ensureSingleLine(`${result}\r\n${result}`)).toBe(`${result} ${result}`); - expect(ensureSingleLine(`${result}\r${result}`)).toBe(`${result} ${result}`); - expect(ensureSingleLine(`${result} \r${result}`)).toBe(`${result} ${result}`); - expect(ensureSingleLine(`${result} \r\n ${result}`)).toBe(`${result} ${result}`); - }); - }); -}); diff --git a/spec/javascripts/locale/sprintf_spec.js b/spec/javascripts/locale/sprintf_spec.js deleted file mode 100644 index 52e903b819f..00000000000 --- a/spec/javascripts/locale/sprintf_spec.js +++ /dev/null @@ -1,74 +0,0 @@ -import sprintf from '~/locale/sprintf'; - -describe('locale', () => { - describe('sprintf', () => { - it('does not modify string without parameters', () => { - const input = 'No parameters'; - - const output = sprintf(input); - - expect(output).toBe(input); - }); - - it('ignores extraneous parameters', () => { - const input = 'No parameters'; - - const output = sprintf(input, { ignore: 'this' }); - - expect(output).toBe(input); - }); - - it('ignores extraneous placeholders', () => { - const input = 'No %{parameters}'; - - const output = sprintf(input); - - expect(output).toBe(input); - }); - - it('replaces parameters', () => { - const input = '%{name} has %{count} parameters'; - const parameters = { - name: 'this', - count: 2, - }; - - const output = sprintf(input, parameters); - - expect(output).toBe('this has 2 parameters'); - }); - - it('replaces multiple occurrences', () => { - const input = 'to %{verb} or not to %{verb}'; - const parameters = { - verb: 'be', - }; - - const output = sprintf(input, parameters); - - expect(output).toBe('to be or not to be'); - }); - - it('escapes parameters', () => { - const input = 'contains %{userContent}'; - const parameters = { - userContent: '', - }; - - const output = sprintf(input, parameters); - - expect(output).toBe('contains <script>alert("malicious!")</script>'); - }); - - it('does not escape parameters for escapeParameters = false', () => { - const input = 'contains %{safeContent}'; - const parameters = { - safeContent: 'bold attempt', - }; - - const output = sprintf(input, parameters, false); - - expect(output).toBe('contains bold attempt'); - }); - }); -}); diff --git a/spec/javascripts/notebook/lib/highlight_spec.js b/spec/javascripts/notebook/lib/highlight_spec.js deleted file mode 100644 index d71c5718858..00000000000 --- a/spec/javascripts/notebook/lib/highlight_spec.js +++ /dev/null @@ -1,15 +0,0 @@ -import Prism from '~/notebook/lib/highlight'; - -describe('Highlight library', () => { - it('imports python language', () => { - expect(Prism.languages.python).toBeDefined(); - }); - - it('uses custom CSS classes', () => { - const el = document.createElement('div'); - el.innerHTML = Prism.highlight('console.log("a");', Prism.languages.javascript); - - expect(el.querySelector('.s')).not.toBeNull(); - expect(el.querySelector('.nf')).not.toBeNull(); - }); -}); diff --git a/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js b/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js deleted file mode 100644 index 07a366cf339..00000000000 --- a/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; - -const localVue = createLocalVue(); - -describe('ReplyPlaceholder', () => { - let wrapper; - - beforeEach(() => { - wrapper = shallowMount(ReplyPlaceholder, { - localVue, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('emits onClick even on button click', () => { - const button = wrapper.find({ ref: 'button' }); - - button.trigger('click'); - - expect(wrapper.emitted()).toEqual({ - onClick: [[]], - }); - }); - - it('should render reply button', () => { - const button = wrapper.find({ ref: 'button' }); - - expect(button.text()).toEqual('Reply...'); - }); -}); diff --git a/spec/javascripts/notes/components/discussion_resolve_button_spec.js b/spec/javascripts/notes/components/discussion_resolve_button_spec.js deleted file mode 100644 index 5024f40ec5d..00000000000 --- a/spec/javascripts/notes/components/discussion_resolve_button_spec.js +++ /dev/null @@ -1,74 +0,0 @@ -import resolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; - -const buttonTitle = 'Resolve discussion'; - -describe('resolveDiscussionButton', () => { - let wrapper; - let localVue; - - const factory = options => { - localVue = createLocalVue(); - wrapper = shallowMount(resolveDiscussionButton, { - localVue, - ...options, - }); - }; - - beforeEach(() => { - factory({ - propsData: { - isResolving: false, - buttonTitle, - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should emit a onClick event on button click', () => { - const button = wrapper.find({ ref: 'button' }); - - button.trigger('click'); - - expect(wrapper.emitted()).toEqual({ - onClick: [[]], - }); - }); - - it('should contain the provided button title', () => { - const button = wrapper.find({ ref: 'button' }); - - expect(button.text()).toContain(buttonTitle); - }); - - it('should show a loading spinner while resolving', () => { - factory({ - propsData: { - isResolving: true, - buttonTitle, - }, - }); - - const button = wrapper.find({ ref: 'isResolvingIcon' }); - - expect(button.exists()).toEqual(true); - }); - - it('should only show a loading spinner while resolving', () => { - factory({ - propsData: { - isResolving: false, - buttonTitle, - }, - }); - - const button = wrapper.find({ ref: 'isResolvingIcon' }); - - localVue.nextTick(() => { - expect(button.exists()).toEqual(false); - }); - }); -}); diff --git a/spec/javascripts/notes/components/note_attachment_spec.js b/spec/javascripts/notes/components/note_attachment_spec.js deleted file mode 100644 index b14a518b622..00000000000 --- a/spec/javascripts/notes/components/note_attachment_spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import Vue from 'vue'; -import noteAttachment from '~/notes/components/note_attachment.vue'; - -describe('issue note attachment', () => { - it('should render properly', () => { - const props = { - attachment: { - filename: 'dk.png', - image: true, - url: '/dk.png', - }, - }; - - const Component = Vue.extend(noteAttachment); - const vm = new Component({ - propsData: props, - }).$mount(); - - expect(vm.$el.classList.contains('note-attachment')).toBeTruthy(); - expect(vm.$el.querySelector('img').src).toContain(props.attachment.url); - expect(vm.$el.querySelector('a').href).toContain(props.attachment.url); - }); -}); diff --git a/spec/javascripts/notes/components/note_edited_text_spec.js b/spec/javascripts/notes/components/note_edited_text_spec.js deleted file mode 100644 index e4c8d954d50..00000000000 --- a/spec/javascripts/notes/components/note_edited_text_spec.js +++ /dev/null @@ -1,47 +0,0 @@ -import Vue from 'vue'; -import noteEditedText from '~/notes/components/note_edited_text.vue'; - -describe('note_edited_text', () => { - let vm; - let props; - - beforeEach(() => { - const Component = Vue.extend(noteEditedText); - props = { - actionText: 'Edited', - className: 'foo-bar', - editedAt: '2017-08-04T09:52:31.062Z', - editedBy: { - avatar_url: 'path', - id: 1, - name: 'Root', - path: '/root', - state: 'active', - username: 'root', - }, - }; - - vm = new Component({ - propsData: props, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render block with provided className', () => { - expect(vm.$el.className).toEqual(props.className); - }); - - it('should render provided actionText', () => { - expect(vm.$el.textContent).toContain(props.actionText); - }); - - it('should render provided user information', () => { - const authorLink = vm.$el.querySelector('.js-user-link'); - - expect(authorLink.getAttribute('href')).toEqual(props.editedBy.path); - expect(authorLink.textContent.trim()).toEqual(props.editedBy.name); - }); -}); diff --git a/spec/javascripts/performance_bar/services/performance_bar_service_spec.js b/spec/javascripts/performance_bar/services/performance_bar_service_spec.js deleted file mode 100644 index cfec4b779e4..00000000000 --- a/spec/javascripts/performance_bar/services/performance_bar_service_spec.js +++ /dev/null @@ -1,68 +0,0 @@ -import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; - -describe('PerformanceBarService', () => { - describe('callbackParams', () => { - describe('fireCallback', () => { - function fireCallback(response, peekUrl) { - return PerformanceBarService.callbackParams(response, peekUrl)[0]; - } - - it('returns false when the request URL is the peek URL', () => { - expect( - fireCallback({ headers: { 'x-request-id': '123' }, url: '/peek' }, '/peek'), - ).toBeFalsy(); - }); - - it('returns false when there is no request ID', () => { - expect(fireCallback({ headers: {}, url: '/request' }, '/peek')).toBeFalsy(); - }); - - it('returns false when the request is an API request', () => { - expect( - fireCallback({ headers: { 'x-request-id': '123' }, url: '/api/' }, '/peek'), - ).toBeFalsy(); - }); - - it('returns false when the response is from the cache', () => { - expect( - fireCallback( - { headers: { 'x-request-id': '123', 'x-gitlab-from-cache': 'true' }, url: '/request' }, - '/peek', - ), - ).toBeFalsy(); - }); - - it('returns true when all conditions are met', () => { - expect( - fireCallback({ headers: { 'x-request-id': '123' }, url: '/request' }, '/peek'), - ).toBeTruthy(); - }); - }); - - describe('requestId', () => { - function requestId(response, peekUrl) { - return PerformanceBarService.callbackParams(response, peekUrl)[1]; - } - - it('gets the request ID from the headers', () => { - expect(requestId({ headers: { 'x-request-id': '123' } }, '/peek')).toEqual('123'); - }); - }); - - describe('requestUrl', () => { - function requestUrl(response, peekUrl) { - return PerformanceBarService.callbackParams(response, peekUrl)[2]; - } - - it('gets the request URL from the response object', () => { - expect(requestUrl({ headers: {}, url: '/request' }, '/peek')).toEqual('/request'); - }); - - it('gets the request URL from response.config if present', () => { - expect( - requestUrl({ headers: {}, config: { url: '/config-url' }, url: '/request' }, '/peek'), - ).toEqual('/config-url'); - }); - }); - }); -}); diff --git a/spec/javascripts/pipelines/blank_state_spec.js b/spec/javascripts/pipelines/blank_state_spec.js deleted file mode 100644 index 033bd5ccb73..00000000000 --- a/spec/javascripts/pipelines/blank_state_spec.js +++ /dev/null @@ -1,25 +0,0 @@ -import Vue from 'vue'; -import component from '~/pipelines/components/blank_state.vue'; -import mountComponent from '../helpers/vue_mount_component_helper'; - -describe('Pipelines Blank State', () => { - let vm; - let Component; - - beforeEach(() => { - Component = Vue.extend(component); - - vm = mountComponent(Component, { - svgPath: 'foo', - message: 'Blank State', - }); - }); - - it('should render svg', () => { - expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toEqual('foo'); - }); - - it('should render message', () => { - expect(vm.$el.querySelector('h4').textContent.trim()).toEqual('Blank State'); - }); -}); diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js deleted file mode 100644 index f12950b8fce..00000000000 --- a/spec/javascripts/pipelines/empty_state_spec.js +++ /dev/null @@ -1,58 +0,0 @@ -import Vue from 'vue'; -import emptyStateComp from '~/pipelines/components/empty_state.vue'; -import mountComponent from '../helpers/vue_mount_component_helper'; - -describe('Pipelines Empty State', () => { - let component; - let EmptyStateComponent; - - beforeEach(() => { - EmptyStateComponent = Vue.extend(emptyStateComp); - - component = mountComponent(EmptyStateComponent, { - helpPagePath: 'foo', - emptyStateSvgPath: 'foo', - canSetCi: true, - }); - }); - - afterEach(() => { - component.$destroy(); - }); - - it('should render empty state SVG', () => { - expect(component.$el.querySelector('.svg-content svg')).toBeDefined(); - }); - - it('should render empty state information', () => { - expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence'); - - expect( - component.$el - .querySelector('p') - .innerHTML.trim() - .replace(/\n+\s+/m, ' ') - .replace(/\s\s+/g, ' '), - ).toContain('Continuous Integration can help catch bugs by running your tests automatically,'); - - expect( - component.$el - .querySelector('p') - .innerHTML.trim() - .replace(/\n+\s+/m, ' ') - .replace(/\s\s+/g, ' '), - ).toContain( - 'while Continuous Deployment can help you deliver code to your product environment', - ); - }); - - it('should render a link with provided help path', () => { - expect(component.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual( - 'foo', - ); - - expect(component.$el.querySelector('.js-get-started-pipelines').textContent).toContain( - 'Get started with Pipelines', - ); - }); -}); diff --git a/spec/javascripts/pipelines/pipeline_store_spec.js b/spec/javascripts/pipelines/pipeline_store_spec.js deleted file mode 100644 index 1d5754d1f05..00000000000 --- a/spec/javascripts/pipelines/pipeline_store_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import PipelineStore from '~/pipelines/stores/pipeline_store'; - -describe('Pipeline Store', () => { - let store; - - beforeEach(() => { - store = new PipelineStore(); - }); - - it('should set defaults', () => { - expect(store.state.pipeline).toEqual({}); - }); - - describe('storePipeline', () => { - it('should store empty object if none is provided', () => { - store.storePipeline(); - - expect(store.state.pipeline).toEqual({}); - }); - - it('should store received object', () => { - store.storePipeline({ foo: 'bar' }); - - expect(store.state.pipeline).toEqual({ foo: 'bar' }); - }); - }); -}); diff --git a/spec/javascripts/pipelines/pipelines_store_spec.js b/spec/javascripts/pipelines/pipelines_store_spec.js deleted file mode 100644 index ce21f788ed5..00000000000 --- a/spec/javascripts/pipelines/pipelines_store_spec.js +++ /dev/null @@ -1,77 +0,0 @@ -import PipelineStore from '~/pipelines/stores/pipelines_store'; - -describe('Pipelines Store', () => { - let store; - - beforeEach(() => { - store = new PipelineStore(); - }); - - it('should be initialized with an empty state', () => { - expect(store.state.pipelines).toEqual([]); - expect(store.state.count).toEqual({}); - expect(store.state.pageInfo).toEqual({}); - }); - - describe('storePipelines', () => { - it('should use the default parameter if none is provided', () => { - store.storePipelines(); - - expect(store.state.pipelines).toEqual([]); - }); - - it('should store the provided array', () => { - const array = [{ id: 1, status: 'running' }, { id: 2, status: 'success' }]; - store.storePipelines(array); - - expect(store.state.pipelines).toEqual(array); - }); - }); - - describe('storeCount', () => { - it('should use the default parameter if none is provided', () => { - store.storeCount(); - - expect(store.state.count).toEqual({}); - }); - - it('should store the provided count', () => { - const count = { all: 20, finished: 10 }; - store.storeCount(count); - - expect(store.state.count).toEqual(count); - }); - }); - - describe('storePagination', () => { - it('should use the default parameter if none is provided', () => { - store.storePagination(); - - expect(store.state.pageInfo).toEqual({}); - }); - - it('should store pagination information normalized and parsed', () => { - const pagination = { - 'X-nExt-pAge': '2', - 'X-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '2', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', - }; - - const expectedResult = { - perPage: 1, - page: 1, - total: 37, - totalPages: 2, - nextPage: 2, - previousPage: 2, - }; - - store.storePagination(pagination); - - expect(store.state.pageInfo).toEqual(expectedResult); - }); - }); -}); diff --git a/spec/javascripts/registry/getters_spec.js b/spec/javascripts/registry/getters_spec.js deleted file mode 100644 index 839aa718997..00000000000 --- a/spec/javascripts/registry/getters_spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import * as getters from '~/registry/stores/getters'; - -describe('Getters Registry Store', () => { - let state; - - beforeEach(() => { - state = { - isLoading: false, - endpoint: '/root/empty-project/container_registry.json', - repos: [ - { - canDelete: true, - destroyPath: 'bar', - id: '134', - isLoading: false, - list: [], - location: 'foo', - name: 'gitlab-org/omnibus-gitlab/foo', - tagsPath: 'foo', - }, - { - canDelete: true, - destroyPath: 'bar', - id: '123', - isLoading: false, - list: [], - location: 'foo', - name: 'gitlab-org/omnibus-gitlab', - tagsPath: 'foo', - }, - ], - }; - }); - - describe('isLoading', () => { - it('should return the isLoading property', () => { - expect(getters.isLoading(state)).toEqual(state.isLoading); - }); - }); - - describe('repos', () => { - it('should return the repos', () => { - expect(getters.repos(state)).toEqual(state.repos); - }); - }); -}); diff --git a/spec/javascripts/reports/components/report_link_spec.js b/spec/javascripts/reports/components/report_link_spec.js deleted file mode 100644 index f879899e9c5..00000000000 --- a/spec/javascripts/reports/components/report_link_spec.js +++ /dev/null @@ -1,69 +0,0 @@ -import Vue from 'vue'; -import component from '~/reports/components/report_link.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('report link', () => { - let vm; - - const Component = Vue.extend(component); - - afterEach(() => { - vm.$destroy(); - }); - - describe('With url', () => { - it('renders link', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - urlPath: '/Gemfile.lock', - }, - }); - - expect(vm.$el.textContent.trim()).toContain('in'); - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('/Gemfile.lock'); - expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Gemfile.lock'); - }); - }); - - describe('Without url', () => { - it('does not render link', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - }, - }); - - expect(vm.$el.querySelector('a')).toBeNull(); - expect(vm.$el.textContent.trim()).toContain('in'); - expect(vm.$el.textContent.trim()).toContain('Gemfile.lock'); - }); - }); - - describe('with line', () => { - it('renders line number', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - urlPath: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', - line: 22, - }, - }); - - expect(vm.$el.querySelector('a').textContent.trim()).toContain('Gemfile.lock:22'); - }); - }); - - describe('without line', () => { - it('does not render line number', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - urlPath: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', - }, - }); - - expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(':22'); - }); - }); -}); diff --git a/spec/javascripts/reports/store/utils_spec.js b/spec/javascripts/reports/store/utils_spec.js deleted file mode 100644 index 1679d120db2..00000000000 --- a/spec/javascripts/reports/store/utils_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -import * as utils from '~/reports/store/utils'; -import { - STATUS_FAILED, - STATUS_SUCCESS, - ICON_WARNING, - ICON_SUCCESS, - ICON_NOTFOUND, -} from '~/reports/constants'; - -describe('Reports store utils', () => { - describe('summaryTextbuilder', () => { - it('should render text for no changed results in multiple tests', () => { - const name = 'Test summary'; - const data = { total: 10 }; - const result = utils.summaryTextBuilder(name, data); - - expect(result).toBe('Test summary contained no changed test results out of 10 total tests'); - }); - - it('should render text for no changed results in one test', () => { - const name = 'Test summary'; - const data = { total: 1 }; - const result = utils.summaryTextBuilder(name, data); - - expect(result).toBe('Test summary contained no changed test results out of 1 total test'); - }); - - it('should render text for multiple failed results', () => { - const name = 'Test summary'; - const data = { failed: 3, total: 10 }; - const result = utils.summaryTextBuilder(name, data); - - expect(result).toBe('Test summary contained 3 failed test results out of 10 total tests'); - }); - - it('should render text for multiple fixed results', () => { - const name = 'Test summary'; - const data = { resolved: 4, total: 10 }; - const result = utils.summaryTextBuilder(name, data); - - expect(result).toBe('Test summary contained 4 fixed test results out of 10 total tests'); - }); - - it('should render text for multiple fixed, and multiple failed results', () => { - const name = 'Test summary'; - const data = { failed: 3, resolved: 4, total: 10 }; - const result = utils.summaryTextBuilder(name, data); - - expect(result).toBe( - 'Test summary contained 3 failed test results and 4 fixed test results out of 10 total tests', - ); - }); - - it('should render text for a singular fixed, and a singular failed result', () => { - const name = 'Test summary'; - const data = { failed: 1, resolved: 1, total: 10 }; - const result = utils.summaryTextBuilder(name, data); - - expect(result).toBe( - 'Test summary contained 1 failed test result and 1 fixed test result out of 10 total tests', - ); - }); - }); - - describe('reportTextBuilder', () => { - it('should render text for no changed results in multiple tests', () => { - const name = 'Rspec'; - const data = { total: 10 }; - const result = utils.reportTextBuilder(name, data); - - expect(result).toBe('Rspec found no changed test results out of 10 total tests'); - }); - - it('should render text for no changed results in one test', () => { - const name = 'Rspec'; - const data = { total: 1 }; - const result = utils.reportTextBuilder(name, data); - - expect(result).toBe('Rspec found no changed test results out of 1 total test'); - }); - - it('should render text for multiple failed results', () => { - const name = 'Rspec'; - const data = { failed: 3, total: 10 }; - const result = utils.reportTextBuilder(name, data); - - expect(result).toBe('Rspec found 3 failed test results out of 10 total tests'); - }); - - it('should render text for multiple fixed results', () => { - const name = 'Rspec'; - const data = { resolved: 4, total: 10 }; - const result = utils.reportTextBuilder(name, data); - - expect(result).toBe('Rspec found 4 fixed test results out of 10 total tests'); - }); - - it('should render text for multiple fixed, and multiple failed results', () => { - const name = 'Rspec'; - const data = { failed: 3, resolved: 4, total: 10 }; - const result = utils.reportTextBuilder(name, data); - - expect(result).toBe( - 'Rspec found 3 failed test results and 4 fixed test results out of 10 total tests', - ); - }); - - it('should render text for a singular fixed, and a singular failed result', () => { - const name = 'Rspec'; - const data = { failed: 1, resolved: 1, total: 10 }; - const result = utils.reportTextBuilder(name, data); - - expect(result).toBe( - 'Rspec found 1 failed test result and 1 fixed test result out of 10 total tests', - ); - }); - }); - - describe('statusIcon', () => { - describe('with failed status', () => { - it('returns ICON_WARNING', () => { - expect(utils.statusIcon(STATUS_FAILED)).toEqual(ICON_WARNING); - }); - }); - - describe('with success status', () => { - it('returns ICON_SUCCESS', () => { - expect(utils.statusIcon(STATUS_SUCCESS)).toEqual(ICON_SUCCESS); - }); - }); - - describe('without a status', () => { - it('returns ICON_NOTFOUND', () => { - expect(utils.statusIcon()).toEqual(ICON_NOTFOUND); - }); - }); - }); -}); diff --git a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_buttons_spec.js deleted file mode 100644 index 32da9f83112..00000000000 --- a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -import Vue from 'vue'; -import editFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue'; - -describe('Edit Form Buttons', () => { - let vm1; - let vm2; - - beforeEach(() => { - const Component = Vue.extend(editFormButtons); - const toggleForm = () => {}; - const updateConfidentialAttribute = () => {}; - - vm1 = new Component({ - propsData: { - isConfidential: true, - toggleForm, - updateConfidentialAttribute, - }, - }).$mount(); - - vm2 = new Component({ - propsData: { - isConfidential: false, - toggleForm, - updateConfidentialAttribute, - }, - }).$mount(); - }); - - it('renders on or off text based on confidentiality', () => { - expect(vm1.$el.innerHTML.includes('Turn Off')).toBe(true); - - expect(vm2.$el.innerHTML.includes('Turn On')).toBe(true); - }); -}); diff --git a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js deleted file mode 100644 index 369088cb258..00000000000 --- a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -import Vue from 'vue'; -import editForm from '~/sidebar/components/confidential/edit_form.vue'; - -describe('Edit Form Dropdown', () => { - let vm1; - let vm2; - - beforeEach(() => { - const Component = Vue.extend(editForm); - const toggleForm = () => {}; - const updateConfidentialAttribute = () => {}; - - vm1 = new Component({ - propsData: { - isConfidential: true, - toggleForm, - updateConfidentialAttribute, - }, - }).$mount(); - - vm2 = new Component({ - propsData: { - isConfidential: false, - toggleForm, - updateConfidentialAttribute, - }, - }).$mount(); - }); - - it('renders on the appropriate warning text', () => { - expect(vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.')).toBe(true); - - expect(vm2.$el.innerHTML.includes('You are going to turn on the confidentiality.')).toBe(true); - }); -}); diff --git a/spec/javascripts/sidebar/lock/edit_form_spec.js b/spec/javascripts/sidebar/lock/edit_form_spec.js deleted file mode 100644 index ec10a999a40..00000000000 --- a/spec/javascripts/sidebar/lock/edit_form_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import Vue from 'vue'; -import editForm from '~/sidebar/components/lock/edit_form.vue'; - -describe('EditForm', () => { - let vm1; - let vm2; - - beforeEach(() => { - const Component = Vue.extend(editForm); - const toggleForm = () => {}; - const updateLockedAttribute = () => {}; - - vm1 = new Component({ - propsData: { - isLocked: true, - toggleForm, - updateLockedAttribute, - issuableType: 'issue', - }, - }).$mount(); - - vm2 = new Component({ - propsData: { - isLocked: false, - toggleForm, - updateLockedAttribute, - issuableType: 'merge_request', - }, - }).$mount(); - }); - - it('renders on the appropriate warning text', () => { - expect(vm1.$el.innerHTML.includes('Unlock this issue?')).toBe(true); - - expect(vm2.$el.innerHTML.includes('Lock this merge request?')).toBe(true); - }); -}); diff --git a/spec/javascripts/u2f/util_spec.js b/spec/javascripts/u2f/util_spec.js deleted file mode 100644 index 32cd6891384..00000000000 --- a/spec/javascripts/u2f/util_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { canInjectU2fApi } from '~/u2f/util'; - -describe('U2F Utils', () => { - describe('canInjectU2fApi', () => { - it('returns false for Chrome < 41', () => { - const userAgent = - 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.28 Safari/537.36'; - - expect(canInjectU2fApi(userAgent)).toBe(false); - }); - - it('returns true for Chrome >= 41', () => { - const userAgent = - 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'; - - expect(canInjectU2fApi(userAgent)).toBe(true); - }); - - it('returns false for Opera < 40', () => { - const userAgent = - 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36 OPR/32.0.1948.25'; - - expect(canInjectU2fApi(userAgent)).toBe(false); - }); - - it('returns true for Opera >= 40', () => { - const userAgent = - 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 OPR/43.0.2442.991'; - - expect(canInjectU2fApi(userAgent)).toBe(true); - }); - - it('returns false for Safari', () => { - const userAgent = - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4'; - - expect(canInjectU2fApi(userAgent)).toBe(false); - }); - - it('returns false for Chrome on Android', () => { - const userAgent = - 'Mozilla/5.0 (Linux; Android 7.0; VS988 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3145.0 Mobile Safari/537.36'; - - expect(canInjectU2fApi(userAgent)).toBe(false); - }); - - it('returns false for Chrome on iOS', () => { - const userAgent = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1'; - - expect(canInjectU2fApi(userAgent)).toBe(false); - }); - - it('returns false for Safari on iOS', () => { - const userAgent = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A356 Safari/604.1'; - - expect(canInjectU2fApi(userAgent)).toBe(false); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js deleted file mode 100644 index 16c8c939a6f..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js +++ /dev/null @@ -1,51 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import MrWidgetContainer from '~/vue_merge_request_widget/components/mr_widget_container.vue'; - -const BODY_HTML = '
    Hello World
    '; -const FOOTER_HTML = ''; - -describe('MrWidgetContainer', () => { - let wrapper; - - const factory = (options = {}) => { - const localVue = createLocalVue(); - - wrapper = shallowMount(localVue.extend(MrWidgetContainer), { - localVue, - ...options, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('has layout', () => { - factory(); - - expect(wrapper.is('.mr-widget-heading')).toBe(true); - expect(wrapper.contains('.mr-widget-content')).toBe(true); - }); - - it('accepts default slot', () => { - factory({ - slots: { - default: BODY_HTML, - }, - }); - - expect(wrapper.contains('.mr-widget-content .test-body')).toBe(true); - }); - - it('accepts footer slot', () => { - factory({ - slots: { - default: BODY_HTML, - footer: FOOTER_HTML, - }, - }); - - expect(wrapper.contains('.mr-widget-content .test-body')).toBe(true); - expect(wrapper.contains('.test-footer')).toBe(true); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js deleted file mode 100644 index f7c2376eebf..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js +++ /dev/null @@ -1,30 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue'; -import Icon from '~/vue_shared/components/icon.vue'; - -const TEST_ICON = 'commit'; - -describe('MrWidgetIcon', () => { - let wrapper; - - beforeEach(() => { - const localVue = createLocalVue(); - - wrapper = shallowMount(localVue.extend(MrWidgetIcon), { - propsData: { - name: TEST_ICON, - }, - sync: false, - localVue, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders icon and container', () => { - expect(wrapper.is('.circle-icon-container')).toBe(true); - expect(wrapper.find(Icon).props('name')).toEqual(TEST_ICON); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js b/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js deleted file mode 100644 index 994d6255324..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; - -const localVue = createLocalVue(); -const testCommitMessage = 'Test commit message'; -const testLabel = 'Test label'; -const testInputId = 'test-input-id'; - -describe('Commits edit component', () => { - let wrapper; - - const createComponent = (slots = {}) => { - wrapper = shallowMount(localVue.extend(CommitEdit), { - localVue, - sync: false, - propsData: { - value: testCommitMessage, - label: testLabel, - inputId: testInputId, - }, - slots: { - ...slots, - }, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - const findTextarea = () => wrapper.find('.form-control'); - - it('has a correct label', () => { - const labelElement = wrapper.find('.col-form-label'); - - expect(labelElement.text()).toBe(testLabel); - }); - - describe('textarea', () => { - it('has a correct ID', () => { - expect(findTextarea().attributes('id')).toBe(testInputId); - }); - - it('has a correct value', () => { - expect(findTextarea().element.value).toBe(testCommitMessage); - }); - - it('emits an input event and receives changed value', () => { - const changedCommitMessage = 'Changed commit message'; - - findTextarea().element.value = changedCommitMessage; - findTextarea().trigger('input'); - - expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]); - expect(findTextarea().element.value).toBe(changedCommitMessage); - }); - }); - - describe('when slots are present', () => { - beforeEach(() => { - createComponent({ - header: `
    ${testCommitMessage}
    `, - checkbox: ``, - }); - }); - - it('renders header slot correctly', () => { - const headerSlotElement = wrapper.find('.test-header'); - - expect(headerSlotElement.exists()).toBe(true); - expect(headerSlotElement.text()).toBe(testCommitMessage); - }); - - it('renders checkbox slot correctly', () => { - const checkboxSlotElement = wrapper.find('.test-checkbox'); - - expect(checkboxSlotElement.exists()).toBe(true); - expect(checkboxSlotElement.text()).toBe(testLabel); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js deleted file mode 100644 index daf1cc2d98b..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { GlDropdownItem } from '@gitlab/ui'; -import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; - -const localVue = createLocalVue(); -const commits = [ - { - title: 'Commit 1', - short_id: '78d5b7', - message: 'Update test.txt', - }, - { - title: 'Commit 2', - short_id: '34cbe28b', - message: 'Fixed test', - }, - { - title: 'Commit 3', - short_id: 'fa42932a', - message: 'Added changelog', - }, -]; - -describe('Commits message dropdown component', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(localVue.extend(CommitMessageDropdown), { - localVue, - sync: false, - propsData: { - commits, - }, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - const findDropdownElements = () => wrapper.findAll(GlDropdownItem); - const findFirstDropdownElement = () => findDropdownElements().at(0); - - it('should have 3 elements in dropdown list', () => { - expect(findDropdownElements().length).toBe(3); - }); - - it('should have correct message for the first dropdown list element', () => { - expect(findFirstDropdownElement().text()).toBe('78d5b7 Commit 1'); - }); - - it('should emit a commit title on selecting commit', () => { - findFirstDropdownElement().vm.$emit('click'); - - expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js deleted file mode 100644 index 9ee2f88c78d..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js +++ /dev/null @@ -1,136 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; -import Icon from '~/vue_shared/components/icon.vue'; - -const localVue = createLocalVue(); - -describe('Commits header component', () => { - let wrapper; - - const createComponent = props => { - wrapper = shallowMount(localVue.extend(CommitsHeader), { - localVue, - sync: false, - propsData: { - isSquashEnabled: false, - targetBranch: 'master', - commitsCount: 5, - isFastForwardEnabled: false, - ...props, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count'); - const findCommitToggle = () => wrapper.find('.commit-edit-toggle'); - const findIcon = () => wrapper.find(Icon); - const findCommitsCountMessage = () => wrapper.find('.commits-count-message'); - const findTargetBranchMessage = () => wrapper.find('.label-branch'); - const findModifyButton = () => wrapper.find('.modify-message-button'); - - describe('when fast-forward is enabled', () => { - beforeEach(() => { - createComponent({ - isFastForwardEnabled: true, - isSquashEnabled: true, - }); - }); - - it('has commits count message showing 1 commit', () => { - expect(findCommitsCountMessage().text()).toBe('1 commit'); - }); - - it('has button with modify commit message', () => { - expect(findModifyButton().text()).toBe('Modify commit message'); - }); - - it('does not have merge commit part of the message', () => { - expect(findHeaderWrapper().text()).not.toContain('1 merge commit'); - }); - }); - - describe('when collapsed', () => { - it('toggle has aria-label equal to Expand', () => { - createComponent(); - - expect(findCommitToggle().attributes('aria-label')).toBe('Expand'); - }); - - it('has a chevron-right icon', () => { - createComponent(); - wrapper.setData({ expanded: false }); - - expect(findIcon().props('name')).toBe('chevron-right'); - }); - - describe('when squash is disabled', () => { - beforeEach(() => { - createComponent(); - }); - - it('has commits count message showing correct amount of commits', () => { - expect(findCommitsCountMessage().text()).toBe('5 commits'); - }); - - it('has button with modify merge commit message', () => { - expect(findModifyButton().text()).toBe('Modify merge commit'); - }); - }); - - describe('when squash is enabled', () => { - beforeEach(() => { - createComponent({ isSquashEnabled: true }); - }); - - it('has commits count message showing one commit when squash is enabled', () => { - expect(findCommitsCountMessage().text()).toBe('1 commit'); - }); - - it('has button with modify commit messages text', () => { - expect(findModifyButton().text()).toBe('Modify commit messages'); - }); - }); - - it('has correct target branch displayed', () => { - createComponent(); - - expect(findTargetBranchMessage().text()).toBe('master'); - }); - - it('does has merge commit part of the message', () => { - expect(findHeaderWrapper().text()).toContain('1 merge commit'); - }); - }); - - describe('when expanded', () => { - beforeEach(() => { - createComponent(); - wrapper.setData({ expanded: true }); - }); - - it('toggle has aria-label equal to collapse', done => { - wrapper.vm.$nextTick(() => { - expect(findCommitToggle().attributes('aria-label')).toBe('Collapse'); - done(); - }); - }); - - it('has a chevron-down icon', done => { - wrapper.vm.$nextTick(() => { - expect(findIcon().props('name')).toBe('chevron-down'); - done(); - }); - }); - - it('has a collapse text', done => { - wrapper.vm.$nextTick(() => { - expect(findHeaderWrapper().text()).toBe('Collapse'); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js deleted file mode 100644 index b356ea85cad..00000000000 --- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js +++ /dev/null @@ -1,103 +0,0 @@ -import getStateKey from '~/vue_merge_request_widget/stores/get_state_key'; - -describe('getStateKey', () => { - it('should return proper state name', () => { - const context = { - mergeStatus: 'checked', - mergeWhenPipelineSucceeds: false, - canMerge: true, - onlyAllowMergeIfPipelineSucceeds: false, - isPipelineFailed: false, - hasMergeableDiscussionsState: false, - isPipelineBlocked: false, - canBeMerged: false, - }; - const data = { - project_archived: false, - branch_missing: false, - commits_count: 2, - has_conflicts: false, - work_in_progress: false, - }; - const bound = getStateKey.bind(context, data); - - expect(bound()).toEqual(null); - - context.canBeMerged = true; - - expect(bound()).toEqual('readyToMerge'); - - context.canMerge = false; - - expect(bound()).toEqual('notAllowedToMerge'); - - context.mergeWhenPipelineSucceeds = true; - - expect(bound()).toEqual('mergeWhenPipelineSucceeds'); - - context.isSHAMismatch = true; - - expect(bound()).toEqual('shaMismatch'); - - context.isPipelineBlocked = true; - - expect(bound()).toEqual('pipelineBlocked'); - - context.hasMergeableDiscussionsState = true; - - expect(bound()).toEqual('unresolvedDiscussions'); - - context.onlyAllowMergeIfPipelineSucceeds = true; - context.isPipelineFailed = true; - - expect(bound()).toEqual('pipelineFailed'); - - data.work_in_progress = true; - - expect(bound()).toEqual('workInProgress'); - - data.has_conflicts = true; - - expect(bound()).toEqual('conflicts'); - - context.mergeStatus = 'unchecked'; - - expect(bound()).toEqual('checking'); - - data.commits_count = 0; - - expect(bound()).toEqual('nothingToMerge'); - - data.branch_missing = true; - - expect(bound()).toEqual('missingBranch'); - - data.project_archived = true; - - expect(bound()).toEqual('archived'); - }); - - it('returns rebased state key', () => { - const context = { - mergeStatus: 'checked', - mergeWhenPipelineSucceeds: false, - canMerge: true, - onlyAllowMergeIfPipelineSucceeds: true, - isPipelineFailed: true, - hasMergeableDiscussionsState: false, - isPipelineBlocked: false, - canBeMerged: false, - shouldBeRebased: true, - }; - const data = { - project_archived: false, - branch_missing: false, - commits_count: 2, - has_conflicts: false, - work_in_progress: false, - }; - const bound = getStateKey.bind(context, data); - - expect(bound()).toEqual('rebase'); - }); -}); diff --git a/spec/javascripts/vue_shared/components/callout_spec.js b/spec/javascripts/vue_shared/components/callout_spec.js deleted file mode 100644 index 91208dfb31a..00000000000 --- a/spec/javascripts/vue_shared/components/callout_spec.js +++ /dev/null @@ -1,66 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import Callout from '~/vue_shared/components/callout.vue'; - -const TEST_MESSAGE = 'This is a callout message!'; -const TEST_SLOT = ''; - -const localVue = createLocalVue(); - -describe('Callout Component', () => { - let wrapper; - - const factory = options => { - wrapper = shallowMount(localVue.extend(Callout), { - localVue, - ...options, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render the appropriate variant of callout', () => { - factory({ - propsData: { - category: 'info', - message: TEST_MESSAGE, - }, - }); - - expect(wrapper.classes()).toEqual(['bs-callout', 'bs-callout-info']); - - expect(wrapper.element.tagName).toEqual('DIV'); - }); - - it('should render accessibility attributes', () => { - factory({ - propsData: { - message: TEST_MESSAGE, - }, - }); - - expect(wrapper.attributes('role')).toEqual('alert'); - expect(wrapper.attributes('aria-live')).toEqual('assertive'); - }); - - it('should render the provided message', () => { - factory({ - propsData: { - message: TEST_MESSAGE, - }, - }); - - expect(wrapper.element.innerHTML.trim()).toEqual(TEST_MESSAGE); - }); - - it('should render the provided slot', () => { - factory({ - slots: { - default: TEST_SLOT, - }, - }); - - expect(wrapper.element.innerHTML.trim()).toEqual(TEST_SLOT); - }); -}); diff --git a/spec/javascripts/vue_shared/components/code_block_spec.js b/spec/javascripts/vue_shared/components/code_block_spec.js deleted file mode 100644 index 6b91a20ff76..00000000000 --- a/spec/javascripts/vue_shared/components/code_block_spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import Vue from 'vue'; -import component from '~/vue_shared/components/code_block.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Code Block', () => { - const Component = Vue.extend(component); - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - it('renders a code block with the provided code', () => { - const code = - "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in \u003ctop (required)\u003e'"; - - vm = mountComponent(Component, { - code, - }); - - expect(vm.$el.querySelector('code').textContent).toEqual(code); - }); - - it('escapes XSS injections', () => { - const code = 'CCC<img src=x onerror=alert(document.domain)>'; - - vm = mountComponent(Component, { - code, - }); - - expect(vm.$el.querySelector('code').textContent).toEqual(code); - }); -}); diff --git a/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js deleted file mode 100644 index c4358f0d9cb..00000000000 --- a/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import ModeChanged from '~/vue_shared/components/diff_viewer/viewers/mode_changed.vue'; - -describe('Diff viewer mode changed component', () => { - let vm; - - beforeEach(() => { - vm = shallowMount(ModeChanged, { - propsData: { - aMode: '123', - bMode: '321', - }, - }); - }); - - afterEach(() => { - vm.destroy(); - }); - - it('renders aMode & bMode', () => { - expect(vm.text()).toContain('File mode changed from 123 to 321'); - }); -}); diff --git a/spec/javascripts/vue_shared/components/identicon_spec.js b/spec/javascripts/vue_shared/components/identicon_spec.js deleted file mode 100644 index 0b3dbb61c96..00000000000 --- a/spec/javascripts/vue_shared/components/identicon_spec.js +++ /dev/null @@ -1,65 +0,0 @@ -import Vue from 'vue'; -import identiconComponent from '~/vue_shared/components/identicon.vue'; - -const createComponent = sizeClass => { - const Component = Vue.extend(identiconComponent); - - return new Component({ - propsData: { - entityId: 1, - entityName: 'entity-name', - sizeClass, - }, - }).$mount(); -}; - -describe('IdenticonComponent', () => { - describe('computed', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('identiconBackgroundClass', () => { - it('should return bg class based on entityId', () => { - vm.entityId = 4; - - expect(vm.identiconBackgroundClass).toBeDefined(); - expect(vm.identiconBackgroundClass).toBe('bg5'); - }); - }); - - describe('identiconTitle', () => { - it('should return first letter of entity title in uppercase', () => { - vm.entityName = 'dummy-group'; - - expect(vm.identiconTitle).toBeDefined(); - expect(vm.identiconTitle).toBe('D'); - }); - }); - }); - - describe('template', () => { - it('should render identicon', () => { - const vm = createComponent(); - - expect(vm.$el.nodeName).toBe('DIV'); - expect(vm.$el.classList.contains('identicon')).toBeTruthy(); - expect(vm.$el.classList.contains('s40')).toBeTruthy(); - expect(vm.$el.classList.contains('bg2')).toBeTruthy(); - vm.$destroy(); - }); - - it('should render identicon with provided sizing class', () => { - const vm = createComponent('s32'); - - expect(vm.$el.classList.contains('s32')).toBeTruthy(); - vm.$destroy(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js b/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js deleted file mode 100644 index 2388660b0c2..00000000000 --- a/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as domUtils from '~/vue_shared/components/lib/utils/dom_utils'; - -describe('domUtils', () => { - describe('pixeliseValue', () => { - it('should add px to a given Number', () => { - expect(domUtils.pixeliseValue(12)).toEqual('12px'); - }); - - it('should not add px to 0', () => { - expect(domUtils.pixeliseValue(0)).toEqual(''); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/pagination_links_spec.js b/spec/javascripts/vue_shared/components/pagination_links_spec.js deleted file mode 100644 index d0cb3731050..00000000000 --- a/spec/javascripts/vue_shared/components/pagination_links_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import Vue from 'vue'; -import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; -import { s__ } from '~/locale'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Pagination links component', () => { - const paginationLinksComponent = Vue.extend(PaginationLinks); - const change = page => page; - const pageInfo = { - page: 3, - perPage: 5, - total: 30, - }; - const translations = { - firstText: s__('Pagination|« First'), - prevText: s__('Pagination|Prev'), - nextText: s__('Pagination|Next'), - lastText: s__('Pagination|Last »'), - }; - - let paginationLinks; - let glPagination; - let destinationComponent; - - beforeEach(() => { - paginationLinks = mountComponent(paginationLinksComponent, { - change, - pageInfo, - }); - [glPagination] = paginationLinks.$children; - [destinationComponent] = glPagination.$children; - }); - - afterEach(() => { - paginationLinks.$destroy(); - }); - - it('should provide translated text to GitLab UI pagination', () => { - Object.entries(translations).forEach(entry => { - expect(destinationComponent[entry[0]]).toBe(entry[1]); - }); - }); - - it('should pass change to GitLab UI pagination', () => { - expect(Object.is(glPagination.change, change)).toBe(true); - }); - - it('should pass page from pageInfo to GitLab UI pagination', () => { - expect(destinationComponent.value).toBe(pageInfo.page); - }); - - it('should pass per page from pageInfo to GitLab UI pagination', () => { - expect(destinationComponent.perPage).toBe(pageInfo.perPage); - }); - - it('should pass total items from pageInfo to GitLab UI pagination', () => { - expect(destinationComponent.totalRows).toBe(pageInfo.total); - }); -}); diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js deleted file mode 100644 index 536bb57b946..00000000000 --- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import Vue from 'vue'; -import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; -import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; - -describe('Time ago with tooltip component', () => { - let TimeagoTooltip; - let vm; - - beforeEach(() => { - TimeagoTooltip = Vue.extend(timeagoTooltip); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render timeago with a bootstrap tooltip', () => { - vm = new TimeagoTooltip({ - propsData: { - time: '2017-05-08T14:57:39.781Z', - }, - }).$mount(); - - expect(vm.$el.tagName).toEqual('TIME'); - expect(vm.$el.getAttribute('data-original-title')).toEqual( - formatDate('2017-05-08T14:57:39.781Z'), - ); - - const timeago = getTimeago(); - - expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z')); - }); - - it('should render provided html class', () => { - vm = new TimeagoTooltip({ - propsData: { - time: '2017-05-08T14:57:39.781Z', - cssClass: 'foo', - }, - }).$mount(); - - expect(vm.$el.classList.contains('foo')).toEqual(true); - }); -}); diff --git a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js b/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js deleted file mode 100644 index d07f8ba1e65..00000000000 --- a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import mutations from '~/vuex_shared/modules/modal/mutations'; -import * as types from '~/vuex_shared/modules/modal/mutation_types'; - -describe('Vuex ModalModule mutations', () => { - describe(types.SHOW, () => { - it('sets isVisible to true', () => { - const state = { - isVisible: false, - }; - - mutations[types.SHOW](state); - - expect(state).toEqual({ - isVisible: true, - }); - }); - }); - - describe(types.HIDE, () => { - it('sets isVisible to false', () => { - const state = { - isVisible: true, - }; - - mutations[types.HIDE](state); - - expect(state).toEqual({ - isVisible: false, - }); - }); - }); - - describe(types.OPEN, () => { - it('sets data and sets isVisible to true', () => { - const data = { id: 7 }; - const state = { - isVisible: false, - data: null, - }; - - mutations[types.OPEN](state, data); - - expect(state).toEqual({ - isVisible: true, - data, - }); - }); - }); -}); -- cgit v1.2.1 From 0cd7ebf717970f9db9298e49959bbbc78bc17342 Mon Sep 17 00:00:00 2001 From: Logan King Date: Mon, 25 Mar 2019 07:42:08 +0000 Subject: Add error tracking usage counts --- spec/lib/gitlab/usage_data_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index cd9e4d48cd1..549cc5ac057 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -13,6 +13,8 @@ describe Gitlab::UsageData do create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true) create(:service, project: projects[1], type: 'SlackService', active: true) create(:service, project: projects[2], type: 'SlackService', active: true) + create(:project_error_tracking_setting, project: projects[0]) + create(:project_error_tracking_setting, project: projects[1], enabled: false) gcp_cluster = create(:cluster, :provided_by_gcp) create(:cluster, :provided_by_user) @@ -117,6 +119,7 @@ describe Gitlab::UsageData do projects_slack_slash_active projects_prometheus_active projects_with_repositories_enabled + projects_with_error_tracking_enabled pages_domains protected_branches releases @@ -146,6 +149,7 @@ describe Gitlab::UsageData do expect(count_data[:projects_slack_notifications_active]).to eq(2) expect(count_data[:projects_slack_slash_active]).to eq(1) expect(count_data[:projects_with_repositories_enabled]).to eq(2) + expect(count_data[:projects_with_error_tracking_enabled]).to eq(1) expect(count_data[:clusters_enabled]).to eq(7) expect(count_data[:project_clusters_enabled]).to eq(6) -- cgit v1.2.1 From a72241f4a92ab0b202eb3c7e03931857428237b6 Mon Sep 17 00:00:00 2001 From: Natalia Tepluhina Date: Mon, 25 Mar 2019 09:16:41 +0000 Subject: Fixed commit logic to pick a branch - fixed getter spec with correct getter name --- .../javascripts/ide/components/commit_sidebar/radio_group_spec.js | 7 +++++++ spec/javascripts/ide/stores/modules/commit/actions_spec.js | 2 +- spec/javascripts/ide/stores/modules/commit/getters_spec.js | 8 ++++---- 3 files changed, 12 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js index ffc2a4c9ddb..db1988be3e1 100644 --- a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js @@ -76,6 +76,7 @@ describe('IDE commit sidebar radio group', () => { const Component = Vue.extend(radioGroup); store.state.commit.commitAction = '1'; + store.state.commit.newBranchName = 'test-123'; vm = createComponentWithStore(Component, store, { value: '1', @@ -113,6 +114,12 @@ describe('IDE commit sidebar radio group', () => { done(); }); }); + + it('renders newBranchName if present', () => { + const input = vm.$el.querySelector('.form-control'); + + expect(input.value).toBe('test-123'); + }); }); describe('tooltipTitle', () => { diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index 06b8b452319..34d97347438 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -396,7 +396,7 @@ describe('IDE commit module actions', () => { .then(() => { expect(visitUrl).toHaveBeenCalledWith( `webUrl/merge_requests/new?merge_request[source_branch]=${ - store.getters['commit/newBranchName'] + store.getters['commit/placeholderBranchName'] }&merge_request[target_branch]=master`, ); diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js index 3f4bf407a1f..702e78ef773 100644 --- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js @@ -29,11 +29,11 @@ describe('IDE commit module getters', () => { }); }); - describe('newBranchName', () => { + describe('placeholderBranchName', () => { it('includes username, currentBranchId, patch & random number', () => { gon.current_username = 'username'; - const branch = getters.newBranchName(state, null, { + const branch = getters.placeholderBranchName(state, null, { currentBranchId: 'testing', }); @@ -46,7 +46,7 @@ describe('IDE commit module getters', () => { currentBranchId: 'master', }; const localGetters = { - newBranchName: 'newBranchName', + placeholderBranchName: 'newBranchName', }; beforeEach(() => { @@ -71,7 +71,7 @@ describe('IDE commit module getters', () => { expect(getters.branchName(state, localGetters, rootState)).toBe('state-newBranchName'); }); - it('uses getters newBranchName when state newBranchName is empty', () => { + it('uses placeholderBranchName when state newBranchName is empty', () => { Object.assign(state, { newBranchName: '', }); -- cgit v1.2.1 From b7ecf433888b1cdd03b8c53b5a27ab057c625ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Mon, 25 Mar 2019 11:08:20 +0100 Subject: Add cr remarks --- spec/controllers/projects_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 41380e79305..717675d1d30 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -383,7 +383,7 @@ describe ProjectsController do id: project.id, project: params } - end.not_to change {project.namespace} + end.not_to change {project.reload.namespace} end def update_project(**parameters) -- cgit v1.2.1 From 45da7dd306c76dc322ca6d2ceed92046219fd90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 21 Mar 2019 17:26:22 +0100 Subject: Backport 'Update user name upon LDAP sync' from EE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/lib/gitlab/auth/o_auth/user_spec.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index dcbd12fe190..b765c265e69 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -207,6 +207,7 @@ describe Gitlab::Auth::OAuth::User do before do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } + allow(ldap_user).to receive(:name) { 'John Doe' } allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } allow(ldap_user).to receive(:dn) { dn } end @@ -221,6 +222,7 @@ describe Gitlab::Auth::OAuth::User do it "creates a user with dual LDAP and omniauth identities" do expect(gl_user).to be_valid expect(gl_user.username).to eql uid + expect(gl_user.name).to eql 'John Doe' expect(gl_user.email).to eql 'johndoe@example.com' expect(gl_user.identities.length).to be 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } @@ -232,11 +234,13 @@ describe Gitlab::Auth::OAuth::User do ) end - it "has email set as synced" do + it "has name and email set as synced" do + expect(gl_user.user_synced_attributes_metadata.name_synced).to be_truthy expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy end - it "has email set as read-only" do + it "has name and email set as read-only" do + expect(gl_user.read_only_attribute?(:name)).to be_truthy expect(gl_user.read_only_attribute?(:email)).to be_truthy end @@ -246,7 +250,7 @@ describe Gitlab::Auth::OAuth::User do end context "and LDAP user has an account already" do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } + let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } it "adds the omniauth identity to the LDAP account" do allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) @@ -254,6 +258,7 @@ describe Gitlab::Auth::OAuth::User do expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' + expect(gl_user.name).to eql 'John Doe' expect(gl_user.email).to eql 'john@example.com' expect(gl_user.identities.length).to be 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } -- cgit v1.2.1 From c5f9b2be826c05e5f06d424f5c110976ad0b68c4 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 22 Mar 2019 15:51:14 +0000 Subject: Remove N+1 queries from users autocomplete Both of these were related to groups: 1. We need to preload routes (using the `with_route` scope) if we're going to get the group's path. 2. We were counting each group's members separately. They're in the same commit because the spec for N+1 detection wouldn't pass with only one of these fixes. --- spec/models/members/group_member_spec.rb | 16 +++++++ .../services/projects/participants_service_spec.rb | 53 ++++++++++++++++------ 2 files changed, 56 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index a3451c67bd8..bc937368cff 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -1,6 +1,22 @@ require 'spec_helper' describe GroupMember do + describe '.count_users_by_group_id' do + it 'counts users by group ID' do + user_1 = create(:user) + user_2 = create(:user) + group_1 = create(:group) + group_2 = create(:group) + + group_1.add_owner(user_1) + group_1.add_owner(user_2) + group_2.add_owner(user_1) + + expect(described_class.count_users_by_group_id).to eq(group_1.id => 2, + group_2.id => 1) + end + end + describe '.access_level_roles' do it 'returns Gitlab::Access.options_with_owner' do expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner) diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index 6040f9100f8..4b6d0c51363 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -2,29 +2,56 @@ require 'spec_helper' describe Projects::ParticipantsService do describe '#groups' do + set(:user) { create(:user) } + set(:project) { create(:project, :public) } + let(:service) { described_class.new(project, user) } + + it 'avoids N+1 queries' do + group_1 = create(:group) + group_1.add_owner(user) + + service.groups # Run general application warmup queries + control_count = ActiveRecord::QueryRecorder.new { service.groups }.count + + group_2 = create(:group) + group_2.add_owner(user) + + expect { service.groups }.not_to exceed_query_limit(control_count) + end + + it 'returns correct user counts for groups' do + group_1 = create(:group) + group_1.add_owner(user) + group_1.add_owner(create(:user)) + + group_2 = create(:group) + group_2.add_owner(user) + create(:group_member, :access_request, group: group_2, user: create(:user)) + + expect(service.groups).to contain_exactly( + a_hash_including(name: group_1.full_name, count: 2), + a_hash_including(name: group_2.full_name, count: 1) + ) + end + describe 'avatar_url' do - let(:project) { create(:project, :public) } let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) } - let(:user) { create(:user) } - let!(:group_member) { create(:group_member, group: group, user: user) } - it 'should return an url for the avatar' do - participants = described_class.new(project, user) - groups = participants.groups + before do + group.add_owner(user) + end - expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") + it 'should return an url for the avatar' do + expect(service.groups.size).to eq 1 + expect(service.groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") end it 'should return an url for the avatar with relative url' do stub_config_setting(relative_url_root: '/gitlab') stub_config_setting(url: Settings.send(:build_gitlab_url)) - participants = described_class.new(project, user) - groups = participants.groups - - expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png") + expect(service.groups.size).to eq 1 + expect(service.groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png") end end end -- cgit v1.2.1 From 1c0588e7173dc9a545434f4bad41172f9fe145e5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 25 Mar 2019 12:47:46 +0000 Subject: Resolve CE/EE differences in MergeRequestPresenter spec --- spec/presenters/merge_request_presenter_spec.rb | 46 +++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index fd03f594c35..4a0f91c4c7a 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe MergeRequestPresenter do - let(:resource) { create :merge_request, source_project: project } - let(:project) { create :project } + let(:resource) { create(:merge_request, source_project: project) } + let(:project) { create(:project) } let(:user) { create(:user) } describe '#ci_status' do @@ -523,4 +523,46 @@ describe MergeRequestPresenter do end end end + + describe '#can_push_to_source_branch' do + before do + allow(resource).to receive(:source_branch_exists?) { source_branch_exists } + + allow_any_instance_of(Gitlab::UserAccess::RequestCacheExtension) + .to receive(:can_push_to_branch?) + .with(resource.source_branch) + .and_return(can_push_to_branch) + end + + subject do + described_class.new(resource, current_user: user).can_push_to_source_branch? + end + + context 'when source branch exists AND user can push to source branch' do + let(:source_branch_exists) { true } + let(:can_push_to_branch) { true } + + it 'returns true' do + is_expected.to eq(true) + end + end + + context 'when source branch does not exists' do + let(:source_branch_exists) { false } + let(:can_push_to_branch) { true } + + it 'returns false' do + is_expected.to eq(false) + end + end + + context 'when user cannot push to source branch' do + let(:source_branch_exists) { true } + let(:can_push_to_branch) { false } + + it 'returns false' do + is_expected.to eq(false) + end + end + end end -- cgit v1.2.1 From 558e946263a58075d396a89427f44843e5fb789f Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 22 Mar 2019 16:44:14 +0000 Subject: Rename GitTagPushService -> Git::TagPushService --- spec/services/git/tag_push_service_spec.rb | 196 +++++++++++++++++++++++++++++ spec/services/git_tag_push_service_spec.rb | 196 ----------------------------- spec/workers/post_receive_spec.rb | 12 +- 3 files changed, 202 insertions(+), 202 deletions(-) create mode 100644 spec/services/git/tag_push_service_spec.rb delete mode 100644 spec/services/git_tag_push_service_spec.rb (limited to 'spec') diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb new file mode 100644 index 00000000000..e151db5827f --- /dev/null +++ b/spec/services/git/tag_push_service_spec.rb @@ -0,0 +1,196 @@ +require 'spec_helper' + +describe Git::TagPushService do + include RepoHelpers + include GitHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:service) { described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) } + + let(:oldrev) { Gitlab::Git::BLANK_SHA } + let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0 + let(:ref) { 'refs/tags/v1.1.0' } + + describe "Push tags" do + subject do + service.execute + service + end + + it 'flushes general cached data' do + expect(project.repository).to receive(:before_push_tag) + + subject + end + + it 'flushes the tags cache' do + expect(project.repository).to receive(:expire_tags_cache) + + subject + end + end + + describe "Pipelines" do + subject { service.execute } + + before do + stub_ci_pipeline_to_return_yaml_file + project.add_developer(user) + end + + it "creates a new pipeline" do + expect { subject }.to change { Ci::Pipeline.count } + expect(Ci::Pipeline.last).to be_push + end + end + + describe "Git Tag Push Data" do + subject { @push_data } + let(:tag) { project.repository.find_tag(tag_name) } + let(:commit) { tag.dereferenced_target } + + context 'annotated tag' do + let(:tag_name) { Gitlab::Git.ref_name(ref) } + + before do + service.execute + @push_data = service.push_data + end + + it { is_expected.to include(object_kind: 'tag_push') } + it { is_expected.to include(ref: ref) } + it { is_expected.to include(before: oldrev) } + it { is_expected.to include(after: newrev) } + it { is_expected.to include(message: tag.message) } + it { is_expected.to include(user_id: user.id) } + it { is_expected.to include(user_name: user.name) } + it { is_expected.to include(project_id: project.id) } + + context "with repository data" do + subject { @push_data[:repository] } + + it { is_expected.to include(name: project.name) } + it { is_expected.to include(url: project.url_to_repo) } + it { is_expected.to include(description: project.description) } + it { is_expected.to include(homepage: project.web_url) } + end + + context "with commits" do + subject { @push_data[:commits] } + + it { is_expected.to be_an(Array) } + it 'has 1 element' do + expect(subject.size).to eq(1) + end + + context "the commit" do + subject { @push_data[:commits].first } + + it { is_expected.to include(id: commit.id) } + it { is_expected.to include(message: commit.safe_message) } + it { is_expected.to include(timestamp: commit.date.xmlschema) } + it do + is_expected.to include( + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + commit.id + ].join('/') + ) + end + + context "with a author" do + subject { @push_data[:commits].first[:author] } + + it { is_expected.to include(name: commit.author_name) } + it { is_expected.to include(email: commit.author_email) } + end + end + end + end + + context 'lightweight tag' do + let(:tag_name) { 'light-tag' } + let(:newrev) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } + let(:ref) { "refs/tags/light-tag" } + + before do + # Create the lightweight tag + rugged_repo(project.repository).tags.create(tag_name, newrev) + + # Clear tag list cache + project.repository.expire_tags_cache + + service.execute + @push_data = service.push_data + end + + it { is_expected.to include(object_kind: 'tag_push') } + it { is_expected.to include(ref: ref) } + it { is_expected.to include(before: oldrev) } + it { is_expected.to include(after: newrev) } + it { is_expected.to include(message: tag.message) } + it { is_expected.to include(user_id: user.id) } + it { is_expected.to include(user_name: user.name) } + it { is_expected.to include(project_id: project.id) } + + context "with repository data" do + subject { @push_data[:repository] } + + it { is_expected.to include(name: project.name) } + it { is_expected.to include(url: project.url_to_repo) } + it { is_expected.to include(description: project.description) } + it { is_expected.to include(homepage: project.web_url) } + end + + context "with commits" do + subject { @push_data[:commits] } + + it { is_expected.to be_an(Array) } + it 'has 1 element' do + expect(subject.size).to eq(1) + end + + context "the commit" do + subject { @push_data[:commits].first } + + it { is_expected.to include(id: commit.id) } + it { is_expected.to include(message: commit.safe_message) } + it { is_expected.to include(timestamp: commit.date.xmlschema) } + it do + is_expected.to include( + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + commit.id + ].join('/') + ) + end + + context "with a author" do + subject { @push_data[:commits].first[:author] } + + it { is_expected.to include(name: commit.author_name) } + it { is_expected.to include(email: commit.author_email) } + end + end + end + end + end + + describe "Webhooks" do + context "execute webhooks" do + let(:service) { described_class.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') } + + it "when pushing tags" do + expect(project).to receive(:execute_hooks) + service.execute + end + end + end +end diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb deleted file mode 100644 index 2699f6e7bcd..00000000000 --- a/spec/services/git_tag_push_service_spec.rb +++ /dev/null @@ -1,196 +0,0 @@ -require 'spec_helper' - -describe GitTagPushService do - include RepoHelpers - include GitHelpers - - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:service) { described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) } - - let(:oldrev) { Gitlab::Git::BLANK_SHA } - let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0 - let(:ref) { 'refs/tags/v1.1.0' } - - describe "Push tags" do - subject do - service.execute - service - end - - it 'flushes general cached data' do - expect(project.repository).to receive(:before_push_tag) - - subject - end - - it 'flushes the tags cache' do - expect(project.repository).to receive(:expire_tags_cache) - - subject - end - end - - describe "Pipelines" do - subject { service.execute } - - before do - stub_ci_pipeline_to_return_yaml_file - project.add_developer(user) - end - - it "creates a new pipeline" do - expect { subject }.to change { Ci::Pipeline.count } - expect(Ci::Pipeline.last).to be_push - end - end - - describe "Git Tag Push Data" do - subject { @push_data } - let(:tag) { project.repository.find_tag(tag_name) } - let(:commit) { tag.dereferenced_target } - - context 'annotated tag' do - let(:tag_name) { Gitlab::Git.ref_name(ref) } - - before do - service.execute - @push_data = service.push_data - end - - it { is_expected.to include(object_kind: 'tag_push') } - it { is_expected.to include(ref: ref) } - it { is_expected.to include(before: oldrev) } - it { is_expected.to include(after: newrev) } - it { is_expected.to include(message: tag.message) } - it { is_expected.to include(user_id: user.id) } - it { is_expected.to include(user_name: user.name) } - it { is_expected.to include(project_id: project.id) } - - context "with repository data" do - subject { @push_data[:repository] } - - it { is_expected.to include(name: project.name) } - it { is_expected.to include(url: project.url_to_repo) } - it { is_expected.to include(description: project.description) } - it { is_expected.to include(homepage: project.web_url) } - end - - context "with commits" do - subject { @push_data[:commits] } - - it { is_expected.to be_an(Array) } - it 'has 1 element' do - expect(subject.size).to eq(1) - end - - context "the commit" do - subject { @push_data[:commits].first } - - it { is_expected.to include(id: commit.id) } - it { is_expected.to include(message: commit.safe_message) } - it { is_expected.to include(timestamp: commit.date.xmlschema) } - it do - is_expected.to include( - url: [ - Gitlab.config.gitlab.url, - project.namespace.to_param, - project.to_param, - 'commit', - commit.id - ].join('/') - ) - end - - context "with a author" do - subject { @push_data[:commits].first[:author] } - - it { is_expected.to include(name: commit.author_name) } - it { is_expected.to include(email: commit.author_email) } - end - end - end - end - - context 'lightweight tag' do - let(:tag_name) { 'light-tag' } - let(:newrev) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } - let(:ref) { "refs/tags/light-tag" } - - before do - # Create the lightweight tag - rugged_repo(project.repository).tags.create(tag_name, newrev) - - # Clear tag list cache - project.repository.expire_tags_cache - - service.execute - @push_data = service.push_data - end - - it { is_expected.to include(object_kind: 'tag_push') } - it { is_expected.to include(ref: ref) } - it { is_expected.to include(before: oldrev) } - it { is_expected.to include(after: newrev) } - it { is_expected.to include(message: tag.message) } - it { is_expected.to include(user_id: user.id) } - it { is_expected.to include(user_name: user.name) } - it { is_expected.to include(project_id: project.id) } - - context "with repository data" do - subject { @push_data[:repository] } - - it { is_expected.to include(name: project.name) } - it { is_expected.to include(url: project.url_to_repo) } - it { is_expected.to include(description: project.description) } - it { is_expected.to include(homepage: project.web_url) } - end - - context "with commits" do - subject { @push_data[:commits] } - - it { is_expected.to be_an(Array) } - it 'has 1 element' do - expect(subject.size).to eq(1) - end - - context "the commit" do - subject { @push_data[:commits].first } - - it { is_expected.to include(id: commit.id) } - it { is_expected.to include(message: commit.safe_message) } - it { is_expected.to include(timestamp: commit.date.xmlschema) } - it do - is_expected.to include( - url: [ - Gitlab.config.gitlab.url, - project.namespace.to_param, - project.to_param, - 'commit', - commit.id - ].join('/') - ) - end - - context "with a author" do - subject { @push_data[:commits].first[:author] } - - it { is_expected.to include(name: commit.author_name) } - it { is_expected.to include(email: commit.author_email) } - end - end - end - end - end - - describe "Webhooks" do - context "execute webhooks" do - let(:service) { described_class.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') } - - it "when pushing tags" do - expect(project).to receive(:execute_hooks) - service.execute - end - end - end -end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index caae46a3175..d2881f99a84 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -34,7 +34,7 @@ describe PostReceive do context 'empty changes' do it "does not call any PushService but runs after project hooks" do expect(GitPushService).not_to receive(:new) - expect(GitTagPushService).not_to receive(:new) + expect(Git::TagPushService).not_to receive(:new) expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) } described_class.new.perform(gl_repository, key_id, "") @@ -46,7 +46,7 @@ describe PostReceive do it 'returns false' do expect(GitPushService).not_to receive(:new) - expect(GitTagPushService).not_to receive(:new) + expect(Git::TagPushService).not_to receive(:new) expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false end @@ -62,7 +62,7 @@ describe PostReceive do it "calls GitPushService" do expect_any_instance_of(GitPushService).to receive(:execute).and_return(true) - expect_any_instance_of(GitTagPushService).not_to receive(:execute) + expect_any_instance_of(Git::TagPushService).not_to receive(:execute) described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -70,9 +70,9 @@ describe PostReceive do context "tags" do let(:changes) { "123456 789012 refs/tags/tag" } - it "calls GitTagPushService" do + it "calls Git::TagPushService" do expect_any_instance_of(GitPushService).not_to receive(:execute) - expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true) + expect_any_instance_of(Git::TagPushService).to receive(:execute).and_return(true) described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -82,7 +82,7 @@ describe PostReceive do it "does not call any of the services" do expect_any_instance_of(GitPushService).not_to receive(:execute) - expect_any_instance_of(GitTagPushService).not_to receive(:execute) + expect_any_instance_of(Git::TagPushService).not_to receive(:execute) described_class.new.perform(gl_repository, key_id, base64_changes) end end -- cgit v1.2.1 From 83fc1464ddbbce004ef310653a7caec441cf0200 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 22 Mar 2019 17:01:48 +0000 Subject: Rename GitPushService -> Git::BranchPushService --- .../projects/environments/environment_spec.rb | 2 +- spec/services/git/branch_push_service_spec.rb | 837 +++++++++++++++++++++ spec/services/git_push_service_spec.rb | 837 --------------------- spec/support/helpers/cycle_analytics_helpers.rb | 2 +- spec/workers/post_receive_spec.rb | 14 +- 5 files changed, 846 insertions(+), 846 deletions(-) create mode 100644 spec/services/git/branch_push_service_spec.rb delete mode 100644 spec/services/git_push_service_spec.rb (limited to 'spec') diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 3090f1a2131..fe71cb7661a 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -319,7 +319,7 @@ describe 'Environment' do yield - GitPushService.new(project, user, params).execute + Git::BranchPushService.new(project, user, params).execute end end diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb new file mode 100644 index 00000000000..d0e2169b4a6 --- /dev/null +++ b/spec/services/git/branch_push_service_spec.rb @@ -0,0 +1,837 @@ +require 'spec_helper' + +describe Git::BranchPushService, services: true do + include RepoHelpers + + set(:user) { create(:user) } + set(:project) { create(:project, :repository) } + let(:blankrev) { Gitlab::Git::BLANK_SHA } + let(:oldrev) { sample_commit.parent_id } + let(:newrev) { sample_commit.id } + let(:ref) { 'refs/heads/master' } + + before do + project.add_maintainer(user) + end + + describe 'with remote mirrors' do + let(:project) { create(:project, :repository, :remote_mirror) } + + subject do + described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) + end + + context 'when remote mirror feature is enabled' do + it 'fails stuck remote mirrors' do + allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) + expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) + + subject.execute + end + + it 'updates remote mirrors' do + expect(project).to receive(:update_remote_mirrors) + + subject.execute + end + end + + context 'when remote mirror feature is disabled' do + before do + stub_application_setting(mirror_available: false) + end + + context 'with remote mirrors global setting overridden' do + before do + project.remote_mirror_available_overridden = true + end + + it 'fails stuck remote mirrors' do + allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) + expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) + + subject.execute + end + + it 'updates remote mirrors' do + expect(project).to receive(:update_remote_mirrors) + + subject.execute + end + end + + context 'without remote mirrors global setting overridden' do + before do + project.remote_mirror_available_overridden = false + end + + it 'does not fails stuck remote mirrors' do + expect(project).not_to receive(:mark_stuck_remote_mirrors_as_failed!) + + subject.execute + end + + it 'does not updates remote mirrors' do + expect(project).not_to receive(:update_remote_mirrors) + + subject.execute + end + end + end + end + + describe 'Push branches' do + subject do + execute_service(project, user, oldrev, newrev, ref) + end + + context 'new branch' do + let(:oldrev) { blankrev } + + it { is_expected.to be_truthy } + + it 'calls the after_push_commit hook' do + expect(project.repository).to receive(:after_push_commit).with('master') + + subject + end + + it 'calls the after_create_branch hook' do + expect(project.repository).to receive(:after_create_branch) + + subject + end + end + + context 'existing branch' do + it { is_expected.to be_truthy } + + it 'calls the after_push_commit hook' do + expect(project.repository).to receive(:after_push_commit).with('master') + + subject + end + end + + context 'rm branch' do + let(:newrev) { blankrev } + + it { is_expected.to be_truthy } + + it 'calls the after_push_commit hook' do + expect(project.repository).to receive(:after_push_commit).with('master') + + subject + end + + it 'calls the after_remove_branch hook' do + expect(project.repository).to receive(:after_remove_branch) + + subject + end + end + end + + describe "Git Push Data" do + let(:commit) { project.commit(newrev) } + + subject { push_data_from_service(project, user, oldrev, newrev, ref) } + + it { is_expected.to include(object_kind: 'push') } + it { is_expected.to include(before: oldrev) } + it { is_expected.to include(after: newrev) } + it { is_expected.to include(ref: ref) } + it { is_expected.to include(user_id: user.id) } + it { is_expected.to include(user_name: user.name) } + it { is_expected.to include(project_id: project.id) } + + context "with repository data" do + subject { push_data_from_service(project, user, oldrev, newrev, ref)[:repository] } + + it { is_expected.to include(name: project.name) } + it { is_expected.to include(url: project.url_to_repo) } + it { is_expected.to include(description: project.description) } + it { is_expected.to include(homepage: project.web_url) } + end + + context "with commits" do + subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits] } + + it { is_expected.to be_an(Array) } + it 'has 1 element' do + expect(subject.size).to eq(1) + end + + context "the commit" do + subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first } + + it { is_expected.to include(id: commit.id) } + it { is_expected.to include(message: commit.safe_message) } + it { expect(subject[:timestamp].in_time_zone).to eq(commit.date.in_time_zone) } + it do + is_expected.to include( + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + commit.id + ].join('/') + ) + end + + context "with a author" do + subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first[:author] } + + it { is_expected.to include(name: commit.author_name) } + it { is_expected.to include(email: commit.author_email) } + end + end + end + end + + describe "Pipelines" do + subject { execute_service(project, user, oldrev, newrev, ref) } + + before do + stub_ci_pipeline_to_return_yaml_file + end + + it "creates a new pipeline" do + expect { subject }.to change { Ci::Pipeline.count } + expect(Ci::Pipeline.last).to be_push + end + end + + describe "Push Event" do + context "with an existing branch" do + let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) } + let(:event) { Event.find_by_action(Event::PUSHED) } + + it 'generates a push event with one commit' do + expect(event).to be_an_instance_of(PushEvent) + expect(event.project).to eq(project) + expect(event.action).to eq(Event::PUSHED) + expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) + expect(event.push_event_payload.commit_from).to eq(oldrev) + expect(event.push_event_payload.commit_to).to eq(newrev) + expect(event.push_event_payload.ref).to eq('master') + expect(event.push_event_payload.commit_count).to eq(1) + end + end + + context "with a new branch" do + let!(:new_branch_data) { push_data_from_service(project, user, Gitlab::Git::BLANK_SHA, newrev, ref) } + let(:event) { Event.find_by_action(Event::PUSHED) } + + it 'generates a push event with more than one commit' do + expect(event).to be_an_instance_of(PushEvent) + expect(event.project).to eq(project) + expect(event.action).to eq(Event::PUSHED) + expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) + expect(event.push_event_payload.commit_from).to be_nil + expect(event.push_event_payload.commit_to).to eq(newrev) + expect(event.push_event_payload.ref).to eq('master') + expect(event.push_event_payload.commit_count).to be > 1 + end + end + + context "Updates merge requests" do + it "when pushing a new branch for the first time" do + expect(UpdateMergeRequestsWorker).to receive(:perform_async) + .with(project.id, user.id, blankrev, 'newrev', ref) + execute_service(project, user, blankrev, 'newrev', ref ) + end + end + + describe 'system hooks' do + let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) } + let!(:system_hooks_service) { SystemHooksService.new } + + it "sends a system hook after pushing a branch" do + allow(SystemHooksService).to receive(:new).and_return(system_hooks_service) + allow(system_hooks_service).to receive(:execute_hooks) + + execute_service(project, user, oldrev, newrev, ref) + + expect(system_hooks_service).to have_received(:execute_hooks).with(push_data, :push_hooks) + end + end + end + + describe "Updates git attributes" do + context "for default branch" do + it "calls the copy attributes method for the first push to the default branch" do + expect(project.repository).to receive(:copy_gitattributes).with('master') + + execute_service(project, user, blankrev, 'newrev', ref) + end + + it "calls the copy attributes method for changes to the default branch" do + expect(project.repository).to receive(:copy_gitattributes).with(ref) + + execute_service(project, user, 'oldrev', 'newrev', ref) + end + end + + context "for non-default branch" do + before do + # Make sure the "default" branch is different + allow(project).to receive(:default_branch).and_return('not-master') + end + + it "does not call copy attributes method" do + expect(project.repository).not_to receive(:copy_gitattributes) + + execute_service(project, user, oldrev, newrev, ref) + end + end + end + + describe "Webhooks" do + context "execute webhooks" do + it "when pushing a branch for the first time" do + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + execute_service(project, user, blankrev, 'newrev', ref) + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) + expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) + end + + it "when pushing a branch for the first time with default branch protection disabled" do + stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE) + + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + execute_service(project, user, blankrev, 'newrev', ref) + expect(project.protected_branches).to be_empty + end + + it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do + stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) + + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + + execute_service(project, user, blankrev, 'newrev', ref) + + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER]) + expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) + end + + it "when pushing a branch for the first time with an existing branch permission configured" do + stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) + + create(:protected_branch, :no_one_can_push, :developers_can_merge, project: project, name: 'master') + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + expect_any_instance_of(ProtectedBranches::CreateService).not_to receive(:execute) + + execute_service(project, user, blankrev, 'newrev', ref) + + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::NO_ACCESS]) + expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER]) + end + + it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do + stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + execute_service(project, user, blankrev, 'newrev', ref) + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) + expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER]) + end + + it "when pushing new commits to existing branch" do + expect(project).to receive(:execute_hooks) + execute_service(project, user, 'oldrev', 'newrev', ref) + end + end + end + + describe "cross-reference notes" do + let(:issue) { create :issue, project: project } + let(:commit_author) { create :user } + let(:commit) { project.commit } + + before do + project.add_developer(commit_author) + project.add_developer(user) + + allow(commit).to receive_messages( + safe_message: "this commit \n mentions #{issue.to_reference}", + references: [issue], + author_name: commit_author.name, + author_email: commit_author.email + ) + + allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit) + .and_return(commit) + + allow(project.repository).to receive(:commits_between).and_return([commit]) + end + + it "creates a note if a pushed commit mentions an issue" do + expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) + + execute_service(project, user, oldrev, newrev, ref) + end + + it "only creates a cross-reference note if one doesn't already exist" do + SystemNoteService.cross_reference(issue, commit, user) + + expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author) + + execute_service(project, user, oldrev, newrev, ref) + end + + it "defaults to the pushing user if the commit's author is not known" do + allow(commit).to receive_messages( + author_name: 'unknown name', + author_email: 'unknown@email.com' + ) + expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user) + + execute_service(project, user, oldrev, newrev, ref) + end + + it "finds references in the first push to a non-default branch" do + allow(project.repository).to receive(:commits_between).with(blankrev, newrev).and_return([]) + allow(project.repository).to receive(:commits_between).with("master", newrev).and_return([commit]) + + expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) + + execute_service(project, user, blankrev, newrev, 'refs/heads/other') + end + end + + describe "issue metrics" do + let(:issue) { create :issue, project: project } + let(:commit_author) { create :user } + let(:commit) { project.commit } + let(:commit_time) { Time.now } + + before do + project.add_developer(commit_author) + project.add_developer(user) + + allow(commit).to receive_messages( + safe_message: "this commit \n mentions #{issue.to_reference}", + references: [issue], + author_name: commit_author.name, + author_email: commit_author.email, + committed_date: commit_time + ) + + allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit) + .and_return(commit) + + allow(project.repository).to receive(:commits_between).and_return([commit]) + end + + context "while saving the 'first_mentioned_in_commit_at' metric for an issue" do + it 'sets the metric for referenced issues' do + execute_service(project, user, oldrev, newrev, ref) + + expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_like_time(commit_time) + end + + it 'does not set the metric for non-referenced issues' do + non_referenced_issue = create(:issue, project: project) + execute_service(project, user, oldrev, newrev, ref) + + expect(non_referenced_issue.reload.metrics.first_mentioned_in_commit_at).to be_nil + end + end + end + + describe "closing issues from pushed commits containing a closing reference" do + let(:issue) { create :issue, project: project } + let(:other_issue) { create :issue, project: project } + let(:commit_author) { create :user } + let(:closing_commit) { project.commit } + + before do + allow(closing_commit).to receive_messages( + issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, + safe_message: "this is some work.\n\ncloses ##{issue.iid}", + author_name: commit_author.name, + author_email: commit_author.email + ) + + allow(project.repository).to receive(:commits_between) + .and_return([closing_commit]) + + allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit) + .and_return(closing_commit) + + project.add_maintainer(commit_author) + end + + context "to default branches" do + it "closes issues" do + execute_service(project, commit_author, oldrev, newrev, ref) + expect(Issue.find(issue.id)).to be_closed + end + + it "adds a note indicating that the issue is now closed" do + expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit) + execute_service(project, commit_author, oldrev, newrev, ref) + end + + it "doesn't create additional cross-reference notes" do + expect(SystemNoteService).not_to receive(:cross_reference) + execute_service(project, commit_author, oldrev, newrev, ref) + end + end + + context "to non-default branches" do + before do + # Make sure the "default" branch is different + allow(project).to receive(:default_branch).and_return('not-master') + end + + it "creates cross-reference notes" do + expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author) + execute_service(project, user, oldrev, newrev, ref) + end + + it "doesn't close issues" do + execute_service(project, user, oldrev, newrev, ref) + expect(Issue.find(issue.id)).to be_opened + end + end + + context "for jira issue tracker" do + include JiraServiceHelper + + let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } + + before do + # project.create_jira_service doesn't seem to invalidate the cache here + project.has_external_issue_tracker = true + jira_service_settings + stub_jira_urls("JIRA-1") + + allow(closing_commit).to receive_messages({ + issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern), + safe_message: message, + author_name: commit_author.name, + author_email: commit_author.email + }) + + allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) + + allow(project.repository).to receive_messages(commits_between: [closing_commit]) + end + + after do + jira_tracker.destroy! + end + + context "mentioning an issue" do + let(:message) { "this is some work.\n\nrelated to JIRA-1" } + + it "initiates one api call to jira server to mention the issue" do + execute_service(project, user, oldrev, newrev, ref) + + expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with( + body: /mentioned this issue in/ + ).once + end + end + + context "closing an issue" do + let(:message) { "this is some work.\n\ncloses JIRA-1" } + let(:comment_body) do + { + body: "Issue solved with [#{closing_commit.id}|http://#{Gitlab.config.gitlab.host}/#{project.full_path}/commit/#{closing_commit.id}]." + }.to_json + end + + before do + open_issue = JIRA::Resource::Issue.new(jira_tracker.client, attrs: { "id" => "JIRA-1" }) + closed_issue = open_issue.dup + allow(open_issue).to receive(:resolution).and_return(false) + allow(closed_issue).to receive(:resolution).and_return(true) + allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue) + + allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return("JIRA-1") + end + + context "using right markdown" do + it "initiates one api call to jira server to close the issue" do + execute_service(project, commit_author, oldrev, newrev, ref) + + expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once + end + + it "initiates one api call to jira server to comment on the issue" do + execute_service(project, commit_author, oldrev, newrev, ref) + + expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with( + body: comment_body + ).once + end + end + + context "using internal issue reference" do + context 'when internal issues are disabled' do + before do + project.issues_enabled = false + project.save! + end + let(:message) { "this is some work.\n\ncloses #1" } + + it "does not initiates one api call to jira server to close the issue" do + execute_service(project, commit_author, oldrev, newrev, ref) + + expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1')) + end + + it "does not initiates one api call to jira server to comment on the issue" do + execute_service(project, commit_author, oldrev, newrev, ref) + + expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with( + body: comment_body + ).once + end + end + + context 'when internal issues are enabled' do + let(:issue) { create(:issue, project: project) } + let(:message) { "this is some work.\n\ncloses JIRA-1 \n\n closes #{issue.to_reference}" } + + it "initiates one api call to jira server to close the jira issue" do + execute_service(project, commit_author, oldrev, newrev, ref) + + expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once + end + + it "initiates one api call to jira server to comment on the jira issue" do + execute_service(project, commit_author, oldrev, newrev, ref) + + expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with( + body: comment_body + ).once + end + + it "closes the internal issue" do + execute_service(project, commit_author, oldrev, newrev, ref) + expect(issue.reload).to be_closed + end + + it "adds a note indicating that the issue is now closed" do + expect(SystemNoteService).to receive(:change_status) + .with(issue, project, commit_author, "closed", closing_commit) + execute_service(project, commit_author, oldrev, newrev, ref) + end + end + end + end + end + end + + describe "empty project" do + let(:project) { create(:project_empty_repo) } + let(:new_ref) { 'refs/heads/feature' } + + before do + allow(project).to receive(:default_branch).and_return('feature') + expect(project).to receive(:change_head) { 'feature'} + end + + it 'push to first branch updates HEAD' do + execute_service(project, user, blankrev, newrev, new_ref) + end + end + + describe "housekeeping" do + let(:housekeeping) { Projects::HousekeepingService.new(project) } + + before do + # Flush any raw key-value data stored by the housekeeping code. + Gitlab::Redis::Cache.with { |conn| conn.flushall } + Gitlab::Redis::Queues.with { |conn| conn.flushall } + Gitlab::Redis::SharedState.with { |conn| conn.flushall } + + allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping) + end + + after do + Gitlab::Redis::Cache.with { |conn| conn.flushall } + Gitlab::Redis::Queues.with { |conn| conn.flushall } + Gitlab::Redis::SharedState.with { |conn| conn.flushall } + end + + it 'does not perform housekeeping when not needed' do + expect(housekeeping).not_to receive(:execute) + + execute_service(project, user, oldrev, newrev, ref) + end + + context 'when housekeeping is needed' do + before do + allow(housekeeping).to receive(:needed?).and_return(true) + end + + it 'performs housekeeping' do + expect(housekeeping).to receive(:execute) + + execute_service(project, user, oldrev, newrev, ref) + end + + it 'does not raise an exception' do + allow(housekeeping).to receive(:try_obtain_lease).and_return(false) + + execute_service(project, user, oldrev, newrev, ref) + end + end + + it 'increments the push counter' do + expect(housekeeping).to receive(:increment!) + + execute_service(project, user, oldrev, newrev, ref) + end + end + + describe '#update_caches' do + let(:service) do + described_class.new(project, + user, + oldrev: oldrev, + newrev: newrev, + ref: ref) + end + + context 'on the default branch' do + before do + allow(service).to receive(:default_branch?).and_return(true) + end + + it 'flushes the caches of any special files that have been changed' do + commit = double(:commit) + diff = double(:diff, new_path: 'README.md') + + expect(commit).to receive(:raw_deltas) + .and_return([diff]) + + service.push_commits = [commit] + + expect(ProjectCacheWorker).to receive(:perform_async) + .with(project.id, %i(readme), %i(commit_count repository_size)) + + service.update_caches + end + end + + context 'on a non-default branch' do + before do + allow(service).to receive(:default_branch?).and_return(false) + end + + it 'does not flush any conditional caches' do + expect(ProjectCacheWorker).to receive(:perform_async) + .with(project.id, [], %i(commit_count repository_size)) + .and_call_original + + service.update_caches + end + end + end + + describe '#process_commit_messages' do + let(:service) do + described_class.new(project, + user, + oldrev: oldrev, + newrev: newrev, + ref: ref) + end + + it 'only schedules a limited number of commits' do + service.push_commits = Array.new(1000, double(:commit, to_hash: {}, matches_cross_reference_regex?: true)) + + expect(ProcessCommitWorker).to receive(:perform_async).exactly(100).times + + service.process_commit_messages + end + + it "skips commits which don't include cross-references" do + service.push_commits = [double(:commit, to_hash: {}, matches_cross_reference_regex?: false)] + + expect(ProcessCommitWorker).not_to receive(:perform_async) + + service.process_commit_messages + end + end + + describe '#update_signatures' do + let(:service) do + described_class.new( + project, + user, + oldrev: oldrev, + newrev: newrev, + ref: 'refs/heads/master' + ) + end + + context 'when the commit has a signature' do + context 'when the signature is already cached' do + before do + create(:gpg_signature, commit_sha: sample_commit.id) + end + + it 'does not queue a CreateGpgSignatureWorker' do + expect(CreateGpgSignatureWorker).not_to receive(:perform_async) + + execute_service(project, user, oldrev, newrev, ref) + end + end + + context 'when the signature is not yet cached' do + it 'queues a CreateGpgSignatureWorker' do + expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id], project.id) + + execute_service(project, user, oldrev, newrev, ref) + end + + it 'can queue several commits to create the gpg signature' do + allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).and_return([sample_commit.id, another_sample_commit.id]) + + expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id, another_sample_commit.id], project.id) + + execute_service(project, user, oldrev, newrev, ref) + end + end + end + + context 'when the commit does not have a signature' do + before do + allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).with(project.repository, [sample_commit.id]).and_return([]) + end + + it 'does not queue a CreateGpgSignatureWorker' do + expect(CreateGpgSignatureWorker).not_to receive(:perform_async).with(sample_commit.id, project.id) + + execute_service(project, user, oldrev, newrev, ref) + end + end + end + + def execute_service(project, user, oldrev, newrev, ref) + service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) + service.execute + service + end + + def push_data_from_service(project, user, oldrev, newrev, ref) + execute_service(project, user, oldrev, newrev, ref).push_data + end +end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb deleted file mode 100644 index e8fce951155..00000000000 --- a/spec/services/git_push_service_spec.rb +++ /dev/null @@ -1,837 +0,0 @@ -require 'spec_helper' - -describe GitPushService, services: true do - include RepoHelpers - - set(:user) { create(:user) } - set(:project) { create(:project, :repository) } - let(:blankrev) { Gitlab::Git::BLANK_SHA } - let(:oldrev) { sample_commit.parent_id } - let(:newrev) { sample_commit.id } - let(:ref) { 'refs/heads/master' } - - before do - project.add_maintainer(user) - end - - describe 'with remote mirrors' do - let(:project) { create(:project, :repository, :remote_mirror) } - - subject do - described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) - end - - context 'when remote mirror feature is enabled' do - it 'fails stuck remote mirrors' do - allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) - expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) - - subject.execute - end - - it 'updates remote mirrors' do - expect(project).to receive(:update_remote_mirrors) - - subject.execute - end - end - - context 'when remote mirror feature is disabled' do - before do - stub_application_setting(mirror_available: false) - end - - context 'with remote mirrors global setting overridden' do - before do - project.remote_mirror_available_overridden = true - end - - it 'fails stuck remote mirrors' do - allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) - expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) - - subject.execute - end - - it 'updates remote mirrors' do - expect(project).to receive(:update_remote_mirrors) - - subject.execute - end - end - - context 'without remote mirrors global setting overridden' do - before do - project.remote_mirror_available_overridden = false - end - - it 'does not fails stuck remote mirrors' do - expect(project).not_to receive(:mark_stuck_remote_mirrors_as_failed!) - - subject.execute - end - - it 'does not updates remote mirrors' do - expect(project).not_to receive(:update_remote_mirrors) - - subject.execute - end - end - end - end - - describe 'Push branches' do - subject do - execute_service(project, user, oldrev, newrev, ref) - end - - context 'new branch' do - let(:oldrev) { blankrev } - - it { is_expected.to be_truthy } - - it 'calls the after_push_commit hook' do - expect(project.repository).to receive(:after_push_commit).with('master') - - subject - end - - it 'calls the after_create_branch hook' do - expect(project.repository).to receive(:after_create_branch) - - subject - end - end - - context 'existing branch' do - it { is_expected.to be_truthy } - - it 'calls the after_push_commit hook' do - expect(project.repository).to receive(:after_push_commit).with('master') - - subject - end - end - - context 'rm branch' do - let(:newrev) { blankrev } - - it { is_expected.to be_truthy } - - it 'calls the after_push_commit hook' do - expect(project.repository).to receive(:after_push_commit).with('master') - - subject - end - - it 'calls the after_remove_branch hook' do - expect(project.repository).to receive(:after_remove_branch) - - subject - end - end - end - - describe "Git Push Data" do - let(:commit) { project.commit(newrev) } - - subject { push_data_from_service(project, user, oldrev, newrev, ref) } - - it { is_expected.to include(object_kind: 'push') } - it { is_expected.to include(before: oldrev) } - it { is_expected.to include(after: newrev) } - it { is_expected.to include(ref: ref) } - it { is_expected.to include(user_id: user.id) } - it { is_expected.to include(user_name: user.name) } - it { is_expected.to include(project_id: project.id) } - - context "with repository data" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:repository] } - - it { is_expected.to include(name: project.name) } - it { is_expected.to include(url: project.url_to_repo) } - it { is_expected.to include(description: project.description) } - it { is_expected.to include(homepage: project.web_url) } - end - - context "with commits" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits] } - - it { is_expected.to be_an(Array) } - it 'has 1 element' do - expect(subject.size).to eq(1) - end - - context "the commit" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first } - - it { is_expected.to include(id: commit.id) } - it { is_expected.to include(message: commit.safe_message) } - it { expect(subject[:timestamp].in_time_zone).to eq(commit.date.in_time_zone) } - it do - is_expected.to include( - url: [ - Gitlab.config.gitlab.url, - project.namespace.to_param, - project.to_param, - 'commit', - commit.id - ].join('/') - ) - end - - context "with a author" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first[:author] } - - it { is_expected.to include(name: commit.author_name) } - it { is_expected.to include(email: commit.author_email) } - end - end - end - end - - describe "Pipelines" do - subject { execute_service(project, user, oldrev, newrev, ref) } - - before do - stub_ci_pipeline_to_return_yaml_file - end - - it "creates a new pipeline" do - expect { subject }.to change { Ci::Pipeline.count } - expect(Ci::Pipeline.last).to be_push - end - end - - describe "Push Event" do - context "with an existing branch" do - let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) } - let(:event) { Event.find_by_action(Event::PUSHED) } - - it 'generates a push event with one commit' do - expect(event).to be_an_instance_of(PushEvent) - expect(event.project).to eq(project) - expect(event.action).to eq(Event::PUSHED) - expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) - expect(event.push_event_payload.commit_from).to eq(oldrev) - expect(event.push_event_payload.commit_to).to eq(newrev) - expect(event.push_event_payload.ref).to eq('master') - expect(event.push_event_payload.commit_count).to eq(1) - end - end - - context "with a new branch" do - let!(:new_branch_data) { push_data_from_service(project, user, Gitlab::Git::BLANK_SHA, newrev, ref) } - let(:event) { Event.find_by_action(Event::PUSHED) } - - it 'generates a push event with more than one commit' do - expect(event).to be_an_instance_of(PushEvent) - expect(event.project).to eq(project) - expect(event.action).to eq(Event::PUSHED) - expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) - expect(event.push_event_payload.commit_from).to be_nil - expect(event.push_event_payload.commit_to).to eq(newrev) - expect(event.push_event_payload.ref).to eq('master') - expect(event.push_event_payload.commit_count).to be > 1 - end - end - - context "Updates merge requests" do - it "when pushing a new branch for the first time" do - expect(UpdateMergeRequestsWorker).to receive(:perform_async) - .with(project.id, user.id, blankrev, 'newrev', ref) - execute_service(project, user, blankrev, 'newrev', ref ) - end - end - - describe 'system hooks' do - let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) } - let!(:system_hooks_service) { SystemHooksService.new } - - it "sends a system hook after pushing a branch" do - allow(SystemHooksService).to receive(:new).and_return(system_hooks_service) - allow(system_hooks_service).to receive(:execute_hooks) - - execute_service(project, user, oldrev, newrev, ref) - - expect(system_hooks_service).to have_received(:execute_hooks).with(push_data, :push_hooks) - end - end - end - - describe "Updates git attributes" do - context "for default branch" do - it "calls the copy attributes method for the first push to the default branch" do - expect(project.repository).to receive(:copy_gitattributes).with('master') - - execute_service(project, user, blankrev, 'newrev', ref) - end - - it "calls the copy attributes method for changes to the default branch" do - expect(project.repository).to receive(:copy_gitattributes).with(ref) - - execute_service(project, user, 'oldrev', 'newrev', ref) - end - end - - context "for non-default branch" do - before do - # Make sure the "default" branch is different - allow(project).to receive(:default_branch).and_return('not-master') - end - - it "does not call copy attributes method" do - expect(project.repository).not_to receive(:copy_gitattributes) - - execute_service(project, user, oldrev, newrev, ref) - end - end - end - - describe "Webhooks" do - context "execute webhooks" do - it "when pushing a branch for the first time" do - expect(project).to receive(:execute_hooks) - expect(project.default_branch).to eq("master") - execute_service(project, user, blankrev, 'newrev', ref) - expect(project.protected_branches).not_to be_empty - expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) - expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) - end - - it "when pushing a branch for the first time with default branch protection disabled" do - stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE) - - expect(project).to receive(:execute_hooks) - expect(project.default_branch).to eq("master") - execute_service(project, user, blankrev, 'newrev', ref) - expect(project.protected_branches).to be_empty - end - - it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do - stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) - - expect(project).to receive(:execute_hooks) - expect(project.default_branch).to eq("master") - - execute_service(project, user, blankrev, 'newrev', ref) - - expect(project.protected_branches).not_to be_empty - expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER]) - expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) - end - - it "when pushing a branch for the first time with an existing branch permission configured" do - stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) - - create(:protected_branch, :no_one_can_push, :developers_can_merge, project: project, name: 'master') - expect(project).to receive(:execute_hooks) - expect(project.default_branch).to eq("master") - expect_any_instance_of(ProtectedBranches::CreateService).not_to receive(:execute) - - execute_service(project, user, blankrev, 'newrev', ref) - - expect(project.protected_branches).not_to be_empty - expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::NO_ACCESS]) - expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER]) - end - - it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do - stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE) - - expect(project).to receive(:execute_hooks) - expect(project.default_branch).to eq("master") - execute_service(project, user, blankrev, 'newrev', ref) - expect(project.protected_branches).not_to be_empty - expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) - expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER]) - end - - it "when pushing new commits to existing branch" do - expect(project).to receive(:execute_hooks) - execute_service(project, user, 'oldrev', 'newrev', ref) - end - end - end - - describe "cross-reference notes" do - let(:issue) { create :issue, project: project } - let(:commit_author) { create :user } - let(:commit) { project.commit } - - before do - project.add_developer(commit_author) - project.add_developer(user) - - allow(commit).to receive_messages( - safe_message: "this commit \n mentions #{issue.to_reference}", - references: [issue], - author_name: commit_author.name, - author_email: commit_author.email - ) - - allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit) - .and_return(commit) - - allow(project.repository).to receive(:commits_between).and_return([commit]) - end - - it "creates a note if a pushed commit mentions an issue" do - expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) - - execute_service(project, user, oldrev, newrev, ref) - end - - it "only creates a cross-reference note if one doesn't already exist" do - SystemNoteService.cross_reference(issue, commit, user) - - expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author) - - execute_service(project, user, oldrev, newrev, ref) - end - - it "defaults to the pushing user if the commit's author is not known" do - allow(commit).to receive_messages( - author_name: 'unknown name', - author_email: 'unknown@email.com' - ) - expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user) - - execute_service(project, user, oldrev, newrev, ref) - end - - it "finds references in the first push to a non-default branch" do - allow(project.repository).to receive(:commits_between).with(blankrev, newrev).and_return([]) - allow(project.repository).to receive(:commits_between).with("master", newrev).and_return([commit]) - - expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) - - execute_service(project, user, blankrev, newrev, 'refs/heads/other') - end - end - - describe "issue metrics" do - let(:issue) { create :issue, project: project } - let(:commit_author) { create :user } - let(:commit) { project.commit } - let(:commit_time) { Time.now } - - before do - project.add_developer(commit_author) - project.add_developer(user) - - allow(commit).to receive_messages( - safe_message: "this commit \n mentions #{issue.to_reference}", - references: [issue], - author_name: commit_author.name, - author_email: commit_author.email, - committed_date: commit_time - ) - - allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit) - .and_return(commit) - - allow(project.repository).to receive(:commits_between).and_return([commit]) - end - - context "while saving the 'first_mentioned_in_commit_at' metric for an issue" do - it 'sets the metric for referenced issues' do - execute_service(project, user, oldrev, newrev, ref) - - expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_like_time(commit_time) - end - - it 'does not set the metric for non-referenced issues' do - non_referenced_issue = create(:issue, project: project) - execute_service(project, user, oldrev, newrev, ref) - - expect(non_referenced_issue.reload.metrics.first_mentioned_in_commit_at).to be_nil - end - end - end - - describe "closing issues from pushed commits containing a closing reference" do - let(:issue) { create :issue, project: project } - let(:other_issue) { create :issue, project: project } - let(:commit_author) { create :user } - let(:closing_commit) { project.commit } - - before do - allow(closing_commit).to receive_messages( - issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, - safe_message: "this is some work.\n\ncloses ##{issue.iid}", - author_name: commit_author.name, - author_email: commit_author.email - ) - - allow(project.repository).to receive(:commits_between) - .and_return([closing_commit]) - - allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit) - .and_return(closing_commit) - - project.add_maintainer(commit_author) - end - - context "to default branches" do - it "closes issues" do - execute_service(project, commit_author, oldrev, newrev, ref) - expect(Issue.find(issue.id)).to be_closed - end - - it "adds a note indicating that the issue is now closed" do - expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit) - execute_service(project, commit_author, oldrev, newrev, ref) - end - - it "doesn't create additional cross-reference notes" do - expect(SystemNoteService).not_to receive(:cross_reference) - execute_service(project, commit_author, oldrev, newrev, ref) - end - end - - context "to non-default branches" do - before do - # Make sure the "default" branch is different - allow(project).to receive(:default_branch).and_return('not-master') - end - - it "creates cross-reference notes" do - expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author) - execute_service(project, user, oldrev, newrev, ref) - end - - it "doesn't close issues" do - execute_service(project, user, oldrev, newrev, ref) - expect(Issue.find(issue.id)).to be_opened - end - end - - context "for jira issue tracker" do - include JiraServiceHelper - - let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } - - before do - # project.create_jira_service doesn't seem to invalidate the cache here - project.has_external_issue_tracker = true - jira_service_settings - stub_jira_urls("JIRA-1") - - allow(closing_commit).to receive_messages({ - issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern), - safe_message: message, - author_name: commit_author.name, - author_email: commit_author.email - }) - - allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) - - allow(project.repository).to receive_messages(commits_between: [closing_commit]) - end - - after do - jira_tracker.destroy! - end - - context "mentioning an issue" do - let(:message) { "this is some work.\n\nrelated to JIRA-1" } - - it "initiates one api call to jira server to mention the issue" do - execute_service(project, user, oldrev, newrev, ref) - - expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with( - body: /mentioned this issue in/ - ).once - end - end - - context "closing an issue" do - let(:message) { "this is some work.\n\ncloses JIRA-1" } - let(:comment_body) do - { - body: "Issue solved with [#{closing_commit.id}|http://#{Gitlab.config.gitlab.host}/#{project.full_path}/commit/#{closing_commit.id}]." - }.to_json - end - - before do - open_issue = JIRA::Resource::Issue.new(jira_tracker.client, attrs: { "id" => "JIRA-1" }) - closed_issue = open_issue.dup - allow(open_issue).to receive(:resolution).and_return(false) - allow(closed_issue).to receive(:resolution).and_return(true) - allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue) - - allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return("JIRA-1") - end - - context "using right markdown" do - it "initiates one api call to jira server to close the issue" do - execute_service(project, commit_author, oldrev, newrev, ref) - - expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once - end - - it "initiates one api call to jira server to comment on the issue" do - execute_service(project, commit_author, oldrev, newrev, ref) - - expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with( - body: comment_body - ).once - end - end - - context "using internal issue reference" do - context 'when internal issues are disabled' do - before do - project.issues_enabled = false - project.save! - end - let(:message) { "this is some work.\n\ncloses #1" } - - it "does not initiates one api call to jira server to close the issue" do - execute_service(project, commit_author, oldrev, newrev, ref) - - expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1')) - end - - it "does not initiates one api call to jira server to comment on the issue" do - execute_service(project, commit_author, oldrev, newrev, ref) - - expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with( - body: comment_body - ).once - end - end - - context 'when internal issues are enabled' do - let(:issue) { create(:issue, project: project) } - let(:message) { "this is some work.\n\ncloses JIRA-1 \n\n closes #{issue.to_reference}" } - - it "initiates one api call to jira server to close the jira issue" do - execute_service(project, commit_author, oldrev, newrev, ref) - - expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once - end - - it "initiates one api call to jira server to comment on the jira issue" do - execute_service(project, commit_author, oldrev, newrev, ref) - - expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with( - body: comment_body - ).once - end - - it "closes the internal issue" do - execute_service(project, commit_author, oldrev, newrev, ref) - expect(issue.reload).to be_closed - end - - it "adds a note indicating that the issue is now closed" do - expect(SystemNoteService).to receive(:change_status) - .with(issue, project, commit_author, "closed", closing_commit) - execute_service(project, commit_author, oldrev, newrev, ref) - end - end - end - end - end - end - - describe "empty project" do - let(:project) { create(:project_empty_repo) } - let(:new_ref) { 'refs/heads/feature' } - - before do - allow(project).to receive(:default_branch).and_return('feature') - expect(project).to receive(:change_head) { 'feature'} - end - - it 'push to first branch updates HEAD' do - execute_service(project, user, blankrev, newrev, new_ref) - end - end - - describe "housekeeping" do - let(:housekeeping) { Projects::HousekeepingService.new(project) } - - before do - # Flush any raw key-value data stored by the housekeeping code. - Gitlab::Redis::Cache.with { |conn| conn.flushall } - Gitlab::Redis::Queues.with { |conn| conn.flushall } - Gitlab::Redis::SharedState.with { |conn| conn.flushall } - - allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping) - end - - after do - Gitlab::Redis::Cache.with { |conn| conn.flushall } - Gitlab::Redis::Queues.with { |conn| conn.flushall } - Gitlab::Redis::SharedState.with { |conn| conn.flushall } - end - - it 'does not perform housekeeping when not needed' do - expect(housekeeping).not_to receive(:execute) - - execute_service(project, user, oldrev, newrev, ref) - end - - context 'when housekeeping is needed' do - before do - allow(housekeeping).to receive(:needed?).and_return(true) - end - - it 'performs housekeeping' do - expect(housekeeping).to receive(:execute) - - execute_service(project, user, oldrev, newrev, ref) - end - - it 'does not raise an exception' do - allow(housekeeping).to receive(:try_obtain_lease).and_return(false) - - execute_service(project, user, oldrev, newrev, ref) - end - end - - it 'increments the push counter' do - expect(housekeeping).to receive(:increment!) - - execute_service(project, user, oldrev, newrev, ref) - end - end - - describe '#update_caches' do - let(:service) do - described_class.new(project, - user, - oldrev: oldrev, - newrev: newrev, - ref: ref) - end - - context 'on the default branch' do - before do - allow(service).to receive(:default_branch?).and_return(true) - end - - it 'flushes the caches of any special files that have been changed' do - commit = double(:commit) - diff = double(:diff, new_path: 'README.md') - - expect(commit).to receive(:raw_deltas) - .and_return([diff]) - - service.push_commits = [commit] - - expect(ProjectCacheWorker).to receive(:perform_async) - .with(project.id, %i(readme), %i(commit_count repository_size)) - - service.update_caches - end - end - - context 'on a non-default branch' do - before do - allow(service).to receive(:default_branch?).and_return(false) - end - - it 'does not flush any conditional caches' do - expect(ProjectCacheWorker).to receive(:perform_async) - .with(project.id, [], %i(commit_count repository_size)) - .and_call_original - - service.update_caches - end - end - end - - describe '#process_commit_messages' do - let(:service) do - described_class.new(project, - user, - oldrev: oldrev, - newrev: newrev, - ref: ref) - end - - it 'only schedules a limited number of commits' do - service.push_commits = Array.new(1000, double(:commit, to_hash: {}, matches_cross_reference_regex?: true)) - - expect(ProcessCommitWorker).to receive(:perform_async).exactly(100).times - - service.process_commit_messages - end - - it "skips commits which don't include cross-references" do - service.push_commits = [double(:commit, to_hash: {}, matches_cross_reference_regex?: false)] - - expect(ProcessCommitWorker).not_to receive(:perform_async) - - service.process_commit_messages - end - end - - describe '#update_signatures' do - let(:service) do - described_class.new( - project, - user, - oldrev: oldrev, - newrev: newrev, - ref: 'refs/heads/master' - ) - end - - context 'when the commit has a signature' do - context 'when the signature is already cached' do - before do - create(:gpg_signature, commit_sha: sample_commit.id) - end - - it 'does not queue a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).not_to receive(:perform_async) - - execute_service(project, user, oldrev, newrev, ref) - end - end - - context 'when the signature is not yet cached' do - it 'queues a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id], project.id) - - execute_service(project, user, oldrev, newrev, ref) - end - - it 'can queue several commits to create the gpg signature' do - allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).and_return([sample_commit.id, another_sample_commit.id]) - - expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id, another_sample_commit.id], project.id) - - execute_service(project, user, oldrev, newrev, ref) - end - end - end - - context 'when the commit does not have a signature' do - before do - allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).with(project.repository, [sample_commit.id]).and_return([]) - end - - it 'does not queue a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).not_to receive(:perform_async).with(sample_commit.id, project.id) - - execute_service(project, user, oldrev, newrev, ref) - end - end - end - - def execute_service(project, user, oldrev, newrev, ref) - service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) - service.execute - service - end - - def push_data_from_service(project, user, oldrev, newrev, ref) - execute_service(project, user, oldrev, newrev, ref).push_data - end -end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index ecefdc23811..33648292037 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -23,7 +23,7 @@ module CycleAnalyticsHelpers return if skip_push_handler - GitPushService.new(project, + Git::BranchPushService.new(project, user, oldrev: oldrev, newrev: commit_shas.last, diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index d2881f99a84..9cddad71a51 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -33,7 +33,7 @@ describe PostReceive do describe "#process_project_changes" do context 'empty changes' do it "does not call any PushService but runs after project hooks" do - expect(GitPushService).not_to receive(:new) + expect(Git::BranchPushService).not_to receive(:new) expect(Git::TagPushService).not_to receive(:new) expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) } @@ -45,7 +45,7 @@ describe PostReceive do let!(:key_id) { "" } it 'returns false' do - expect(GitPushService).not_to receive(:new) + expect(Git::BranchPushService).not_to receive(:new) expect(Git::TagPushService).not_to receive(:new) expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false @@ -60,8 +60,8 @@ describe PostReceive do context "branches" do let(:changes) { "123456 789012 refs/heads/tést" } - it "calls GitPushService" do - expect_any_instance_of(GitPushService).to receive(:execute).and_return(true) + it "calls Git::BranchPushService" do + expect_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true) expect_any_instance_of(Git::TagPushService).not_to receive(:execute) described_class.new.perform(gl_repository, key_id, base64_changes) end @@ -71,7 +71,7 @@ describe PostReceive do let(:changes) { "123456 789012 refs/tags/tag" } it "calls Git::TagPushService" do - expect_any_instance_of(GitPushService).not_to receive(:execute) + expect_any_instance_of(Git::BranchPushService).not_to receive(:execute) expect_any_instance_of(Git::TagPushService).to receive(:execute).and_return(true) described_class.new.perform(gl_repository, key_id, base64_changes) end @@ -81,7 +81,7 @@ describe PostReceive do let(:changes) { "123456 789012 refs/merge-requests/123" } it "does not call any of the services" do - expect_any_instance_of(GitPushService).not_to receive(:execute) + expect_any_instance_of(Git::BranchPushService).not_to receive(:execute) expect_any_instance_of(Git::TagPushService).not_to receive(:execute) described_class.new.perform(gl_repository, key_id, base64_changes) end @@ -125,7 +125,7 @@ describe PostReceive do allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data) # silence hooks so we can isolate allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true) - allow_any_instance_of(GitPushService).to receive(:execute).and_return(true) + allow_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true) end it 'calls SystemHooksService' do -- cgit v1.2.1 From 93b806c468a5a88950d28f82f99294ee0e8cef13 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Mon, 25 Mar 2019 15:18:39 +0000 Subject: Improvements for Knative installation Displays Knative domain field automatically after Helm is successfully installed. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/56954 --- spec/factories/clusters/providers/gcp.rb | 4 ++++ spec/helpers/clusters_helper_spec.rb | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 spec/helpers/clusters_helper_spec.rb (limited to 'spec') diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb index a002ab28519..186c7c8027c 100644 --- a/spec/factories/clusters/providers/gcp.rb +++ b/spec/factories/clusters/providers/gcp.rb @@ -28,5 +28,9 @@ FactoryBot.define do gcp.make_errored('Something wrong') end end + + trait :abac_enabled do + legacy_abac true + end end end diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb new file mode 100644 index 00000000000..4ea0f76fc28 --- /dev/null +++ b/spec/helpers/clusters_helper_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ClustersHelper do + describe '#has_rbac_enabled?' do + context 'when kubernetes platform has been created' do + let(:platform_kubernetes) { build_stubbed(:cluster_platform_kubernetes) } + let(:cluster) { build_stubbed(:cluster, :provided_by_gcp, platform_kubernetes: platform_kubernetes) } + + it 'returns kubernetes platform value' do + expect(helper.has_rbac_enabled?(cluster)).to be_truthy + end + end + + context 'when kubernetes platform has not been created yet' do + let(:cluster) { build_stubbed(:cluster, :providing_by_gcp) } + + it 'delegates to cluster provider' do + expect(helper.has_rbac_enabled?(cluster)).to be_truthy + end + + context 'when ABAC cluster is created' do + let(:provider) { build_stubbed(:cluster_provider_gcp, :abac_enabled) } + let(:cluster) { build_stubbed(:cluster, :providing_by_gcp, provider_gcp: provider) } + + it 'delegates to cluster provider' do + expect(helper.has_rbac_enabled?(cluster)).to be_falsy + end + end + end + end +end -- cgit v1.2.1 From 929ee4d18da886826e9fcc15c35b4d4024bc8237 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 22 Mar 2019 13:51:47 -0300 Subject: Add multiple assignees migration and table population This will be further required for supporting multi-assignees MRs --- .../populate_merge_request_assignees_table_spec.rb | 56 ++++++++++++++++++++++ spec/lib/gitlab/import_export/all_models.yml | 1 + ..._populate_merge_request_assignees_table_spec.rb | 47 ++++++++++++++++++ spec/models/merge_request_spec.rb | 25 ++++++++++ 4 files changed, 129 insertions(+) create mode 100644 spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb create mode 100644 spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb new file mode 100644 index 00000000000..4a81a37d341 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migration, schema: 20190315191339 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:users) { table(:users) } + + let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } + let(:user_2) { users.create!(email: 'test2@example.com', projects_limit: 100, username: 'test') } + let(:user_3) { users.create!(email: 'test3@example.com', projects_limit: 100, username: 'test') } + + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:merge_requests) { table(:merge_requests) } + let(:merge_request_assignees) { table(:merge_request_assignees) } + + def create_merge_request(id, params = {}) + params.merge!(id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}") + + merge_requests.create(params) + end + + describe '#perform' do + it 'creates merge_request_assignees rows according to merge_requests' do + create_merge_request(2, assignee_id: user.id) + create_merge_request(3, assignee_id: user_2.id) + create_merge_request(4, assignee_id: user_3.id) + # Test filtering already migrated row + merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id) + + subject.perform(1, 4) + + rows = merge_request_assignees.order(:id).map { |row| row.attributes.slice('merge_request_id', 'user_id') } + existing_rows = [ + { 'merge_request_id' => 2, 'user_id' => user_3.id } + ] + created_rows = [ + { 'merge_request_id' => 3, 'user_id' => user_2.id }, + { 'merge_request_id' => 4, 'user_id' => user_3.id } + ] + expected_rows = existing_rows + created_rows + + expect(rows.size).to eq(expected_rows.size) + expected_rows.each do |expected_row| + expect(rows).to include(expected_row) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 01da3ea7081..5299ab297f6 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -100,6 +100,7 @@ merge_requests: - head_pipeline - latest_merge_request_diff - merge_request_pipelines +- merge_request_assignees merge_request_diff: - merge_request - merge_request_diff_commits diff --git a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb new file mode 100644 index 00000000000..e397fbb7138 --- /dev/null +++ b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190322132835_schedule_populate_merge_request_assignees_table.rb') + +describe SchedulePopulateMergeRequestAssigneesTable, :migration, :sidekiq do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:merge_requests) { table(:merge_requests) } + + def create_merge_request(id) + params = { + id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}" + } + + merge_requests.create!(params) + end + + it 'correctly schedules background migrations' do + create_merge_request(1) + create_merge_request(2) + create_merge_request(3) + + stub_const("#{described_class.name}::BATCH_SIZE", 2) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(8.minutes, 1, 2) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(16.minutes, 3, 3) + + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 22998bc5b6a..ec39c174171 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -184,6 +184,31 @@ describe MergeRequest do expect(MergeRequest::Metrics.count).to eq(1) end end + + describe '#refresh_merge_request_assignees' do + set(:user) { create(:user) } + + it 'creates merge request assignees relation upon MR creation' do + merge_request = create(:merge_request, assignee: nil) + + expect(merge_request.merge_request_assignees).to be_empty + + expect { merge_request.update!(assignee: user) } + .to change { merge_request.reload.merge_request_assignees.count } + .from(0).to(1) + end + + it 'updates merge request assignees relation upon MR assignee change' do + another_user = create(:user) + merge_request = create(:merge_request, assignee: user) + + expect { merge_request.update!(assignee: another_user) } + .to change { merge_request.reload.merge_request_assignees.first.assignee } + .from(user).to(another_user) + + expect(merge_request.merge_request_assignees.count).to eq(1) + end + end end describe 'respond to' do -- cgit v1.2.1 From f82380b9df9693e7976b7474233840a469635429 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Wed, 20 Mar 2019 17:08:51 +1300 Subject: Allow custom hooks errors to appear in GitLab UI Error messages from custom pre-receive hooks now appear in the GitLab UI. This is re-enabling a feature that had been disabled in merge request https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18646 The feature had been disabled due to security concerns that information which was not intended to be public (like stack traces) would leak into public view. PreReceiveErrors (from pre-receive, post-receive and update custom hooks) are now filtered for messages that have been prefixed in a particular way. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/48132 --- spec/features/tags/master_deletes_tag_spec.rb | 2 +- .../components/states/mr_widget_failed_to_merge_spec.js | 2 +- spec/lib/gitlab/git/pre_receive_error_spec.rb | 16 +++++++++++++--- spec/lib/gitlab/gitaly_client/operation_service_spec.rb | 10 +++++----- spec/services/merge_requests/ff_merge_service_spec.rb | 2 +- spec/services/merge_requests/merge_service_spec.rb | 2 +- spec/services/tags/create_service_spec.rb | 2 +- 7 files changed, 23 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb index 8d567e925ef..bdbbe645779 100644 --- a/spec/features/tags/master_deletes_tag_spec.rb +++ b/spec/features/tags/master_deletes_tag_spec.rb @@ -37,7 +37,7 @@ describe 'Maintainer deletes tag' do context 'when pre-receive hook fails', :js do before do allow_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rm_tag) - .and_raise(Gitlab::Git::PreReceiveError, 'Do not delete tags') + .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: Do not delete tags') end it 'shows the error message' do diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js index 3229ddd5e27..780bed1bf69 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js @@ -120,7 +120,7 @@ describe('MRWidgetFailedToMerge', () => { it('renders given error', () => { expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual( - 'Merge error happened.', + 'Merge error happened', ); }); diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb index 1b8be62dec6..cb030e38032 100644 --- a/spec/lib/gitlab/git/pre_receive_error_spec.rb +++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb @@ -1,9 +1,19 @@ require 'spec_helper' describe Gitlab::Git::PreReceiveError do - it 'makes its message HTML-friendly' do - ex = described_class.new("hello\nworld\n") + Gitlab::Git::PreReceiveError::SAFE_MESSAGE_PREFIXES.each do |prefix| + context "error messages prefixed with #{prefix}" do + it 'accepts only errors lines with the prefix' do + ex = described_class.new("#{prefix} Hello,\nworld!") - expect(ex.message).to eq('hello
    world
    ') + expect(ex.message).to eq('Hello,') + end + + it 'makes its message HTML-friendly' do + ex = described_class.new("#{prefix} Hello,\n#{prefix} world!\n") + + expect(ex.message).to eq('Hello,
    world!') + end + end end end diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index b37fe2686b6..7579a6577b9 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -39,7 +39,7 @@ describe Gitlab::GitalyClient::OperationService do context "when pre_receive_error is present" do let(:response) do - Gitaly::UserCreateBranchResponse.new(pre_receive_error: "something failed") + Gitaly::UserCreateBranchResponse.new(pre_receive_error: "GitLab: something failed") end it "throws a PreReceive exception" do @@ -80,7 +80,7 @@ describe Gitlab::GitalyClient::OperationService do context "when pre_receive_error is present" do let(:response) do - Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "something failed") + Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "GitLab: something failed") end it "throws a PreReceive exception" do @@ -117,7 +117,7 @@ describe Gitlab::GitalyClient::OperationService do context "when pre_receive_error is present" do let(:response) do - Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "something failed") + Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "GitLab: something failed") end it "throws a PreReceive exception" do @@ -175,7 +175,7 @@ describe Gitlab::GitalyClient::OperationService do shared_examples 'cherry pick and revert errors' do context 'when a pre_receive_error is present' do - let(:response) { response_class.new(pre_receive_error: "something failed") } + let(:response) { response_class.new(pre_receive_error: "GitLab: something failed") } it 'raises a PreReceiveError' do expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed") @@ -313,7 +313,7 @@ describe Gitlab::GitalyClient::OperationService do end context 'when a pre_receive_error is present' do - let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "something failed") } + let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "GitLab: something failed") } it 'raises a PreReceiveError' do expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed") diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index fe673de46aa..1430e12a07e 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -72,7 +72,7 @@ describe MergeRequests::FfMergeService do it 'logs and saves error if there is an PreReceiveError exception' do error_message = 'error message' - allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message) + allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}") allow(service).to receive(:execute_hooks) service.execute(merge_request) diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index ede79b87bcc..887ec17171e 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -239,7 +239,7 @@ describe MergeRequests::MergeService do it 'logs and saves error if there is an PreReceiveError exception' do error_message = 'error message' - allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message) + allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}") allow(service).to receive(:execute_hooks) service.execute(merge_request) diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb index 0cbe57352be..e112cdc8881 100644 --- a/spec/services/tags/create_service_spec.rb +++ b/spec/services/tags/create_service_spec.rb @@ -41,7 +41,7 @@ describe Tags::CreateService do it 'returns an error' do expect(repository).to receive(:add_tag) .with(user, 'v1.1.0', 'master', 'Foo') - .and_raise(Gitlab::Git::PreReceiveError, 'something went wrong') + .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: something went wrong') response = service.execute('v1.1.0', 'master', 'Foo') -- cgit v1.2.1 From 7495140de2b4412240a09d7ee72800a84f57433e Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Fri, 22 Mar 2019 22:33:01 +0800 Subject: Expand discussion when opening link to comment Makes discussion expansion depend on `discussion.expanded` --- .../user_scrolls_to_note_on_load_spec.rb | 38 +++++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb index 8c2599615cb..2f7d359575e 100644 --- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb +++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb @@ -5,9 +5,7 @@ describe 'Merge request > User scrolls to note on load', :js do let(:user) { project.creator } let(:merge_request) { create(:merge_request, source_project: project, author: user) } let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } - let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) } let(:fragment_id) { "#note_#{note.id}" } - let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" } before do sign_in(user) @@ -45,13 +43,35 @@ describe 'Merge request > User scrolls to note on load', :js do end end - # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 - xit 'expands collapsed notes' do - visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}" - note_element = find(collapsed_fragment_id) - note_container = note_element.ancestor('.timeline-content') + context 'resolved notes' do + let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" } - expect(note_element.visible?).to eq true - expect(note_container.find('.line_content.noteable_line.old', match: :first).visible?).to eq true + context 'when diff note' do + let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) } + + it 'expands collapsed notes' do + visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}" + + note_element = find(collapsed_fragment_id) + diff_container = note_element.ancestor('.diff-content') + + expect(note_element.visible?).to eq(true) + expect(diff_container.visible?).to eq(true) + end + end + + context 'when non-diff note' do + let(:non_diff_discussion) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project) } + let(:resolved_note) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project, in_reply_to: non_diff_discussion) } + + it 'expands collapsed replies' do + visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}" + + note_element = find(collapsed_fragment_id) + + expect(note_element.visible?).to eq(true) + expect(note_element.sibling('.replies-toggle')[:class]).to include('expanded') + end + end end end -- cgit v1.2.1 From 2d233dac4573f8d321464712333d4ff8bcb767af Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 26 Mar 2019 01:01:14 -0500 Subject: Clear pipeline status cache after destruction of pipeline `project.pipeline_status.has_status?` is cached, which can lead to Error 500s in the UI if the this condition is used to check whether a pipeline exists for a commit. We now expire the cache to ensure that the information is consistent. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59453 --- spec/services/ci/destroy_pipeline_service_spec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb index d896f990470..bff2b3179fb 100644 --- a/spec/services/ci/destroy_pipeline_service_spec.rb +++ b/spec/services/ci/destroy_pipeline_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe ::Ci::DestroyPipelineService do - let(:project) { create(:project) } - let!(:pipeline) { create(:ci_pipeline, project: project) } + let(:project) { create(:project, :repository) } + let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) } subject { described_class.new(project, user).execute(pipeline) } @@ -17,6 +17,17 @@ describe ::Ci::DestroyPipelineService do expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound) end + it 'clears the cache', :use_clean_rails_memory_store_caching do + create(:commit_status, :success, pipeline: pipeline, ref: pipeline.ref) + + expect(project.pipeline_status.has_status?).to be_truthy + + subject + + # Need to use find to avoid memoization + expect(Project.find(project.id).pipeline_status.has_status?).to be_falsey + end + it 'does not log an audit event' do expect { subject }.not_to change { SecurityEvent.count } end -- cgit v1.2.1 From b641c654b19f6ac9374fe256802cf3ae5c3f2ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Tue, 26 Mar 2019 09:30:16 +0100 Subject: Refactor specs according to the code review --- spec/controllers/projects_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 717675d1d30..356d606d5c5 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -383,7 +383,7 @@ describe ProjectsController do id: project.id, project: params } - end.not_to change {project.reload.namespace} + end.not_to change { project.namespace.reload } end def update_project(**parameters) -- cgit v1.2.1 From 76d281881a945518fe4565e1dc71f6a3bc28c575 Mon Sep 17 00:00:00 2001 From: Tiger Watson Date: Tue, 26 Mar 2019 09:59:48 +0000 Subject: Allow runners to be installed on group clusters A runner installed on a cluster will now use the cluster's `cluster_type` as its `runner_type`. --- .../clusters/components/applications_spec.js | 2 +- spec/models/clusters/applications/runner_spec.rb | 43 ++++++++++++++++------ .../clusters/applications/create_service_spec.rb | 2 +- 3 files changed, 34 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js index 790e4b9602c..0f8153ad493 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/javascripts/clusters/components/applications_spec.js @@ -79,7 +79,7 @@ describe('Applications', () => { }); it('renders a row for GitLab Runner', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeNull(); + expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); }); it('renders a row for Jupyter', () => { diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 06d9bc076cd..3ce8aa1c7bc 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -64,24 +64,45 @@ describe Clusters::Applications::Runner do end context 'without a runner' do - let(:project) { create(:project) } - let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } let(:application) { create(:clusters_applications_runner, runner: nil, cluster: cluster) } + let(:runner) { application.runner } - it 'creates a runner' do - expect do - subject - end.to change { Ci::Runner.count }.by(1) + shared_examples 'runner creation' do + it 'creates a runner' do + expect { subject }.to change { Ci::Runner.count }.by(1) + end + + it 'uses the new runner token' do + expect(values).to match(/runnerToken: '?#{runner.token}/) + end end - it 'uses the new runner token' do - expect(values).to match(/runnerToken: '?#{application.reload.runner.token}/) + context 'project cluster' do + let(:project) { create(:project) } + let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } + + include_examples 'runner creation' + + it 'creates a project runner' do + subject + + expect(runner).to be_project_type + expect(runner.projects).to eq [project] + end end - it 'assigns the new runner to runner' do - subject + context 'group cluster' do + let(:group) { create(:group) } + let(:cluster) { create(:cluster, :with_installed_helm, cluster_type: :group_type, groups: [group]) } + + include_examples 'runner creation' + + it 'creates a group runner' do + subject - expect(application.reload.runner).to be_project_type + expect(runner).to be_group_type + expect(runner.groups).to eq [group] + end end end diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb index cbdef008b07..20555873503 100644 --- a/spec/services/clusters/applications/create_service_spec.rb +++ b/spec/services/clusters/applications/create_service_spec.rb @@ -150,7 +150,7 @@ describe Clusters::Applications::CreateService do where(:application, :association, :allowed, :pre_create_helm) do 'helm' | :application_helm | true | false 'ingress' | :application_ingress | true | true - 'runner' | :application_runner | false | true + 'runner' | :application_runner | true | true 'jupyter' | :application_jupyter | false | true 'prometheus' | :application_prometheus | false | true end -- cgit v1.2.1 From b27c4420c9e62ffc8cf53a4a854f4907454af400 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 25 Mar 2019 14:41:38 +0700 Subject: Destroy releases when delete a tag ok ok ok ok --- spec/services/releases/destroy_service_spec.rb | 8 +++----- spec/services/tags/destroy_service_spec.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb index dd5b8708f36..28663ca8853 100644 --- a/spec/services/releases/destroy_service_spec.rb +++ b/spec/services/releases/destroy_service_spec.rb @@ -28,13 +28,11 @@ describe Releases::DestroyService do end end - context 'when tag is not found' do + context 'when tag does not exist in the repository' do let(:tag) { 'v1.1.1' } - it 'returns an error' do - is_expected.to include(status: :error, - message: 'Tag does not exist', - http_status: 404) + it 'removes the orphaned release' do + expect { subject }.to change { project.releases.count }.by(-1) end end diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb index 7c8c1dd0d3a..a541d300595 100644 --- a/spec/services/tags/destroy_service_spec.rb +++ b/spec/services/tags/destroy_service_spec.rb @@ -7,11 +7,27 @@ describe Tags::DestroyService do let(:service) { described_class.new(project, user) } describe '#execute' do + subject { service.execute(tag_name) } + it 'removes the tag' do expect(repository).to receive(:before_remove_tag) expect(service).to receive(:success) service.execute('v1.1.0') end + + context 'when there is an associated release on the tag' do + let(:tag) { repository.tags.first } + let(:tag_name) { tag.name } + + before do + project.add_maintainer(user) + create(:release, tag: tag_name, project: project) + end + + it 'destroys the release' do + expect { subject }.to change { project.releases.count }.by(-1) + end + end end end -- cgit v1.2.1 From d36415b7545fff543f08a5175790e3a92f383475 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 18 Mar 2019 17:51:11 +0100 Subject: Allow multiple repositories per project This changes the repository type from a binary `wiki?` to a type. So we can have more than 2 repository types. Now everywhere we called `.wiki?` and expected a boolean, we check that type. --- spec/lib/gitlab/gl_repository/repo_type_spec.rb | 64 +++++++++++++++++++++++++ spec/lib/gitlab/gl_repository_spec.rb | 4 +- spec/lib/gitlab/repo_path_spec.rb | 20 ++++---- spec/lib/gitlab/workhorse_spec.rb | 10 ++-- spec/models/project_spec.rb | 14 +----- spec/requests/api/internal_spec.rb | 6 +-- 6 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 spec/lib/gitlab/gl_repository/repo_type_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb new file mode 100644 index 00000000000..f06a2448ff7 --- /dev/null +++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::GlRepository::RepoType do + set(:project) { create(:project) } + + shared_examples 'a repo type' do + describe "#identifier_for_subject" do + subject { described_class.identifier_for_subject(project) } + + it { is_expected.to eq(expected_identifier) } + end + + describe "#fetch_id" do + it "finds an id match in the identifier" do + expect(described_class.fetch_id(expected_identifier)).to eq(expected_id) + end + + it 'does not break on other identifiers' do + expect(described_class.fetch_id("wiki-noid")).to eq(nil) + end + end + + describe "#path_suffix" do + subject { described_class.path_suffix } + + it { is_expected.to eq(expected_suffix) } + end + + describe "#repository_for" do + it "finds the repository for the repo type" do + expect(described_class.repository_for(project)).to eq(expected_repository) + end + end + end + + describe Gitlab::GlRepository::PROJECT do + it_behaves_like 'a repo type' do + let(:expected_identifier) { "project-#{project.id}" } + let(:expected_id) { project.id.to_s } + let(:expected_suffix) { "" } + let(:expected_repository) { project.repository } + end + + it "knows its type" do + expect(described_class).not_to be_wiki + expect(described_class).to be_project + end + end + + describe Gitlab::GlRepository::WIKI do + it_behaves_like 'a repo type' do + let(:expected_identifier) { "wiki-#{project.id}" } + let(:expected_id) { project.id.to_s } + let(:expected_suffix) { ".wiki" } + let(:expected_repository) { project.wiki.repository } + end + + it "knows its type" do + expect(described_class).to be_wiki + expect(described_class).not_to be_project + end + end +end diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb index 4e09020471b..d4b6c629659 100644 --- a/spec/lib/gitlab/gl_repository_spec.rb +++ b/spec/lib/gitlab/gl_repository_spec.rb @@ -5,11 +5,11 @@ describe ::Gitlab::GlRepository do set(:project) { create(:project, :repository) } it 'parses a project gl_repository' do - expect(described_class.parse("project-#{project.id}")).to eq([project, false]) + expect(described_class.parse("project-#{project.id}")).to eq([project, Gitlab::GlRepository::PROJECT]) end it 'parses a wiki gl_repository' do - expect(described_class.parse("wiki-#{project.id}")).to eq([project, true]) + expect(described_class.parse("wiki-#{project.id}")).to eq([project, Gitlab::GlRepository::WIKI]) end it 'throws an argument error on an invalid gl_repository' do diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index 13940713dfc..4c7ca4e2b57 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -6,43 +6,47 @@ describe ::Gitlab::RepoPath do context 'a repository storage path' do it 'parses a full repository path' do - expect(described_class.parse(project.repository.full_path)).to eq([project, false, nil]) + expect(described_class.parse(project.repository.full_path)).to eq([project, Gitlab::GlRepository::PROJECT, nil]) end it 'parses a full wiki path' do - expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, true, nil]) + expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, Gitlab::GlRepository::WIKI, nil]) end end context 'a relative path' do it 'parses a relative repository path' do - expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil]) + expect(described_class.parse(project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil]) end it 'parses a relative wiki path' do - expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil]) + expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, nil]) end it 'parses a relative path starting with /' do - expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil]) + expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil]) end context 'of a redirected project' do let(:redirect) { project.route.create_redirect('foo/bar') } it 'parses a relative repository path' do - expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar']) + expect(described_class.parse(redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar']) end it 'parses a relative wiki path' do - expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki']) + expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki']) end it 'parses a relative path starting with /' do - expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar']) + expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar']) end end end + + it "returns nil for non existent paths" do + expect(described_class.parse("path/non-existent.git")).to eq(nil) + end end describe '.find_project' do diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 7213eee5675..d88086b01b1 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -250,11 +250,11 @@ describe Gitlab::Workhorse do } end - subject { described_class.git_http_ok(repository, false, user, action) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action) } it { expect(subject).to include(params) } - context 'when is_wiki' do + context 'when the repo_type is a wiki' do let(:params) do { GL_ID: "user-#{user.id}", @@ -264,7 +264,7 @@ describe Gitlab::Workhorse do } end - subject { described_class.git_http_ok(repository, true, user, action) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::WIKI, user, action) } it { expect(subject).to include(params) } end @@ -304,7 +304,7 @@ describe Gitlab::Workhorse do end context 'show_all_refs enabled' do - subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) } it { is_expected.to include(ShowAllRefs: true) } end @@ -322,7 +322,7 @@ describe Gitlab::Workhorse do it { expect(subject).to include(gitaly_params) } context 'show_all_refs enabled' do - subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) } it { is_expected.to include(ShowAllRefs: true) } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1ea54eeb4f7..fc9d9a28b9a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3428,7 +3428,7 @@ describe Project do end it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the project repo is in use' do - Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase + Gitlab::ReferenceCounter.new(Gitlab::GlRepository::PROJECT.identifier_for_subject(project)).increase expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in) @@ -3436,7 +3436,7 @@ describe Project do end it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do - Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase + Gitlab::ReferenceCounter.new(Gitlab::GlRepository::WIKI.identifier_for_subject(project)).increase expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in) @@ -3569,16 +3569,6 @@ describe Project do end end - describe '#gl_repository' do - let(:project) { create(:project) } - - it 'delegates to Gitlab::GlRepository.gl_repository' do - expect(Gitlab::GlRepository).to receive(:gl_repository).with(project, true) - - project.gl_repository(is_wiki: true) - end - end - describe '#has_ci?' do set(:project) { create(:project) } let(:repository) { double } diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index b184c92824a..537194b8e11 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -321,7 +321,7 @@ describe API::Internal do end context 'with env passed as a JSON' do - let(:gl_repository) { project.gl_repository(is_wiki: true) } + let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_subject(project) } it 'sets env in RequestStore' do obj_dir_relative = './objects' @@ -975,9 +975,9 @@ describe API::Internal do def gl_repository_for(project_or_wiki) case project_or_wiki when ProjectWiki - project_or_wiki.project.gl_repository(is_wiki: true) + Gitlab::GlRepository::WIKI.identifier_for_subject(project_or_wiki.project) when Project - project_or_wiki.gl_repository(is_wiki: false) + Gitlab::GlRepository::PROJECT.identifier_for_subject(project_or_wiki) else nil end -- cgit v1.2.1 From 7fbfb1998a0757b843d63cba0b43294a53891f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 22 Mar 2019 14:20:24 +0100 Subject: Force to recreate all diffs on import If for whatever reason we fail to import MR data, subsequent run will fail as we try to insert duplicate data. Instead of trying to recover, lets delete all and retry again. --- .../importer/pull_request_importer_spec.rb | 78 +++++++++++++--------- 1 file changed, 45 insertions(+), 33 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index 680de47de2b..2e4a7c36fb8 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -11,6 +11,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi let(:source_commit) { project.repository.commit('feature') } let(:target_commit) { project.repository.commit('master') } let(:milestone) { create(:milestone, project: project) } + let(:state) { :closed } let(:pull_request) do alice = Gitlab::GithubImport::Representation::User.new(id: 4, login: 'alice') @@ -26,13 +27,13 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi source_repository_id: 400, target_repository_id: 200, source_repository_owner: 'alice', - state: :closed, + state: state, milestone_number: milestone.iid, author: alice, assignee: alice, created_at: created_at, updated_at: updated_at, - merged_at: merged_at + merged_at: state == :closed && merged_at ) end @@ -260,58 +261,63 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi end it 'does not create the source branch if merge request is merged' do - mr, exists = importer.create_merge_request - - importer.insert_git_data(mr, exists) + mr = insert_git_data expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy end - it 'creates the source branch if merge request is open' do - mr, exists = importer.create_merge_request - mr.state = 'opened' - mr.save + context 'when merge request is open' do + let(:state) { :opened } - # Ensure the project creator is creating the branches because the - # merge request author may not have access to push to this - # repository. The project owner may also be a group. - allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original + it 'creates the source branch' do + # Ensure the project creator is creating the branches because the + # merge request author may not have access to push to this + # repository. The project owner may also be a group. + allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original - importer.insert_git_data(mr, exists) + mr = insert_git_data - expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy - expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy - end + expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy + expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + end - it 'ignores Git errors when creating a branch' do - mr, exists = importer.create_merge_request - mr.state = 'opened' - mr.save + it 'is able to retry on pre-receive errors' do + expect(importer).to receive(:insert_or_replace_git_data).twice.and_call_original + expect(project.repository).to receive(:add_branch).and_raise('exception') - expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError) - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original + expect { insert_git_data }.to raise_error('exception') - importer.insert_git_data(mr, exists) + expect(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original - expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey - expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + mr = insert_git_data + + expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy + expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + expect(mr.merge_request_diffs).to be_one + end + + it 'ignores Git command errors when creating a branch' do + expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError) + expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original + + mr = insert_git_data + + expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey + expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + end end it 'creates the merge request diffs' do - mr, exists = importer.create_merge_request - - importer.insert_git_data(mr, exists) + mr = insert_git_data expect(mr.merge_request_diffs.exists?).to eq(true) end it 'creates the merge request diff commits' do - mr, exists = importer.create_merge_request - - importer.insert_git_data(mr, exists) + mr = insert_git_data - diff = mr.merge_request_diffs.take + diff = mr.merge_request_diffs.reload.first expect(diff.merge_request_diff_commits.exists?).to eq(true) end @@ -327,5 +333,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi expect(mr.merge_request_diffs.exists?).to eq(true) end end + + def insert_git_data + mr, exists = importer.create_merge_request + importer.insert_git_data(mr, exists) + mr + end end end -- cgit v1.2.1 From 838f7e56a407aa335cf4406632373bac3d05dee0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 23 Mar 2019 14:09:14 +0100 Subject: Remove .wiki and @md-typography mixin in favor of .md and .md-file Before, the styling for `img:not(.emoji)`` was repeated between `.md:not(.use-csslab):not(.wiki)` (for comments and the Markdown preview), `.documentation.wiki` (for help pages), and `.issuable-details .description .wiki` (for MR and issue descriptions). In these places, we now simply use `.md`. Wherever we use `.md` but don't want this image styling (like wikis and blobs), `.md-file` is added. --- spec/features/merge_request/user_views_open_merge_request_spec.rb | 2 +- spec/features/projects/wiki/user_creates_wiki_page_spec.rb | 2 +- spec/features/task_lists_spec.rb | 8 ++++---- spec/javascripts/fixtures/static/merge_requests_show.html.raw | 2 +- spec/javascripts/issue_show/components/app_spec.js | 4 ++-- spec/javascripts/issue_show/components/description_spec.js | 4 ++-- spec/support/matchers/issuable_matchers.rb | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 71022c6bb08..849fab62fc6 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -13,7 +13,7 @@ describe 'User views an open merge request' do end it 'renders both the title and the description' do - node = find('.wiki h1 a#user-content-description-header') + node = find('.md h1 a#user-content-description-header') expect(node[:href]).to end_with('#description-header') # Work around a weird Capybara behavior where calling `parent` on a node diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index b1a7f167977..efb7b01f5ad 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -136,7 +136,7 @@ describe "User creates wiki page" do click_button("Create page") end - page.within ".wiki" do + page.within ".md" do expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4") end end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 6fe840dccf6..33d9c10f5e8 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -79,7 +79,7 @@ describe 'Task Lists' do visit_issue(project, issue) wait_for_requests - expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector('a.btn-close') end @@ -87,14 +87,14 @@ describe 'Task Lists' do visit_issue(project, issue) wait_for_requests - expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox") logout(:user) login_as(user2) visit current_path wait_for_requests - expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox") end it 'provides a summary on Issues#index' do @@ -231,7 +231,7 @@ describe 'Task Lists' do container = '.detail-page-description .description.js-task-list-container' expect(page).to have_selector(container) - expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .md .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html.raw b/spec/javascripts/fixtures/static/merge_requests_show.html.raw index e219d9462aa..87e36c9f315 100644 --- a/spec/javascripts/fixtures/static/merge_requests_show.html.raw +++ b/spec/javascripts/fixtures/static/merge_requests_show.html.raw @@ -1,7 +1,7 @@
    -
    +
    • diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 0ccf771c7ef..dfc889773c1 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -75,7 +75,7 @@ describe('Issuable output', () => { .then(() => { expect(document.querySelector('title').innerText).toContain('this is a title (#1)'); expect(vm.$el.querySelector('.title').innerHTML).toContain('

      this is a title

      '); - expect(vm.$el.querySelector('.wiki').innerHTML).toContain('

      this is a description!

      '); + expect(vm.$el.querySelector('.md').innerHTML).toContain('

      this is a description!

      '); expect(vm.$el.querySelector('.js-task-list-field').value).toContain( 'this is a description', ); @@ -92,7 +92,7 @@ describe('Issuable output', () => { .then(() => { expect(document.querySelector('title').innerText).toContain('2 (#1)'); expect(vm.$el.querySelector('.title').innerHTML).toContain('

      2

      '); - expect(vm.$el.querySelector('.wiki').innerHTML).toContain('

      42

      '); + expect(vm.$el.querySelector('.md').innerHTML).toContain('

      42

      '); expect(vm.$el.querySelector('.js-task-list-field').value).toContain('42'); expect(vm.$el.querySelector('.edited-text')).toBeTruthy(); expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch( diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js index 2eeed6770be..7e00fbf2745 100644 --- a/spec/javascripts/issue_show/components/description_spec.js +++ b/spec/javascripts/issue_show/components/description_spec.js @@ -43,12 +43,12 @@ describe('Description component', () => { Vue.nextTick(() => { expect( - vm.$el.querySelector('.wiki').classList.contains('issue-realtime-pre-pulse'), + vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'), ).toBeTruthy(); setTimeout(() => { expect( - vm.$el.querySelector('.wiki').classList.contains('issue-realtime-trigger-pulse'), + vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'), ).toBeTruthy(); done(); diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb index f5d9a97051a..62f510b0fbd 100644 --- a/spec/support/matchers/issuable_matchers.rb +++ b/spec/support/matchers/issuable_matchers.rb @@ -1,4 +1,4 @@ -RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".wiki"| +RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"| match do |actual| node = find("#{parent} h#{level} a#user-content-#{id}") -- cgit v1.2.1 From 63792707f981a08097addec2ab7fdc9b301ef06c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 23 Mar 2019 14:19:36 +0100 Subject: Remove now-unnecessary md-preview class --- spec/features/snippets/notes_on_personal_snippets_spec.rb | 2 +- spec/features/snippets/user_creates_snippet_spec.rb | 2 +- spec/javascripts/vue_shared/components/markdown/field_spec.js | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index eeacaf5f72a..fc6726985ae 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -70,7 +70,7 @@ describe 'Comments on personal snippets', :js do fill_in 'note[note]', with: 'This is **awesome**!' find('.js-md-preview-button').click - page.within('.new-note .md-preview') do + page.within('.new-note .md-preview-holder') do expect(page).to have_content('This is awesome!') expect(page).to have_selector('strong') end diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index 879c46d7c4e..1c97d5ec5b4 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -37,7 +37,7 @@ describe 'User creates snippet', :js do dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') find('.js-md-preview-button').click - page.within('#new_personal_snippet .md-preview') do + page.within('#new_personal_snippet .md-preview-holder') do expect(page).to have_content('My Snippet') link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js index 79e0e756a7a..02d73e1849a 100644 --- a/spec/javascripts/vue_shared/components/markdown/field_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js @@ -5,7 +5,7 @@ import fieldComponent from '~/vue_shared/components/markdown/field.vue'; function assertMarkdownTabs(isWrite, writeLink, previewLink, vm) { expect(writeLink.parentNode.classList.contains('active')).toEqual(isWrite); expect(previewLink.parentNode.classList.contains('active')).toEqual(!isWrite); - expect(vm.$el.querySelector('.md-preview').style.display).toEqual(isWrite ? 'none' : ''); + expect(vm.$el.querySelector('.md-preview-holder').style.display).toEqual(isWrite ? 'none' : ''); } describe('Markdown field component', () => { @@ -80,7 +80,9 @@ describe('Markdown field component', () => { previewLink.click(); Vue.nextTick(() => { - expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading…'); + expect(vm.$el.querySelector('.md-preview-holder').textContent.trim()).toContain( + 'Loading…', + ); done(); }); @@ -90,7 +92,7 @@ describe('Markdown field component', () => { previewLink.click(); setTimeout(() => { - expect(vm.$el.querySelector('.md-preview').innerHTML).toContain( + expect(vm.$el.querySelector('.md-preview-holder').innerHTML).toContain( '

      markdown preview

      ', ); -- cgit v1.2.1 From 40e3f207cbc2817c75df35ad19febb1aa76e9993 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 23 Mar 2019 14:34:08 +0100 Subject: Remove now-unnecessary noteable_line class --- spec/javascripts/diffs/mock_data/diff_discussions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index fd5dd611383..711ab543411 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -496,7 +496,7 @@ export default { { text: 'line', rich_text: - '\n\n \n\n\n1\n\n - Bad dates\n\n\n\n\n \n\n\n2\n\n\n\n\n', + '\n\n \n\n\n1\n\n - Bad dates\n\n\n\n\n \n\n\n2\n\n\n\n\n', can_receive_suggestion: true, line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1', type: 'new', -- cgit v1.2.1 From c02b60261c09919ec0ee48f4fa35163612f43bc2 Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Tue, 26 Mar 2019 14:41:17 +1100 Subject: Remove EE differences for monitoring dashboard --- spec/javascripts/monitoring/dashboard_spec.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 6078a0e7872..454777fa912 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -33,6 +33,11 @@ describe('Dashboard', () => {
      `); + window.gon = { + ...window.gon, + ee: false, + }; + mock = new MockAdapter(axios); DashboardComponent = Vue.extend(Dashboard); }); @@ -152,6 +157,25 @@ describe('Dashboard', () => { done(); }); }); + + it('hides the dropdown', done => { + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + environmentsEndpoint: '', + }, + }); + + Vue.nextTick(() => { + const dropdownIsActiveElement = component.$el.querySelectorAll('.environments'); + + expect(dropdownIsActiveElement.length).toEqual(0); + done(); + }); + }); }); describe('when the window resizes', () => { -- cgit v1.2.1 From bdee9e8412dc9fe4925031cb06070de0fb134de3 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 22 Mar 2019 19:14:39 +0700 Subject: Disallow guest users from accessing Releases As they do not have a permission to read git tag --- spec/policies/project_policy_spec.rb | 4 ++-- spec/requests/api/releases_spec.rb | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 772d1fbee2b..c12c4677af1 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -15,7 +15,7 @@ describe ProjectPolicy do read_project_for_iids read_issue_iid read_label read_milestone read_project_snippet read_project_member read_note create_project create_issue create_note upload_file create_merge_request_in - award_emoji read_release + award_emoji ] end @@ -24,7 +24,7 @@ describe ProjectPolicy do download_code fork_project create_project_snippet update_issue admin_issue admin_label admin_list read_commit_status read_build read_container_image read_pipeline read_environment read_deployment - read_merge_request download_wiki_code read_sentry_issue + read_merge_request download_wiki_code read_sentry_issue read_release ] end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 1f317971a66..71ec091c42c 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -4,12 +4,14 @@ describe API::Releases do let(:project) { create(:project, :repository, :private) } let(:maintainer) { create(:user) } let(:reporter) { create(:user) } + let(:guest) { create(:user) } let(:non_project_member) { create(:user) } let(:commit) { create(:commit, project: project) } before do project.add_maintainer(maintainer) project.add_reporter(reporter) + project.add_guest(guest) project.repository.add_tag(maintainer, 'v0.1', commit.id) project.repository.add_tag(maintainer, 'v0.2', commit.id) @@ -66,6 +68,24 @@ describe API::Releases do end end + context 'when user is a guest' do + it 'responds 403 Forbidden' do + get api("/projects/#{project.id}/releases", guest) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'responds 200 OK' do + get api("/projects/#{project.id}/releases", guest) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + context 'when user is not a project member' do it 'cannot find the project' do get api("/projects/#{project.id}/releases", non_project_member) @@ -189,6 +209,24 @@ describe API::Releases do end end end + + context 'when user is a guest' do + it 'responds 403 Forbidden' do + get api("/projects/#{project.id}/releases/v0.1", guest) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'responds 200 OK' do + get api("/projects/#{project.id}/releases/v0.1", guest) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end end context 'when specified tag is not found in the project' do -- cgit v1.2.1 From ad985a7a28c19a085011afc7531f4f2c13783cb2 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Mar 2019 16:03:28 +0000 Subject: Merge branch 'winh-remove-sushi' into 'master' Remove .raw from JavaScript fixture file names Closes #59201 See merge request gitlab-org/gitlab-ce!26430 (cherry picked from commit 79a45f7f02939a1c2fc1f378e43ec9b96630f918) --- spec/javascripts/activities_spec.js | 2 +- spec/javascripts/ajax_loading_spinner_spec.js | 2 +- spec/javascripts/awards_handler_spec.js | 4 +- spec/javascripts/behaviors/quick_submit_spec.js | 4 +- spec/javascripts/behaviors/requires_input_spec.js | 4 +- .../behaviors/shortcuts/shortcuts_issuable_spec.js | 2 +- .../balsamiq/balsamiq_viewer_integration_spec.js | 4 +- spec/javascripts/blob/blob_file_dropzone_spec.js | 4 +- spec/javascripts/blob/notebook/index_spec.js | 4 +- spec/javascripts/blob/pdf/index_spec.js | 4 +- spec/javascripts/blob/sketch/index_spec.js | 4 +- spec/javascripts/blob/viewer/index_spec.js | 4 +- spec/javascripts/boards/components/board_spec.js | 2 +- spec/javascripts/bootstrap_linked_tabs_spec.js | 4 +- .../ci_variable_list/ajax_variable_list_spec.js | 8 +- .../ci_variable_list/ci_variable_list_spec.js | 16 +-- .../native_form_variable_list_spec.js | 4 +- spec/javascripts/clusters/clusters_bundle_spec.js | 4 +- spec/javascripts/collapsed_sidebar_todo_spec.js | 2 +- spec/javascripts/create_item_dropdown_spec.js | 4 +- .../filtered_search/dropdown_user_spec.js | 2 +- .../filtered_search/dropdown_utils_spec.js | 2 +- spec/javascripts/fixtures/.gitignore | 1 + spec/javascripts/fixtures/abuse_reports.rb | 2 +- spec/javascripts/fixtures/admin_users.rb | 2 +- spec/javascripts/fixtures/application_settings.rb | 2 +- spec/javascripts/fixtures/blob.rb | 2 +- spec/javascripts/fixtures/boards.rb | 2 +- spec/javascripts/fixtures/branches.rb | 2 +- spec/javascripts/fixtures/clusters.rb | 2 +- spec/javascripts/fixtures/commit.rb | 2 +- spec/javascripts/fixtures/groups.rb | 4 +- spec/javascripts/fixtures/issues.rb | 10 +- spec/javascripts/fixtures/jobs.rb | 2 +- spec/javascripts/fixtures/merge_requests.rb | 10 +- spec/javascripts/fixtures/pipeline_schedules.rb | 4 +- spec/javascripts/fixtures/projects.rb | 10 +- spec/javascripts/fixtures/prometheus_service.rb | 2 +- spec/javascripts/fixtures/search.rb | 2 +- spec/javascripts/fixtures/services.rb | 2 +- spec/javascripts/fixtures/sessions.rb | 2 +- spec/javascripts/fixtures/snippet.rb | 2 +- .../fixtures/static/ajax_loading_spinner.html | 3 + .../fixtures/static/ajax_loading_spinner.html.raw | 3 - .../fixtures/static/balsamiq_viewer.html | 1 + .../fixtures/static/balsamiq_viewer.html.raw | 1 - .../fixtures/static/create_item_dropdown.html | 11 +++ .../fixtures/static/create_item_dropdown.html.raw | 11 --- spec/javascripts/fixtures/static/event_filter.html | 44 +++++++++ .../fixtures/static/event_filter.html.raw | 44 --------- spec/javascripts/fixtures/static/gl_dropdown.html | 26 +++++ .../fixtures/static/gl_dropdown.html.raw | 26 ----- .../fixtures/static/gl_field_errors.html | 22 +++++ .../fixtures/static/gl_field_errors.html.raw | 22 ----- .../fixtures/static/issuable_filter.html | 9 ++ .../fixtures/static/issuable_filter.html.raw | 9 -- .../fixtures/static/issue_sidebar_label.html | 26 +++++ .../fixtures/static/issue_sidebar_label.html.raw | 26 ----- .../fixtures/static/line_highlighter.html | 107 +++++++++++++++++++++ .../fixtures/static/line_highlighter.html.raw | 107 --------------------- spec/javascripts/fixtures/static/linked_tabs.html | 20 ++++ .../fixtures/static/linked_tabs.html.raw | 20 ---- .../fixtures/static/merge_requests_show.html | 15 +++ .../fixtures/static/merge_requests_show.html.raw | 15 --- .../fixtures/static/mini_dropdown_graph.html | 13 +++ .../fixtures/static/mini_dropdown_graph.html.raw | 13 --- .../fixtures/static/notebook_viewer.html | 1 + .../fixtures/static/notebook_viewer.html.raw | 1 - .../fixtures/static/oauth_remember_me.html | 6 ++ .../fixtures/static/oauth_remember_me.html.raw | 6 -- spec/javascripts/fixtures/static/pdf_viewer.html | 1 + .../fixtures/static/pdf_viewer.html.raw | 1 - .../fixtures/static/pipeline_graph.html | 24 +++++ .../fixtures/static/pipeline_graph.html.raw | 24 ----- spec/javascripts/fixtures/static/pipelines.html | 3 + .../javascripts/fixtures/static/pipelines.html.raw | 3 - .../static/project_select_combo_button.html | 9 ++ .../static/project_select_combo_button.html.raw | 9 -- .../fixtures/static/search_autocomplete.html | 15 +++ .../fixtures/static/search_autocomplete.html.raw | 15 --- spec/javascripts/fixtures/static/signin_tabs.html | 8 ++ .../fixtures/static/signin_tabs.html.raw | 8 -- .../javascripts/fixtures/static/sketch_viewer.html | 3 + .../fixtures/static/sketch_viewer.html.raw | 3 - spec/javascripts/fixtures/static_fixtures.rb | 2 +- spec/javascripts/fixtures/todos.rb | 2 +- spec/javascripts/fixtures/u2f.rb | 4 +- spec/javascripts/gl_dropdown_spec.js | 4 +- spec/javascripts/gl_field_errors_spec.js | 4 +- spec/javascripts/header_spec.js | 2 +- .../integrations/integration_settings_form_spec.js | 2 +- spec/javascripts/issue_spec.js | 10 +- spec/javascripts/labels_issue_sidebar_spec.js | 4 +- spec/javascripts/lazy_loader_spec.js | 6 +- spec/javascripts/line_highlighter_spec.js | 4 +- spec/javascripts/merge_request_spec.js | 8 +- spec/javascripts/merge_request_tabs_spec.js | 6 +- .../mini_pipeline_graph_dropdown_spec.js | 4 +- spec/javascripts/new_branch_spec.js | 4 +- spec/javascripts/notes_spec.js | 2 +- spec/javascripts/oauth_remember_me_spec.js | 4 +- .../admin/abuse_reports/abuse_reports_spec.js | 2 +- .../account_and_limits_spec.js | 2 +- .../pages/admin/users/new/index_spec.js | 2 +- .../sessions/new/preserve_url_fragment_spec.js | 4 +- spec/javascripts/pipelines_spec.js | 4 +- .../project_select_combo_button_spec.js | 2 +- .../prometheus_metrics/prometheus_metrics_spec.js | 2 +- spec/javascripts/read_more_spec.js | 2 +- spec/javascripts/right_sidebar_spec.js | 2 +- spec/javascripts/search_autocomplete_spec.js | 4 +- spec/javascripts/search_spec.js | 2 +- spec/javascripts/settings_panels_spec.js | 4 +- spec/javascripts/shortcuts_spec.js | 2 +- spec/javascripts/sidebar/sidebar_assignees_spec.js | 4 +- spec/javascripts/signin_tabs_memoizer_spec.js | 2 +- spec/javascripts/todos_spec.js | 4 +- spec/javascripts/u2f/authenticate_spec.js | 4 +- spec/javascripts/u2f/register_spec.js | 4 +- spec/javascripts/user_popovers_spec.js | 2 +- .../states/mr_widget_ready_to_merge_spec.js | 2 +- .../components/user_popover/user_popover_spec.js | 2 +- spec/javascripts/zen_mode_spec.js | 2 +- .../support/helpers/javascript_fixtures_helpers.rb | 2 +- 124 files changed, 513 insertions(+), 512 deletions(-) create mode 100644 spec/javascripts/fixtures/static/ajax_loading_spinner.html delete mode 100644 spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw create mode 100644 spec/javascripts/fixtures/static/balsamiq_viewer.html delete mode 100644 spec/javascripts/fixtures/static/balsamiq_viewer.html.raw create mode 100644 spec/javascripts/fixtures/static/create_item_dropdown.html delete mode 100644 spec/javascripts/fixtures/static/create_item_dropdown.html.raw create mode 100644 spec/javascripts/fixtures/static/event_filter.html delete mode 100644 spec/javascripts/fixtures/static/event_filter.html.raw create mode 100644 spec/javascripts/fixtures/static/gl_dropdown.html delete mode 100644 spec/javascripts/fixtures/static/gl_dropdown.html.raw create mode 100644 spec/javascripts/fixtures/static/gl_field_errors.html delete mode 100644 spec/javascripts/fixtures/static/gl_field_errors.html.raw create mode 100644 spec/javascripts/fixtures/static/issuable_filter.html delete mode 100644 spec/javascripts/fixtures/static/issuable_filter.html.raw create mode 100644 spec/javascripts/fixtures/static/issue_sidebar_label.html delete mode 100644 spec/javascripts/fixtures/static/issue_sidebar_label.html.raw create mode 100644 spec/javascripts/fixtures/static/line_highlighter.html delete mode 100644 spec/javascripts/fixtures/static/line_highlighter.html.raw create mode 100644 spec/javascripts/fixtures/static/linked_tabs.html delete mode 100644 spec/javascripts/fixtures/static/linked_tabs.html.raw create mode 100644 spec/javascripts/fixtures/static/merge_requests_show.html delete mode 100644 spec/javascripts/fixtures/static/merge_requests_show.html.raw create mode 100644 spec/javascripts/fixtures/static/mini_dropdown_graph.html delete mode 100644 spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw create mode 100644 spec/javascripts/fixtures/static/notebook_viewer.html delete mode 100644 spec/javascripts/fixtures/static/notebook_viewer.html.raw create mode 100644 spec/javascripts/fixtures/static/oauth_remember_me.html delete mode 100644 spec/javascripts/fixtures/static/oauth_remember_me.html.raw create mode 100644 spec/javascripts/fixtures/static/pdf_viewer.html delete mode 100644 spec/javascripts/fixtures/static/pdf_viewer.html.raw create mode 100644 spec/javascripts/fixtures/static/pipeline_graph.html delete mode 100644 spec/javascripts/fixtures/static/pipeline_graph.html.raw create mode 100644 spec/javascripts/fixtures/static/pipelines.html delete mode 100644 spec/javascripts/fixtures/static/pipelines.html.raw create mode 100644 spec/javascripts/fixtures/static/project_select_combo_button.html delete mode 100644 spec/javascripts/fixtures/static/project_select_combo_button.html.raw create mode 100644 spec/javascripts/fixtures/static/search_autocomplete.html delete mode 100644 spec/javascripts/fixtures/static/search_autocomplete.html.raw create mode 100644 spec/javascripts/fixtures/static/signin_tabs.html delete mode 100644 spec/javascripts/fixtures/static/signin_tabs.html.raw create mode 100644 spec/javascripts/fixtures/static/sketch_viewer.html delete mode 100644 spec/javascripts/fixtures/static/sketch_viewer.html.raw (limited to 'spec') diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js index 068b8eb65bc..23b6de7e4e0 100644 --- a/spec/javascripts/activities_spec.js +++ b/spec/javascripts/activities_spec.js @@ -7,7 +7,7 @@ import Pager from '~/pager'; describe('Activities', () => { window.gon || (window.gon = {}); - const fixtureTemplate = 'static/event_filter.html.raw'; + const fixtureTemplate = 'static/event_filter.html'; const filters = [ { id: 'all', diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js index 9389fc94f17..89195a4397f 100644 --- a/spec/javascripts/ajax_loading_spinner_spec.js +++ b/spec/javascripts/ajax_loading_spinner_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import AjaxLoadingSpinner from '~/ajax_loading_spinner'; describe('Ajax Loading Spinner', () => { - const fixtureTemplate = 'static/ajax_loading_spinner.html.raw'; + const fixtureTemplate = 'static/ajax_loading_spinner.html'; preloadFixtures(fixtureTemplate); beforeEach(() => { diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index e5b5707dcef..e10df1b45e7 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -24,13 +24,13 @@ const lazyAssert = function(done, assertFn) { describe('AwardsHandler', function() { const emojiData = getJSONFixture('emojis/emojis.json'); - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(function(done) { mock = new MockAdapter(axios); mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData); - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); loadAwardsHandler(true) .then(obj => { awardsHandler = obj; diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index 681463aab66..7af8c984841 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -4,10 +4,10 @@ import '~/behaviors/quick_submit'; describe('Quick Submit behavior', function() { const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options); - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(() => { - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); $('form').submit(e => { // Prevent a form submit from moving us off the testing page e.preventDefault(); diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js index 1bde2bb3024..617fe49b059 100644 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ b/spec/javascripts/behaviors/requires_input_spec.js @@ -3,10 +3,10 @@ import '~/behaviors/requires_input'; describe('requiresInput', () => { let submitButton; - preloadFixtures('branches/new_branch.html.raw'); + preloadFixtures('branches/new_branch.html'); beforeEach(() => { - loadFixtures('branches/new_branch.html.raw'); + loadFixtures('branches/new_branch.html'); submitButton = $('button[type="submit"]'); }); diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js index 4843a0386b5..5e457a4e823 100644 --- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js +++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js @@ -9,7 +9,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form'; describe('ShortcutsIssuable', function() { - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; preloadFixtures(fixtureName); beforeAll(done => { diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js index 5f027f59fcf..68b4f261617 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js @@ -6,10 +6,10 @@ describe('Balsamiq integration spec', () => { let endpoint; let balsamiqViewer; - preloadFixtures('static/balsamiq_viewer.html.raw'); + preloadFixtures('static/balsamiq_viewer.html'); beforeEach(() => { - loadFixtures('static/balsamiq_viewer.html.raw'); + loadFixtures('static/balsamiq_viewer.html'); container = document.getElementById('js-balsamiq-viewer'); balsamiqViewer = new BalsamiqViewer(container); diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js index 432d8a65b0a..cab06a0a9be 100644 --- a/spec/javascripts/blob/blob_file_dropzone_spec.js +++ b/spec/javascripts/blob/blob_file_dropzone_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import BlobFileDropzone from '~/blob/blob_file_dropzone'; describe('BlobFileDropzone', function() { - preloadFixtures('blob/show.html.raw'); + preloadFixtures('blob/show.html'); beforeEach(() => { - loadFixtures('blob/show.html.raw'); + loadFixtures('blob/show.html'); const form = $('.js-upload-blob-form'); this.blobFileDropzone = new BlobFileDropzone(form, 'POST'); this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone; diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index 28d3b2f5ea3..6bb5bac007f 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -3,10 +3,10 @@ import axios from '~/lib/utils/axios_utils'; import renderNotebook from '~/blob/notebook'; describe('iPython notebook renderer', () => { - preloadFixtures('static/notebook_viewer.html.raw'); + preloadFixtures('static/notebook_viewer.html'); beforeEach(() => { - loadFixtures('static/notebook_viewer.html.raw'); + loadFixtures('static/notebook_viewer.html'); }); it('shows loading icon', () => { diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index be917a0613f..acf87580777 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -15,10 +15,10 @@ describe('PDF renderer', () => { } }; - preloadFixtures('static/pdf_viewer.html.raw'); + preloadFixtures('static/pdf_viewer.html'); beforeEach(() => { - loadFixtures('static/pdf_viewer.html.raw'); + loadFixtures('static/pdf_viewer.html'); viewer = document.getElementById('js-pdf-viewer'); viewer.dataset.endpoint = testPDF; }); diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js index 2b1e81e9cbc..3d3129e10da 100644 --- a/spec/javascripts/blob/sketch/index_spec.js +++ b/spec/javascripts/blob/sketch/index_spec.js @@ -13,10 +13,10 @@ describe('Sketch viewer', () => { }); }; - preloadFixtures('static/sketch_viewer.html.raw'); + preloadFixtures('static/sketch_viewer.html'); beforeEach(() => { - loadFixtures('static/sketch_viewer.html.raw'); + loadFixtures('static/sketch_viewer.html'); }); describe('with error message', () => { diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index 93a942fe8d4..4ac15ca5aa2 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -9,12 +9,12 @@ describe('Blob viewer', () => { let blob; let mock; - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(() => { mock = new MockAdapter(axios); - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); $('#modal-upload-blob').remove(); blob = new BlobViewer(); diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index dee7841c088..6e6b3e6950b 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -9,7 +9,7 @@ describe('Board component', () => { let el; beforeEach(done => { - loadFixtures('boards/show.html.raw'); + loadFixtures('boards/show.html'); el = document.createElement('div'); document.body.appendChild(el); diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js index c3e3d78ff63..1d21637ceae 100644 --- a/spec/javascripts/bootstrap_linked_tabs_spec.js +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js @@ -1,10 +1,10 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; describe('Linked Tabs', () => { - preloadFixtures('static/linked_tabs.html.raw'); + preloadFixtures('static/linked_tabs.html'); beforeEach(() => { - loadFixtures('static/linked_tabs.html.raw'); + loadFixtures('static/linked_tabs.html'); }); describe('when is initialized', () => { diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 1fc0e206d5e..481b1a4d4b0 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -7,8 +7,8 @@ const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-proje const HIDE_CLASS = 'hide'; describe('AjaxFormVariableList', () => { - preloadFixtures('projects/ci_cd_settings.html.raw'); - preloadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + preloadFixtures('projects/ci_cd_settings.html'); + preloadFixtures('projects/ci_cd_settings_with_variables.html'); let container; let saveButton; @@ -18,7 +18,7 @@ describe('AjaxFormVariableList', () => { let ajaxVariableList; beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); container = document.querySelector('.js-ci-variable-list-section'); mock = new MockAdapter(axios); @@ -168,7 +168,7 @@ describe('AjaxFormVariableList', () => { describe('updateRowsWithPersistedVariables', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + loadFixtures('projects/ci_cd_settings_with_variables.html'); container = document.querySelector('.js-ci-variable-list-section'); const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section'); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index bef59b86d0c..70f49469300 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -5,9 +5,9 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; const HIDE_CLASS = 'hide'; describe('VariableList', () => { - preloadFixtures('pipeline_schedules/edit.html.raw'); - preloadFixtures('pipeline_schedules/edit_with_variables.html.raw'); - preloadFixtures('projects/ci_cd_settings.html.raw'); + preloadFixtures('pipeline_schedules/edit.html'); + preloadFixtures('pipeline_schedules/edit_with_variables.html'); + preloadFixtures('projects/ci_cd_settings.html'); let $wrapper; let variableList; @@ -15,7 +15,7 @@ describe('VariableList', () => { describe('with only key/value inputs', () => { describe('with no variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html.raw'); + loadFixtures('pipeline_schedules/edit.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -82,7 +82,7 @@ describe('VariableList', () => { describe('with persisted variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + loadFixtures('pipeline_schedules/edit_with_variables.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -115,7 +115,7 @@ describe('VariableList', () => { describe('with all inputs(key, value, protected)', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); $wrapper = $('.js-ci-variable-list-section'); $wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false'); @@ -149,7 +149,7 @@ describe('VariableList', () => { describe('toggleEnableRow method', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + loadFixtures('pipeline_schedules/edit_with_variables.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -198,7 +198,7 @@ describe('VariableList', () => { describe('hideValues', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js index 997d0d54d79..4982b68fa81 100644 --- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js @@ -2,12 +2,12 @@ import $ from 'jquery'; import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list'; describe('NativeFormVariableList', () => { - preloadFixtures('pipeline_schedules/edit.html.raw'); + preloadFixtures('pipeline_schedules/edit.html'); let $wrapper; beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html.raw'); + loadFixtures('pipeline_schedules/edit.html'); $wrapper = $('.js-ci-variable-list-section'); setupNativeFormVariableList({ diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 71f16dc259e..0d3dcc29f22 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -9,10 +9,10 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; describe('Clusters', () => { let cluster; - preloadFixtures('clusters/show_cluster.html.raw'); + preloadFixtures('clusters/show_cluster.html'); beforeEach(() => { - loadFixtures('clusters/show_cluster.html.raw'); + loadFixtures('clusters/show_cluster.html'); cluster = new Clusters(); }); diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js index dc5737558c0..bb90e53e525 100644 --- a/spec/javascripts/collapsed_sidebar_todo_spec.js +++ b/spec/javascripts/collapsed_sidebar_todo_spec.js @@ -6,7 +6,7 @@ import Sidebar from '~/right_sidebar'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Issuable right sidebar collapsed todo toggle', () => { - const fixtureName = 'issues/open-issue.html.raw'; + const fixtureName = 'issues/open-issue.html'; const jsonFixtureName = 'todos/todos.json'; let mock; diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js index 9cf72d7c55b..a814952faab 100644 --- a/spec/javascripts/create_item_dropdown_spec.js +++ b/spec/javascripts/create_item_dropdown_spec.js @@ -20,7 +20,7 @@ const DROPDOWN_ITEM_DATA = [ ]; describe('CreateItemDropdown', () => { - preloadFixtures('static/create_item_dropdown.html.raw'); + preloadFixtures('static/create_item_dropdown.html'); let $wrapperEl; let createItemDropdown; @@ -44,7 +44,7 @@ describe('CreateItemDropdown', () => { } beforeEach(() => { - loadFixtures('static/create_item_dropdown.html.raw'); + loadFixtures('static/create_item_dropdown.html'); $wrapperEl = $('.js-create-item-dropdown-fixture-root'); }); diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index e8fcc8592eb..f764800fff0 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -72,7 +72,7 @@ describe('Dropdown User', () => { }); describe('hideCurrentUser', () => { - const fixtureTemplate = 'issues/issue_list.html.raw'; + const fixtureTemplate = 'issues/issue_list.html'; preloadFixtures(fixtureTemplate); let dropdown; diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index cfd0b96ec43..62d1bd69635 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -4,7 +4,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; describe('Dropdown Utils', () => { - const issueListFixture = 'issues/issue_list.html.raw'; + const issueListFixture = 'issues/issue_list.html'; preloadFixtures(issueListFixture); describe('getEscapedText', () => { diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore index 0c35cdd778e..2507c8e7263 100644 --- a/spec/javascripts/fixtures/.gitignore +++ b/spec/javascripts/fixtures/.gitignore @@ -1,2 +1,3 @@ *.html.raw +*.html *.json diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb index 387858cba77..54b6419bcdb 100644 --- a/spec/javascripts/fixtures/abuse_reports.rb +++ b/spec/javascripts/fixtures/abuse_reports.rb @@ -18,7 +18,7 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll sign_in(admin) end - it 'abuse_reports/abuse_reports_list.html.raw' do |example| + it 'abuse_reports/abuse_reports_list.html' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb index 9989ac4fff2..76dbdf603da 100644 --- a/spec/javascripts/fixtures/admin_users.rb +++ b/spec/javascripts/fixtures/admin_users.rb @@ -17,7 +17,7 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('admin/users') end - it 'admin/users/new_with_internal_user_regex.html.raw' do |example| + it 'admin/users/new_with_internal_user_regex.html' do |example| stub_application_setting(user_default_external: true) stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?') diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb index a9d3043f73d..c535e598e12 100644 --- a/spec/javascripts/fixtures/application_settings.rb +++ b/spec/javascripts/fixtures/application_settings.rb @@ -23,7 +23,7 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c remove_repository(project) end - it 'application_settings/accounts_and_limit.html.raw' do |example| + it 'application_settings/accounts_and_limit.html' do |example| stub_application_setting(user_default_external: false) get :show diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index cd66d98f92a..db7749bc000 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -22,7 +22,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'blob/show.html.raw' do |example| + it 'blob/show.html' do |example| get(:show, params: { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb index 1d675e008ba..c4390e89578 100644 --- a/spec/javascripts/fixtures/boards.rb +++ b/spec/javascripts/fixtures/boards.rb @@ -17,7 +17,7 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller sign_in(admin) end - it 'boards/show.html.raw' do |example| + it 'boards/show.html' do |example| get(:index, params: { namespace_id: project.namespace, project_id: project diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb index 3cc713ef90f..5d2d6c7ec0e 100644 --- a/spec/javascripts/fixtures/branches.rb +++ b/spec/javascripts/fixtures/branches.rb @@ -21,7 +21,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'branches/new_branch.html.raw' do |example| + it 'branches/new_branch.html' do |example| get :new, params: { namespace_id: project.namespace.to_param, project_id: project diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 69dbe54ffc2..8ebd8a41366 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -22,7 +22,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'clusters/show_cluster.html.raw' do |example| + it 'clusters/show_cluster.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index 295f13b34a4..ab10f559e4b 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -19,7 +19,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end - it 'commit/show.html.raw' do |example| + it 'commit/show.html' do |example| params = { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index 03136f4e661..16e31028b05 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -18,7 +18,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe GroupsController, '(JavaScript fixtures)', type: :controller do - it 'groups/edit.html.raw' do |example| + it 'groups/edit.html' do |example| get :edit, params: { id: group } expect(response).to be_success @@ -27,7 +27,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'groups/ci_cd_settings.html.raw' do |example| + it 'groups/ci_cd_settings.html' do |example| get :show, params: { group_id: group } expect(response).to be_success diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 9b8e90c2a43..645b3aa788a 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -21,26 +21,26 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller remove_repository(project) end - it 'issues/open-issue.html.raw' do |example| + it 'issues/open-issue.html' do |example| render_issue(example.description, create(:issue, project: project)) end - it 'issues/closed-issue.html.raw' do |example| + it 'issues/closed-issue.html' do |example| render_issue(example.description, create(:closed_issue, project: project)) end - it 'issues/issue-with-task-list.html.raw' do |example| + it 'issues/issue-with-task-list.html' do |example| issue = create(:issue, project: project, description: '- [ ] Task List Item') render_issue(example.description, issue) end - it 'issues/issue_with_comment.html.raw' do |example| + it 'issues/issue_with_comment.html' do |example| issue = create(:issue, project: project) create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save render_issue(example.description, issue) end - it 'issues/issue_list.html.raw' do |example| + it 'issues/issue_list.html' do |example| create(:issue, project: project) get :index, params: { diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 433bb690a1c..941235190b5 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -32,7 +32,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'builds/build-with-artifacts.html.raw' do |example| + it 'builds/build-with-artifacts.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index eb37be87e1d..7df1e5cb512 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -42,19 +42,19 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont remove_repository(project) end - it 'merge_requests/merge_request_of_current_user.html.raw' do |example| + it 'merge_requests/merge_request_of_current_user.html' do |example| merge_request.update(author: admin) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_task_list.html.raw' do |example| + it 'merge_requests/merge_request_with_task_list.html' do |example| create(:ci_build, :pending, pipeline: pipeline) render_merge_request(example.description, merge_request) end - it 'merge_requests/merged_merge_request.html.raw' do |example| + it 'merge_requests/merged_merge_request.html' do |example| expect_next_instance_of(MergeRequest) do |merge_request| allow(merge_request).to receive(:source_branch_exists?).and_return(true) allow(merge_request).to receive(:can_remove_source_branch?).and_return(true) @@ -62,13 +62,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merged_merge_request) end - it 'merge_requests/diff_comment.html.raw' do |example| + it 'merge_requests/diff_comment.html' do |example| create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_comment.html.raw' do |example| + it 'merge_requests/merge_request_with_comment.html' do |example| create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item') render_merge_request(example.description, merge_request) end diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb index 05d79ec8de9..e5176a58273 100644 --- a/spec/javascripts/fixtures/pipeline_schedules.rb +++ b/spec/javascripts/fixtures/pipeline_schedules.rb @@ -21,7 +21,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : sign_in(admin) end - it 'pipeline_schedules/edit.html.raw' do |example| + it 'pipeline_schedules/edit.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, @@ -32,7 +32,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : store_frontend_fixture(response, example.description) end - it 'pipeline_schedules/edit_with_variables.html.raw' do |example| + it 'pipeline_schedules/edit_with_variables.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 85f02923804..446da83a7f9 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -28,7 +28,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe ProjectsController, '(JavaScript fixtures)', type: :controller do - it 'projects/dashboard.html.raw' do |example| + it 'projects/dashboard.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, id: project @@ -38,7 +38,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/overview.html.raw' do |example| + it 'projects/overview.html' do |example| get :show, params: { namespace_id: project_with_repo.namespace.to_param, id: project_with_repo @@ -48,7 +48,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/edit.html.raw' do |example| + it 'projects/edit.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, id: project @@ -60,7 +60,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'projects/ci_cd_settings.html.raw' do |example| + it 'projects/ci_cd_settings.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project @@ -70,7 +70,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/ci_cd_settings_with_variables.html.raw' do |example| + it 'projects/ci_cd_settings_with_variables.html' do |example| create(:ci_variable, project: project_variable_populated) create(:ci_variable, project: project_variable_populated) diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb index 746fbfd66dd..29dc95305b7 100644 --- a/spec/javascripts/fixtures/prometheus_service.rb +++ b/spec/javascripts/fixtures/prometheus_service.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/prometheus/prometheus_service.html.raw' do |example| + it 'services/prometheus/prometheus_service.html' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb index 703cd3d49fa..5f5b4d4e60d 100644 --- a/spec/javascripts/fixtures/search.rb +++ b/spec/javascripts/fixtures/search.rb @@ -9,7 +9,7 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('search/') end - it 'search/show.html.raw' do |example| + it 'search/show.html' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb index 6ccd74a07ff..dc7ee484c22 100644 --- a/spec/javascripts/fixtures/services.rb +++ b/spec/javascripts/fixtures/services.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/edit_service.html.raw' do |example| + it 'services/edit_service.html' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb index e90a58e8c54..8656dea696a 100644 --- a/spec/javascripts/fixtures/sessions.rb +++ b/spec/javascripts/fixtures/sessions.rb @@ -16,7 +16,7 @@ describe 'Sessions (JavaScript fixtures)' do set_devise_mapping(context: @request) end - it 'sessions/new.html.raw' do |example| + it 'sessions/new.html' do |example| get :new expect(response).to be_success diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index bcd6546f3df..ebc5b793166 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -23,7 +23,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'snippets/show.html.raw' do |example| + it 'snippets/show.html' do |example| create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') get(:show, params: { id: snippet.to_param }) diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html b/spec/javascripts/fixtures/static/ajax_loading_spinner.html new file mode 100644 index 00000000000..0e1ebb32b1c --- /dev/null +++ b/spec/javascripts/fixtures/static/ajax_loading_spinner.html @@ -0,0 +1,3 @@ + + + diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw b/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw deleted file mode 100644 index 0e1ebb32b1c..00000000000 --- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html b/spec/javascripts/fixtures/static/balsamiq_viewer.html new file mode 100644 index 00000000000..cdd723d1a84 --- /dev/null +++ b/spec/javascripts/fixtures/static/balsamiq_viewer.html @@ -0,0 +1 @@ +
      diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw b/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw deleted file mode 100644 index cdd723d1a84..00000000000 --- a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw +++ /dev/null @@ -1 +0,0 @@ -
      diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html b/spec/javascripts/fixtures/static/create_item_dropdown.html new file mode 100644 index 00000000000..d2d38370092 --- /dev/null +++ b/spec/javascripts/fixtures/static/create_item_dropdown.html @@ -0,0 +1,11 @@ +
      + +
      diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw b/spec/javascripts/fixtures/static/create_item_dropdown.html.raw deleted file mode 100644 index d2d38370092..00000000000 --- a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw +++ /dev/null @@ -1,11 +0,0 @@ -
      - -
      diff --git a/spec/javascripts/fixtures/static/event_filter.html b/spec/javascripts/fixtures/static/event_filter.html new file mode 100644 index 00000000000..8e9b6fb1b5c --- /dev/null +++ b/spec/javascripts/fixtures/static/event_filter.html @@ -0,0 +1,44 @@ + diff --git a/spec/javascripts/fixtures/static/event_filter.html.raw b/spec/javascripts/fixtures/static/event_filter.html.raw deleted file mode 100644 index 8e9b6fb1b5c..00000000000 --- a/spec/javascripts/fixtures/static/event_filter.html.raw +++ /dev/null @@ -1,44 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html b/spec/javascripts/fixtures/static/gl_dropdown.html new file mode 100644 index 00000000000..08f6738414e --- /dev/null +++ b/spec/javascripts/fixtures/static/gl_dropdown.html @@ -0,0 +1,26 @@ +
      + +
      diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html.raw b/spec/javascripts/fixtures/static/gl_dropdown.html.raw deleted file mode 100644 index 08f6738414e..00000000000 --- a/spec/javascripts/fixtures/static/gl_dropdown.html.raw +++ /dev/null @@ -1,26 +0,0 @@ -
      - -
      diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html b/spec/javascripts/fixtures/static/gl_field_errors.html new file mode 100644 index 00000000000..f8470e02b7c --- /dev/null +++ b/spec/javascripts/fixtures/static/gl_field_errors.html @@ -0,0 +1,22 @@ +
      +
      +Text +
      +
      +Email +
      +
      +Password +
      +
      +Alphanumeric +
      +
      + +
      +
      +Custom, do not validate +
      +
      +Submit +
      diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html.raw b/spec/javascripts/fixtures/static/gl_field_errors.html.raw deleted file mode 100644 index f8470e02b7c..00000000000 --- a/spec/javascripts/fixtures/static/gl_field_errors.html.raw +++ /dev/null @@ -1,22 +0,0 @@ -
      -
      -Text -
      -
      -Email -
      -
      -Password -
      -
      -Alphanumeric -
      -
      - -
      -
      -Custom, do not validate -
      -
      -Submit -
      diff --git a/spec/javascripts/fixtures/static/issuable_filter.html b/spec/javascripts/fixtures/static/issuable_filter.html new file mode 100644 index 00000000000..06b70fb43f1 --- /dev/null +++ b/spec/javascripts/fixtures/static/issuable_filter.html @@ -0,0 +1,9 @@ +
      + + + + + + + +
      diff --git a/spec/javascripts/fixtures/static/issuable_filter.html.raw b/spec/javascripts/fixtures/static/issuable_filter.html.raw deleted file mode 100644 index 06b70fb43f1..00000000000 --- a/spec/javascripts/fixtures/static/issuable_filter.html.raw +++ /dev/null @@ -1,9 +0,0 @@ -
      - - - - - - - -
      diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html b/spec/javascripts/fixtures/static/issue_sidebar_label.html new file mode 100644 index 00000000000..ec8fb30f219 --- /dev/null +++ b/spec/javascripts/fixtures/static/issue_sidebar_label.html @@ -0,0 +1,26 @@ +
      + + + +
      diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw b/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw deleted file mode 100644 index ec8fb30f219..00000000000 --- a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw +++ /dev/null @@ -1,26 +0,0 @@ -
      - - - -
      diff --git a/spec/javascripts/fixtures/static/line_highlighter.html b/spec/javascripts/fixtures/static/line_highlighter.html new file mode 100644 index 00000000000..897a25d6760 --- /dev/null +++ b/spec/javascripts/fixtures/static/line_highlighter.html @@ -0,0 +1,107 @@ +
      +
      + +
      Line 1Line 2Line 3Line 4Line 5Line 6Line 7Line 8Line 9Line 10Line 11Line 12Line 13Line 14Line 15Line 16Line 17Line 18Line 19Line 20Line 21Line 22Line 23Line 24Line 25
      +
      +
      diff --git a/spec/javascripts/fixtures/static/line_highlighter.html.raw b/spec/javascripts/fixtures/static/line_highlighter.html.raw deleted file mode 100644 index 897a25d6760..00000000000 --- a/spec/javascripts/fixtures/static/line_highlighter.html.raw +++ /dev/null @@ -1,107 +0,0 @@ -
      -
      - -
      Line 1Line 2Line 3Line 4Line 5Line 6Line 7Line 8Line 9Line 10Line 11Line 12Line 13Line 14Line 15Line 16Line 17Line 18Line 19Line 20Line 21Line 22Line 23Line 24Line 25
      -
      -
      diff --git a/spec/javascripts/fixtures/static/linked_tabs.html b/spec/javascripts/fixtures/static/linked_tabs.html new file mode 100644 index 00000000000..c25463bf1db --- /dev/null +++ b/spec/javascripts/fixtures/static/linked_tabs.html @@ -0,0 +1,20 @@ + +
      +
      +Tab 1 Content +
      +
      +Tab 2 Content +
      +
      diff --git a/spec/javascripts/fixtures/static/linked_tabs.html.raw b/spec/javascripts/fixtures/static/linked_tabs.html.raw deleted file mode 100644 index c25463bf1db..00000000000 --- a/spec/javascripts/fixtures/static/linked_tabs.html.raw +++ /dev/null @@ -1,20 +0,0 @@ - -
      -
      -Tab 1 Content -
      -
      -Tab 2 Content -
      -
      diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html b/spec/javascripts/fixtures/static/merge_requests_show.html new file mode 100644 index 00000000000..e219d9462aa --- /dev/null +++ b/spec/javascripts/fixtures/static/merge_requests_show.html @@ -0,0 +1,15 @@ + +
      +
      +
      +
        +
      • + +Task List Item +
      • +
      + +
      +
      +
      +
      diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html.raw b/spec/javascripts/fixtures/static/merge_requests_show.html.raw deleted file mode 100644 index e219d9462aa..00000000000 --- a/spec/javascripts/fixtures/static/merge_requests_show.html.raw +++ /dev/null @@ -1,15 +0,0 @@ - -
      -
      -
      -
        -
      • - -Task List Item -
      • -
      - -
      -
      -
      -
      diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html b/spec/javascripts/fixtures/static/mini_dropdown_graph.html new file mode 100644 index 00000000000..cd0b8dec3fc --- /dev/null +++ b/spec/javascripts/fixtures/static/mini_dropdown_graph.html @@ -0,0 +1,13 @@ + diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw b/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw deleted file mode 100644 index cd0b8dec3fc..00000000000 --- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html b/spec/javascripts/fixtures/static/notebook_viewer.html new file mode 100644 index 00000000000..4bbb7bf1094 --- /dev/null +++ b/spec/javascripts/fixtures/static/notebook_viewer.html @@ -0,0 +1 @@ +
      diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html.raw b/spec/javascripts/fixtures/static/notebook_viewer.html.raw deleted file mode 100644 index 4bbb7bf1094..00000000000 --- a/spec/javascripts/fixtures/static/notebook_viewer.html.raw +++ /dev/null @@ -1 +0,0 @@ -
      diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html b/spec/javascripts/fixtures/static/oauth_remember_me.html new file mode 100644 index 00000000000..9ba1ffc72fe --- /dev/null +++ b/spec/javascripts/fixtures/static/oauth_remember_me.html @@ -0,0 +1,6 @@ +
      + + + + +
      diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw b/spec/javascripts/fixtures/static/oauth_remember_me.html.raw deleted file mode 100644 index 9ba1ffc72fe..00000000000 --- a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw +++ /dev/null @@ -1,6 +0,0 @@ -
      - - - - -
      diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html b/spec/javascripts/fixtures/static/pdf_viewer.html new file mode 100644 index 00000000000..350d35a262f --- /dev/null +++ b/spec/javascripts/fixtures/static/pdf_viewer.html @@ -0,0 +1 @@ +
      diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html.raw b/spec/javascripts/fixtures/static/pdf_viewer.html.raw deleted file mode 100644 index 350d35a262f..00000000000 --- a/spec/javascripts/fixtures/static/pdf_viewer.html.raw +++ /dev/null @@ -1 +0,0 @@ -
      diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html b/spec/javascripts/fixtures/static/pipeline_graph.html new file mode 100644 index 00000000000..422372bb7d5 --- /dev/null +++ b/spec/javascripts/fixtures/static/pipeline_graph.html @@ -0,0 +1,24 @@ + diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html.raw b/spec/javascripts/fixtures/static/pipeline_graph.html.raw deleted file mode 100644 index 422372bb7d5..00000000000 --- a/spec/javascripts/fixtures/static/pipeline_graph.html.raw +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/pipelines.html b/spec/javascripts/fixtures/static/pipelines.html new file mode 100644 index 00000000000..42333f94f2f --- /dev/null +++ b/spec/javascripts/fixtures/static/pipelines.html @@ -0,0 +1,3 @@ +
      +
      +
      diff --git a/spec/javascripts/fixtures/static/pipelines.html.raw b/spec/javascripts/fixtures/static/pipelines.html.raw deleted file mode 100644 index 42333f94f2f..00000000000 --- a/spec/javascripts/fixtures/static/pipelines.html.raw +++ /dev/null @@ -1,3 +0,0 @@ -
      -
      -
      diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html b/spec/javascripts/fixtures/static/project_select_combo_button.html new file mode 100644 index 00000000000..50c826051c0 --- /dev/null +++ b/spec/javascripts/fixtures/static/project_select_combo_button.html @@ -0,0 +1,9 @@ +
      + + + + + + + +
      diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw b/spec/javascripts/fixtures/static/project_select_combo_button.html.raw deleted file mode 100644 index 50c826051c0..00000000000 --- a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw +++ /dev/null @@ -1,9 +0,0 @@ -
      - - - - - - - -
      diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html b/spec/javascripts/fixtures/static/search_autocomplete.html new file mode 100644 index 00000000000..29db9020424 --- /dev/null +++ b/spec/javascripts/fixtures/static/search_autocomplete.html @@ -0,0 +1,15 @@ + diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html.raw b/spec/javascripts/fixtures/static/search_autocomplete.html.raw deleted file mode 100644 index 29db9020424..00000000000 --- a/spec/javascripts/fixtures/static/search_autocomplete.html.raw +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/signin_tabs.html b/spec/javascripts/fixtures/static/signin_tabs.html new file mode 100644 index 00000000000..7e66ab9394b --- /dev/null +++ b/spec/javascripts/fixtures/static/signin_tabs.html @@ -0,0 +1,8 @@ + diff --git a/spec/javascripts/fixtures/static/signin_tabs.html.raw b/spec/javascripts/fixtures/static/signin_tabs.html.raw deleted file mode 100644 index 7e66ab9394b..00000000000 --- a/spec/javascripts/fixtures/static/signin_tabs.html.raw +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html b/spec/javascripts/fixtures/static/sketch_viewer.html new file mode 100644 index 00000000000..e25e554e568 --- /dev/null +++ b/spec/javascripts/fixtures/static/sketch_viewer.html @@ -0,0 +1,3 @@ +
      +
      +
      diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html.raw b/spec/javascripts/fixtures/static/sketch_viewer.html.raw deleted file mode 100644 index e25e554e568..00000000000 --- a/spec/javascripts/fixtures/static/sketch_viewer.html.raw +++ /dev/null @@ -1,3 +0,0 @@ -
      -
      -
      diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb index b5188eeb994..cb4b90cdca5 100644 --- a/spec/javascripts/fixtures/static_fixtures.rb +++ b/spec/javascripts/fixtures/static_fixtures.rb @@ -4,7 +4,7 @@ describe ApplicationController, '(Static JavaScript fixtures)', type: :controlle include JavaScriptFixturesHelpers Dir.glob('{,ee/}spec/javascripts/fixtures/**/*.haml').map do |file_path| - it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '.raw')}" do |example| + it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '')}" do |example| store_frontend_fixture(render_template(file_path), example.description) end end diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb index b5f6620873b..6e37a2e5a4c 100644 --- a/spec/javascripts/fixtures/todos.rb +++ b/spec/javascripts/fixtures/todos.rb @@ -26,7 +26,7 @@ describe 'Todos (JavaScript fixtures)' do sign_in(admin) end - it 'todos/todos.html.raw' do |example| + it 'todos/todos.html' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index 5cdbadef639..15866d65a4f 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -18,7 +18,7 @@ context 'U2F' do set_devise_mapping(context: @request) end - it 'u2f/authenticate.html.raw' do |example| + it 'u2f/authenticate.html' do |example| allow(controller).to receive(:find_user).and_return(user) post :create, params: { user: { login: user.username, password: user.password } } @@ -36,7 +36,7 @@ context 'U2F' do allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares') end - it 'u2f/register.html.raw' do |example| + it 'u2f/register.html' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 85083653db8..57e31d933ca 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -5,7 +5,7 @@ import GLDropdown from '~/gl_dropdown'; import '~/lib/utils/common_utils'; describe('glDropdown', function describeDropdown() { - preloadFixtures('static/gl_dropdown.html.raw'); + preloadFixtures('static/gl_dropdown.html'); loadJSONFixtures('projects.json'); const NON_SELECTABLE_CLASSES = @@ -64,7 +64,7 @@ describe('glDropdown', function describeDropdown() { } beforeEach(() => { - loadFixtures('static/gl_dropdown.html.raw'); + loadFixtures('static/gl_dropdown.html'); this.dropdownContainerElement = $('.dropdown.inline'); this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); this.projectsData = getJSONFixture('projects.json'); diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js index b463c9afbee..294f219d6fe 100644 --- a/spec/javascripts/gl_field_errors_spec.js +++ b/spec/javascripts/gl_field_errors_spec.js @@ -4,10 +4,10 @@ import $ from 'jquery'; import GlFieldErrors from '~/gl_field_errors'; describe('GL Style Field Errors', function() { - preloadFixtures('static/gl_field_errors.html.raw'); + preloadFixtures('static/gl_field_errors.html'); beforeEach(function() { - loadFixtures('static/gl_field_errors.html.raw'); + loadFixtures('static/gl_field_errors.html'); const $form = $('form.gl-show-field-errors'); this.$form = $form; diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 2fe34e5a76f..0ddf589f368 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -3,7 +3,7 @@ import initTodoToggle from '~/header'; describe('Header', function() { const todosPendingCount = '.todos-count'; - const fixtureTemplate = 'issues/open-issue.html.raw'; + const fixtureTemplate = 'issues/open-issue.html'; function isTodosCountHidden() { return $(todosPendingCount).hasClass('hidden'); diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 4f4c9a7b463..069e2cb07b5 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils'; import IntegrationSettingsForm from '~/integrations/integration_settings_form'; describe('IntegrationSettingsForm', () => { - const FIXTURE = 'services/edit_service.html.raw'; + const FIXTURE = 'services/edit_service.html'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 7be495d1d35..11ab6c38a55 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -9,9 +9,9 @@ import '~/lib/utils/text_utility'; describe('Issue', function() { let $boxClosed, $boxOpen, $btn; - preloadFixtures('issues/closed-issue.html.raw'); - preloadFixtures('issues/issue-with-task-list.html.raw'); - preloadFixtures('issues/open-issue.html.raw'); + preloadFixtures('issues/closed-issue.html'); + preloadFixtures('issues/issue-with-task-list.html'); + preloadFixtures('issues/open-issue.html'); function expectErrorMessage() { const $flashMessage = $('div.flash-alert'); @@ -105,9 +105,9 @@ describe('Issue', function() { beforeEach(function() { if (isIssueInitiallyOpen) { - loadFixtures('issues/open-issue.html.raw'); + loadFixtures('issues/open-issue.html'); } else { - loadFixtures('issues/closed-issue.html.raw'); + loadFixtures('issues/closed-issue.html'); } mock = new MockAdapter(axios); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index e5678ee5379..ccf439aac74 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -16,10 +16,10 @@ let saveLabelCount = 0; let mock; describe('Issue dropdown sidebar', () => { - preloadFixtures('static/issue_sidebar_label.html.raw'); + preloadFixtures('static/issue_sidebar_label.html'); beforeEach(() => { - loadFixtures('static/issue_sidebar_label.html.raw'); + loadFixtures('static/issue_sidebar_label.html'); mock = new MockAdapter(axios); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index cbdc1644430..f3fb792c62d 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -11,11 +11,11 @@ const execImmediately = callback => { describe('LazyLoader', function() { let lazyLoader = null; - preloadFixtures('issues/issue_with_comment.html.raw'); + preloadFixtures('issues/issue_with_comment.html'); describe('without IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html.raw'); + loadFixtures('issues/issue_with_comment.html'); lazyLoader = new LazyLoader({ observerNode: 'foobar', @@ -131,7 +131,7 @@ describe('LazyLoader', function() { describe('with IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html.raw'); + loadFixtures('issues/issue_with_comment.html'); lazyLoader = new LazyLoader({ observerNode: 'foobar', diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index 4eea364bd69..a75470b4db8 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -5,7 +5,7 @@ import LineHighlighter from '~/line_highlighter'; describe('LineHighlighter', function() { var clickLine; - preloadFixtures('static/line_highlighter.html.raw'); + preloadFixtures('static/line_highlighter.html'); clickLine = function(number, eventData = {}) { if ($.isEmptyObject(eventData)) { return $('#L' + number).click(); @@ -15,7 +15,7 @@ describe('LineHighlighter', function() { } }; beforeEach(function() { - loadFixtures('static/line_highlighter.html.raw'); + loadFixtures('static/line_highlighter.html'); this['class'] = new LineHighlighter(); this.css = this['class'].highlightLineClass; return (this.spies = { diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index ab809930804..431798c6ec3 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -11,9 +11,9 @@ describe('MergeRequest', function() { describe('task lists', function() { let mock; - preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + preloadFixtures('merge_requests/merge_request_with_task_list.html'); beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); spyOn(axios, 'patch').and.callThrough(); mock = new MockAdapter(axios); @@ -125,7 +125,7 @@ describe('MergeRequest', function() { describe('hideCloseButton', () => { describe('merge request of another user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); this.el = document.querySelector('.js-issuable-actions'); new MergeRequest(); // eslint-disable-line no-new MergeRequest.hideCloseButton(); @@ -145,7 +145,7 @@ describe('MergeRequest', function() { describe('merge request of current_user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + loadFixtures('merge_requests/merge_request_of_current_user.html'); this.el = document.querySelector('.js-issuable-actions'); MergeRequest.hideCloseButton(); }); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index c8df05eccf5..1295d900de7 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -22,8 +22,8 @@ describe('MergeRequestTabs', function() { }; preloadFixtures( - 'merge_requests/merge_request_with_task_list.html.raw', - 'merge_requests/diff_comment.html.raw', + 'merge_requests/merge_request_with_task_list.html', + 'merge_requests/diff_comment.html', ); beforeEach(function() { @@ -48,7 +48,7 @@ describe('MergeRequestTabs', function() { var windowTarget = '_blank'; beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); tabUrl = $('.commits-tab a').attr('href'); }); diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js index 092ca9e1dab..aa4a376caf7 100644 --- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js +++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js @@ -5,10 +5,10 @@ import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Mini Pipeline Graph Dropdown', () => { - preloadFixtures('static/mini_dropdown_graph.html.raw'); + preloadFixtures('static/mini_dropdown_graph.html'); beforeEach(() => { - loadFixtures('static/mini_dropdown_graph.html.raw'); + loadFixtures('static/mini_dropdown_graph.html'); }); describe('When is initialized', () => { diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 1d7b885e64f..4e3140ce4f1 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -3,7 +3,7 @@ import NewBranchForm from '~/new_branch_form'; describe('Branch', function() { describe('create a new branch', function() { - preloadFixtures('branches/new_branch.html.raw'); + preloadFixtures('branches/new_branch.html'); function fillNameWith(value) { $('.js-branch-name') @@ -16,7 +16,7 @@ describe('Branch', function() { } beforeEach(function() { - loadFixtures('branches/new_branch.html.raw'); + loadFixtures('branches/new_branch.html'); $('form').on('submit', function(e) { return e.preventDefault(); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 7c869d4c326..3d2c617e479 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -34,7 +34,7 @@ const htmlEscape = comment => { describe('Notes', function() { const FLASH_TYPE_ALERT = 'alert'; const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; - var fixture = 'snippets/show.html.raw'; + var fixture = 'snippets/show.html'; preloadFixtures(fixture); beforeEach(function() { diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js index 4125706a407..381be82697e 100644 --- a/spec/javascripts/oauth_remember_me_spec.js +++ b/spec/javascripts/oauth_remember_me_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me'; describe('OAuthRememberMe', () => { - preloadFixtures('static/oauth_remember_me.html.raw'); + preloadFixtures('static/oauth_remember_me.html'); beforeEach(() => { - loadFixtures('static/oauth_remember_me.html.raw'); + loadFixtures('static/oauth_remember_me.html'); new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents(); }); diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js index 23d07056925..f7637964c60 100644 --- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js +++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js @@ -3,7 +3,7 @@ import '~/lib/utils/text_utility'; import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; describe('Abuse Reports', () => { - const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; + const FIXTURE = 'abuse_reports/abuse_reports_list.html'; const MAX_MESSAGE_LENGTH = 500; let $messages; diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js index 561bd2c96cb..6a239e307e9 100644 --- a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js +++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js @@ -5,7 +5,7 @@ import initUserInternalRegexPlaceholder, { } from '~/pages/admin/application_settings/account_and_limits'; describe('AccountAndLimits', () => { - const FIXTURE = 'application_settings/accounts_and_limit.html.raw'; + const FIXTURE = 'application_settings/accounts_and_limit.html'; let $userDefaultExternal; let $userInternalRegex; preloadFixtures(FIXTURE); diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js index 5a849f34bc3..3896323eef7 100644 --- a/spec/javascripts/pages/admin/users/new/index_spec.js +++ b/spec/javascripts/pages/admin/users/new/index_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import UserInternalRegexHandler from '~/pages/admin/users/new/index'; describe('UserInternalRegexHandler', () => { - const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw'; + const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; let $userExternal; let $userEmail; let $warningMessage; diff --git a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js index 7a8227479d4..1809e92e1d9 100644 --- a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js +++ b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment'; describe('preserve_url_fragment', () => { - preloadFixtures('sessions/new.html.raw'); + preloadFixtures('sessions/new.html'); beforeEach(() => { - loadFixtures('sessions/new.html.raw'); + loadFixtures('sessions/new.html'); }); it('adds the url fragment to all login and sign up form actions', () => { diff --git a/spec/javascripts/pipelines_spec.js b/spec/javascripts/pipelines_spec.js index 6b86f9ea437..6d4d634c575 100644 --- a/spec/javascripts/pipelines_spec.js +++ b/spec/javascripts/pipelines_spec.js @@ -1,10 +1,10 @@ import Pipelines from '~/pipelines'; describe('Pipelines', () => { - preloadFixtures('static/pipeline_graph.html.raw'); + preloadFixtures('static/pipeline_graph.html'); beforeEach(() => { - loadFixtures('static/pipeline_graph.html.raw'); + loadFixtures('static/pipeline_graph.html'); }); it('should be defined', () => { diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js index 109a5000f5d..dc85292c23e 100644 --- a/spec/javascripts/project_select_combo_button_spec.js +++ b/spec/javascripts/project_select_combo_button_spec.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import ProjectSelectComboButton from '~/project_select_combo_button'; -const fixturePath = 'static/project_select_combo_button.html.raw'; +const fixturePath = 'static/project_select_combo_button.html'; describe('Project Select Combo Button', function() { preloadFixtures(fixturePath); diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js index 94e2f959d46..dca3e1553b9 100644 --- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js +++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js @@ -5,7 +5,7 @@ import PANEL_STATE from '~/prometheus_metrics/constants'; import { metrics, missingVarMetrics } from './mock_data'; describe('PrometheusMetrics', () => { - const FIXTURE = 'services/prometheus/prometheus_service.html.raw'; + const FIXTURE = 'services/prometheus/prometheus_service.html'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/read_more_spec.js b/spec/javascripts/read_more_spec.js index b1af0f80a50..d1d01272403 100644 --- a/spec/javascripts/read_more_spec.js +++ b/spec/javascripts/read_more_spec.js @@ -1,7 +1,7 @@ import initReadMore from '~/read_more'; describe('Read more click-to-expand functionality', () => { - const fixtureName = 'projects/overview.html.raw'; + const fixtureName = 'projects/overview.html'; preloadFixtures(fixtureName); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 992e17978c1..9565e3ce546 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -23,7 +23,7 @@ const assertSidebarState = function(state) { describe('RightSidebar', function() { describe('fixture tests', () => { - const fixtureName = 'issues/open-issue.html.raw'; + const fixtureName = 'issues/open-issue.html'; preloadFixtures(fixtureName); loadJSONFixtures('todos/todos.json'); let mock; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 7a4ca587313..ce7fa7a52ae 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -126,9 +126,9 @@ describe('Search autocomplete dropdown', () => { expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created"); }; - preloadFixtures('static/search_autocomplete.html.raw'); + preloadFixtures('static/search_autocomplete.html'); beforeEach(function() { - loadFixtures('static/search_autocomplete.html.raw'); + loadFixtures('static/search_autocomplete.html'); window.gon = {}; window.gon.current_user_id = userId; diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js index 40bdbac7451..32f60508fa3 100644 --- a/spec/javascripts/search_spec.js +++ b/spec/javascripts/search_spec.js @@ -3,7 +3,7 @@ import Api from '~/api'; import Search from '~/pages/search/show/search'; describe('Search', () => { - const fixturePath = 'search/show.html.raw'; + const fixturePath = 'search/show.html'; const searchTerm = 'some search'; const fillDropdownInput = dropdownSelector => { const dropdownElement = document.querySelector(dropdownSelector).parentNode; diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js index 3b681a9ff28..2c5d91a45bc 100644 --- a/spec/javascripts/settings_panels_spec.js +++ b/spec/javascripts/settings_panels_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import initSettingsPanels from '~/settings_panels'; describe('Settings Panels', () => { - preloadFixtures('groups/edit.html.raw'); + preloadFixtures('groups/edit.html'); beforeEach(() => { - loadFixtures('groups/edit.html.raw'); + loadFixtures('groups/edit.html'); }); describe('initSettingsPane', () => { diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js index 3ca6ecaa938..df7012bb659 100644 --- a/spec/javascripts/shortcuts_spec.js +++ b/spec/javascripts/shortcuts_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Shortcuts from '~/behaviors/shortcuts/shortcuts'; describe('Shortcuts', () => { - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; const createEvent = (type, target) => $.Event(type, { target, diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js index 3f0f67d71ca..016f5e033a5 100644 --- a/spec/javascripts/sidebar/sidebar_assignees_spec.js +++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js @@ -11,12 +11,12 @@ describe('sidebar assignees', () => { let vm; let mediator; let sidebarAssigneesEl; - preloadFixtures('issues/open-issue.html.raw'); + preloadFixtures('issues/open-issue.html'); beforeEach(() => { Vue.http.interceptors.push(Mock.sidebarMockInterceptor); - loadFixtures('issues/open-issue.html.raw'); + loadFixtures('issues/open-issue.html'); mediator = new SidebarMediator(Mock.mediator); spyOn(mediator, 'saveAssignees').and.callThrough(); diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js index 52da6a79939..ef5c774736b 100644 --- a/spec/javascripts/signin_tabs_memoizer_spec.js +++ b/spec/javascripts/signin_tabs_memoizer_spec.js @@ -2,7 +2,7 @@ import AccessorUtilities from '~/lib/utils/accessor'; import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; describe('SigninTabsMemoizer', () => { - const fixtureTemplate = 'static/signin_tabs.html.raw'; + const fixtureTemplate = 'static/signin_tabs.html'; const tabSelector = 'ul.new-session-tabs'; const currentTabKey = 'current_signin_tab'; let memo; diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js index 69e43274250..802f54f6a7e 100644 --- a/spec/javascripts/todos_spec.js +++ b/spec/javascripts/todos_spec.js @@ -3,11 +3,11 @@ import Todos from '~/pages/dashboard/todos/index/todos'; import '~/lib/utils/common_utils'; describe('Todos', () => { - preloadFixtures('todos/todos.html.raw'); + preloadFixtures('todos/todos.html'); let todoItem; beforeEach(() => { - loadFixtures('todos/todos.html.raw'); + loadFixtures('todos/todos.html'); todoItem = document.querySelector('.todos-list .todo'); return new Todos(); diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index ddb09811dda..8f9cb270729 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FAuthenticate', function() { - preloadFixtures('u2f/authenticate.html.raw'); + preloadFixtures('u2f/authenticate.html'); beforeEach(() => { - loadFixtures('u2f/authenticate.html.raw'); + loadFixtures('u2f/authenticate.html'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-authenticate-u2f'); this.component = new U2FAuthenticate( diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index 261db3d66d7..a75ceca9f4c 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FRegister', function() { - preloadFixtures('u2f/register.html.raw'); + preloadFixtures('u2f/register.html'); beforeEach(done => { - loadFixtures('u2f/register.html.raw'); + loadFixtures('u2f/register.html'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-register-u2f'); this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token'); diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js index b174a51c1a0..c0d5ee9c446 100644 --- a/spec/javascripts/user_popovers_spec.js +++ b/spec/javascripts/user_popovers_spec.js @@ -2,7 +2,7 @@ import initUserPopovers from '~/user_popovers'; import UsersCache from '~/lib/utils/users_cache'; describe('User Popovers', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + const fixtureTemplate = 'merge_requests/diff_comment.html'; preloadFixtures(fixtureTemplate); const selector = '.js-user-link'; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 30659ad16f3..368c997d318 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -415,7 +415,7 @@ describe('ReadyToMerge', () => { }); beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + loadFixtures('merge_requests/merge_request_of_current_user.html'); }); it('should call start and stop polling when MR merged', done => { diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js index e8b41e8eeff..852558a83bc 100644 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js @@ -17,7 +17,7 @@ const DEFAULT_PROPS = { const UserPopover = Vue.extend(userPopover); describe('User Popover Component', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + const fixtureTemplate = 'merge_requests/diff_comment.html'; preloadFixtures(fixtureTemplate); let vm; diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index e5f1e6ae937..8f662c71c7a 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -6,7 +6,7 @@ import ZenMode from '~/zen_mode'; describe('ZenMode', () => { let zen; let dropzoneForElementSpy; - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; preloadFixtures(fixtureName); diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index cceb179d53e..9cae8f934db 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -24,7 +24,7 @@ module JavaScriptFixturesHelpers # def clean_frontend_fixtures(directory_name) full_directory_name = File.expand_path(directory_name, fixture_root_path) - Dir[File.expand_path('*.html.raw', full_directory_name)].each do |file_name| + Dir[File.expand_path('*.html', full_directory_name)].each do |file_name| FileUtils.rm(file_name) end end -- cgit v1.2.1 From 60c28a5fe0fa0f93043f6037f68912c7803b6318 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 26 Mar 2019 17:17:57 +0100 Subject: GitalyClient spec: Remove the diff between CE/EE --- spec/lib/gitlab/gitaly_client/ref_service_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 400d426c949..0dab39575b9 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -89,6 +89,16 @@ describe Gitlab::GitalyClient::RefService do end end + describe '#list_new_blobs' do + it 'raises DeadlineExceeded when timeout is too small' do + newrev = '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' + + expect do + client.list_new_blobs(newrev, dynamic_timeout: 0.001) + end.to raise_error(GRPC::DeadlineExceeded) + end + end + describe '#local_branches' do it 'sends a find_local_branches message' do expect_any_instance_of(Gitaly::RefService::Stub) -- cgit v1.2.1 From d97d35d3da48493189c3c0017198b19d3f91cf9a Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Tue, 26 Mar 2019 13:47:54 +0000 Subject: Fix bug when reopening milestone from index page --- spec/features/milestone_spec.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'spec') diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 6e349395017..adac59b89ef 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -122,4 +122,32 @@ describe 'Milestone' do expect(page).to have_selector('.popover') end end + + describe 'reopen closed milestones' do + before do + create(:milestone, :closed, project: project) + end + + describe 'group milestones page' do + it 'reopens the milestone' do + visit group_milestones_path(group, { state: 'closed' }) + + click_link 'Reopen Milestone' + + expect(page).not_to have_selector('.status-box-closed') + expect(page).to have_selector('.status-box-open') + end + end + + describe 'project milestones page' do + it 'reopens the milestone' do + visit project_milestones_path(project, { state: 'closed' }) + + click_link 'Reopen Milestone' + + expect(page).not_to have_selector('.status-box-closed') + expect(page).to have_selector('.status-box-open') + end + end + end end -- cgit v1.2.1 From 3b158409b382014dba56e5bb6e66c298786af7f6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 26 Mar 2019 18:20:02 +0000 Subject: Remove expand diff to full file feature flag --- spec/javascripts/diffs/components/diff_file_header_spec.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 66c5b17b825..e10193c25b7 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -23,9 +23,6 @@ describe('diff_file_header', () => { }); beforeEach(() => { - gon.features = { - expandDiffFullFile: true, - }; const diffFile = diffDiscussionMock.diff_file; diffFile.added_lines = 2; -- cgit v1.2.1 From 4d1ac5651870d110a8e61657730a594bf3f179fc Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Mar 2019 19:53:07 +0100 Subject: Remove top-level mount from note_form_spec.js --- spec/javascripts/notes/components/note_form_spec.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js index c48f8188105..b632ee6736d 100644 --- a/spec/javascripts/notes/components/note_form_spec.js +++ b/spec/javascripts/notes/components/note_form_spec.js @@ -41,8 +41,6 @@ describe('issue_note_form component', () => { noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.', noteId: '545', }; - - wrapper = createComponentWrapper(); }); afterEach(() => { @@ -50,6 +48,10 @@ describe('issue_note_form component', () => { }); describe('noteHash', () => { + beforeEach(() => { + wrapper = createComponentWrapper(); + }); + it('returns note hash string based on `noteId`', () => { expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`); }); @@ -71,6 +73,10 @@ describe('issue_note_form component', () => { }); describe('conflicts editing', () => { + beforeEach(() => { + wrapper = createComponentWrapper(); + }); + it('should show conflict message if note changes outside the component', done => { wrapper.setProps({ ...props, @@ -100,6 +106,10 @@ describe('issue_note_form component', () => { }); describe('form', () => { + beforeEach(() => { + wrapper = createComponentWrapper(); + }); + it('should render text area with placeholder', () => { const textarea = wrapper.find('textarea'); @@ -198,10 +208,6 @@ describe('issue_note_form component', () => { }); describe('with autosaveKey', () => { - beforeEach(() => { - wrapper.destroy(); - }); - describe('with draft', () => { beforeEach(done => { Object.assign(props, { -- cgit v1.2.1 From 4cdef3da6caa155cfd88447099c04caae1815da4 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Mar 2019 20:23:19 +0100 Subject: Copy missing test in assignees_spec.js from EE --- spec/javascripts/sidebar/assignees_spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js index eced4925489..57b16b12cb0 100644 --- a/spec/javascripts/sidebar/assignees_spec.js +++ b/spec/javascripts/sidebar/assignees_spec.js @@ -210,6 +210,19 @@ describe('Assignee component', () => { expect(component.$el.querySelector('.user-list-more')).toBe(null); }); + it('sets tooltip container to body', () => { + const users = UsersMockHelper.createNumberRandomUsers(2); + component = new AssigneeComponent({ + propsData: { + rootPath: 'http://localhost:3000', + users, + editable: true, + }, + }).$mount(); + + expect(component.$el.querySelector('.user-link').getAttribute('data-container')).toBe('body'); + }); + it('Shows the "show-less" assignees label', done => { const users = UsersMockHelper.createNumberRandomUsers(6); component = new AssigneeComponent({ -- cgit v1.2.1 From f9f5ebc2ac751ceb288381d1792aecec43bdbe39 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Mar 2019 20:48:34 +0100 Subject: Add jest-environment-jsdom and jest-util dependencies --- spec/frontend/helpers/fixtures.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'spec') diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js index f96f27c4d80..de9058d7832 100644 --- a/spec/frontend/helpers/fixtures.js +++ b/spec/frontend/helpers/fixtures.js @@ -3,8 +3,6 @@ import fs from 'fs'; import path from 'path'; -// jest-util is part of Jest -// eslint-disable-next-line import/no-extraneous-dependencies import { ErrorWithStack } from 'jest-util'; const fixturesBasePath = path.join(process.cwd(), 'spec', 'javascripts', 'fixtures'); -- cgit v1.2.1 From fbe1f8b4cb7cfca5aa6a6a1cd36600e77f52cf68 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 26 Mar 2019 20:49:05 +0100 Subject: Provide custom Jest environment with mocked console --- spec/frontend/environment.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 spec/frontend/environment.js (limited to 'spec') diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js new file mode 100644 index 00000000000..cb128c7d880 --- /dev/null +++ b/spec/frontend/environment.js @@ -0,0 +1,27 @@ +/* eslint-disable import/no-commonjs */ + +const { ErrorWithStack } = require('jest-util'); +const JSDOMEnvironment = require('jest-environment-jsdom'); + +class CustomEnvironment extends JSDOMEnvironment { + constructor(config, context) { + super(config, context); + Object.assign(context.console, { + error(...args) { + throw new ErrorWithStack( + `Unexpected call of console.error() with:\n\n${args.join(', ')}`, + this.error, + ); + }, + + warn(...args) { + throw new ErrorWithStack( + `Unexpected call of console.warn() with:\n\n${args.join(', ')}`, + this.warn, + ); + }, + }); + } +} + +module.exports = CustomEnvironment; -- cgit v1.2.1 From 90718774e19d333a64f6625f0ac23b8508f37e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 26 Mar 2019 15:33:56 +0100 Subject: Use a Gitlab::FakeApplicationSettings when migrations are pending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/lib/gitlab/current_settings_spec.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 17d5eae24f5..cdf2ad1d962 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -116,8 +116,7 @@ describe Gitlab::CurrentSettings do let(:current_settings) { described_class.current_application_settings } it 'returns a non-persisted ApplicationSetting object' do - expect(current_settings).to be_a(ApplicationSetting) - expect(current_settings).not_to be_persisted + expect(current_settings).to be_a(Gitlab::FakeApplicationSettings) end it 'uses the default value from ApplicationSetting.defaults' do @@ -146,6 +145,16 @@ describe Gitlab::CurrentSettings do it 'uses the value from the DB attribute if present and not overridden by an accessor' do expect(current_settings.home_page_url).to eq(db_settings.home_page_url) end + + context 'when a new column is used before being migrated' do + before do + allow(ApplicationSetting).to receive(:defaults).and_return({ foo: 'bar' }) + end + + it 'uses the default value if present' do + expect(current_settings.foo).to eq('bar') + end + end end end -- cgit v1.2.1 From 035c8af12e4ea6dbba18255214bad2a53c1b1cf1 Mon Sep 17 00:00:00 2001 From: Fabio Busatto Date: Wed, 27 Mar 2019 09:01:07 +0000 Subject: Resolve "Get milestone by title via API" --- spec/support/api/milestones_shared_examples.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 5f709831ce1..63b719be03e 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -72,6 +72,15 @@ shared_examples_for 'group and project milestones' do |route_definition| expect(json_response.first['id']).to eq closed_milestone.id end + it 'returns a milestone by title' do + get api(route, user), params: { title: 'version2' } + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to eq(1) + expect(json_response.first['title']).to eq milestone.title + expect(json_response.first['id']).to eq milestone.id + end + it 'returns a milestone by searching for title' do get api(route, user), params: { search: 'version2' } -- cgit v1.2.1 From 1cb82c4de9001a0682fb6e4970cc2b5a270e2555 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 27 Mar 2019 11:14:33 +0000 Subject: Fixes link in artifacts breadcrumb The link to the job page was not set properly, with this commit it links to the correct job page. --- spec/features/projects/artifacts/user_browses_artifacts_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb index 5f630c9ffa4..a1fcd4024c0 100644 --- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb +++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb @@ -19,6 +19,12 @@ describe "User browses artifacts" do visit(browse_project_job_artifacts_path(project, job)) end + it "renders a link to the job in the breadcrumbs" do + page.within('.js-breadcrumbs-list') do + expect(page).to have_link("##{job.id}", href: project_job_path(project, job)) + end + end + it "shows artifacts" do expect(page).not_to have_selector(".build-sidebar") -- cgit v1.2.1 From c408be48ca3d6840076c6f16c7910411cdfca24c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 22 Mar 2019 06:39:58 -0700 Subject: Optimize /api/v4/projects endpoint for visibility level Previously when a user requested a list of projects, `Project#public_or_visible_to_user` would search all authorized projects and public/internal projects as well. However, when a user requests a specific `visibility_level` (e.g. private), that should reduce the search space, and we shouldn't need to load public/internal projects. Improves https://gitlab.com/gitlab-org/gitlab-ce/issues/59329 --- spec/models/project_spec.rb | 49 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index fc9d9a28b9a..90dcf861849 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2710,7 +2710,7 @@ describe Project do end describe '#any_lfs_file_locks?', :request_store do - set(:project) { create(:project) } + let!(:project) { create(:project) } it 'returns false when there are no LFS file locks' do expect(project.any_lfs_file_locks?).to be_falsey @@ -3148,6 +3148,53 @@ describe Project do expect(projects).to eq([public_project]) end end + + context 'with requested visibility levels' do + set(:internal_project) { create(:project, :internal, :repository) } + set(:private_project_2) { create(:project, :private) } + + context 'with admin user' do + set(:admin) { create(:admin) } + + it 'returns all projects' do + projects = described_class.all.public_or_visible_to_user(admin, []) + + expect(projects).to match_array([public_project, private_project, private_project_2, internal_project]) + end + + it 'returns all public and private projects' do + projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to match_array([public_project, private_project, private_project_2]) + end + + it 'returns all private projects' do + projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to match_array([private_project, private_project_2]) + end + end + + context 'with regular user' do + it 'returns authorized projects' do + projects = described_class.all.public_or_visible_to_user(user, []) + + expect(projects).to match_array([public_project, private_project, internal_project]) + end + + it "returns user's public and private projects" do + projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to match_array([public_project, private_project]) + end + + it 'returns one private project' do + projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to eq([private_project]) + end + end + end end describe '.with_feature_available_for_user' do -- cgit v1.2.1 From e081c0cba2dd5b1a19d22b44918022f72035434c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 27 Mar 2019 12:49:01 +0000 Subject: Removes EE differences for stage_column_component_spec --- spec/javascripts/pipelines/graph/stage_column_component_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js index dafb892da43..3240e8e4c1b 100644 --- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js +++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js @@ -55,7 +55,7 @@ describe('stage column component', () => { id: 4259, name: '', status: { - icon: 'icon_status_success', + icon: 'status_success', label: 'success', tooltip: '', }, -- cgit v1.2.1 From 9aca2ccfd2baef52b09834bf913c7a92d6d04034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 26 Mar 2019 09:22:46 +0100 Subject: [CE] Reduce the diff with EE in spec/policies/namespace_policy_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/policies/namespace_policy_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb index 1fdf95ad716..99fa8b1fe44 100644 --- a/spec/policies/namespace_policy_spec.rb +++ b/spec/policies/namespace_policy_spec.rb @@ -30,7 +30,7 @@ describe NamespacePolicy do context 'user who has exceeded project limit' do let(:owner) { create(:user, projects_limit: 0) } - it { is_expected.not_to be_allowed(:create_projects) } + it { is_expected.to be_disallowed(:create_projects) } end end -- cgit v1.2.1 From 1983d05ccd6f7484ed1634d744ac0f414c4967ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 26 Mar 2019 12:31:47 +0100 Subject: [CE] Reduce the diff with EE in spec/policies/project_policy_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/policies/project_policy_spec.rb | 324 +-------------------- spec/policies/project_snippet_policy_spec.rb | 101 ++++--- .../policies/project_policy_shared_context.rb | 96 ++++++ .../policies/project_policy_shared_examples.rb | 231 +++++++++++++++ 4 files changed, 379 insertions(+), 373 deletions(-) create mode 100644 spec/support/shared_context/policies/project_policy_shared_context.rb create mode 100644 spec/support/shared_examples/policies/project_policy_shared_examples.rb (limited to 'spec') diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 772d1fbee2b..726ccba8807 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,96 +1,7 @@ require 'spec_helper' describe ProjectPolicy do - set(:guest) { create(:user) } - set(:reporter) { create(:user) } - set(:developer) { create(:user) } - set(:maintainer) { create(:user) } - set(:owner) { create(:user) } - set(:admin) { create(:admin) } - let(:project) { create(:project, :public, namespace: owner.namespace) } - - let(:base_guest_permissions) do - %i[ - read_project read_board read_list read_wiki read_issue - read_project_for_iids read_issue_iid read_label - read_milestone read_project_snippet read_project_member read_note - create_project create_issue create_note upload_file create_merge_request_in - award_emoji read_release - ] - end - - let(:base_reporter_permissions) do - %i[ - download_code fork_project create_project_snippet update_issue - admin_issue admin_label admin_list read_commit_status read_build - read_container_image read_pipeline read_environment read_deployment - read_merge_request download_wiki_code read_sentry_issue - ] - end - - let(:team_member_reporter_permissions) do - %i[build_download_code build_read_container_image] - end - - let(:developer_permissions) do - %i[ - admin_milestone admin_merge_request update_merge_request create_commit_status - update_commit_status create_build update_build create_pipeline - update_pipeline create_merge_request_from create_wiki push_code - resolve_note create_container_image update_container_image - create_environment create_deployment create_release update_release - ] - end - - let(:base_maintainer_permissions) do - %i[ - push_to_delete_protected_branch update_project_snippet update_environment - update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project - admin_commit_status admin_build admin_container_image - admin_pipeline admin_environment admin_deployment destroy_release add_cluster - daily_statistics - ] - end - - let(:public_permissions) do - %i[ - download_code fork_project read_commit_status read_pipeline - read_container_image build_download_code build_read_container_image - download_wiki_code read_release - ] - end - - let(:owner_permissions) do - %i[ - change_namespace change_visibility_level rename_project remove_project - archive_project remove_fork_project destroy_merge_request destroy_issue - set_issue_iid set_issue_created_at set_note_created_at - ] - end - - # Used in EE specs - let(:additional_guest_permissions) { [] } - let(:additional_reporter_permissions) { [] } - let(:additional_maintainer_permissions) { [] } - - let(:guest_permissions) { base_guest_permissions + additional_guest_permissions } - let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions } - let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions } - - before do - project.add_guest(guest) - project.add_maintainer(maintainer) - project.add_developer(developer) - project.add_reporter(reporter) - end - - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end + include_context 'ProjectPolicy context' it 'does not include the read_issue permission when the issue author is not a member of the private project' do project = create(:project, :private) @@ -140,7 +51,7 @@ describe ProjectPolicy do end it 'disables boards and lists permissions' do - expect_disallowed :read_board, :create_board, :update_board, :admin_board + expect_disallowed :read_board, :create_board, :update_board expect_disallowed :read_list, :create_list, :update_list, :admin_list end @@ -237,237 +148,6 @@ describe ProjectPolicy do end end - shared_examples 'archived project policies' do - let(:feature_write_abilities) do - described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature| - described_class.create_update_admin_destroy(feature) - end - end - - let(:other_write_abilities) do - %i[ - create_merge_request_in - create_merge_request_from - push_to_delete_protected_branch - push_code - request_access - upload_file - resolve_note - award_emoji - ] - end - - context 'when the project is archived' do - before do - project.archived = true - end - - it 'disables write actions on all relevant project features' do - expect_disallowed(*feature_write_abilities) - end - - it 'disables some other important write actions' do - expect_disallowed(*other_write_abilities) - end - - it 'does not disable other abilities' do - expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities)) - end - end - end - - shared_examples 'project policies as anonymous' do - context 'abilities for public projects' do - context 'when a project has pending invites' do - let(:group) { create(:group, :public) } - let(:project) { create(:project, :public, namespace: group) } - let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] } - let(:anonymous_permissions) { guest_permissions - user_permissions } - - subject { described_class.new(nil, project) } - - before do - create(:group_member, :invited, group: group) - end - - it 'does not grant owner access' do - expect_allowed(*anonymous_permissions) - expect_disallowed(*user_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { anonymous_permissions } - end - end - end - - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(nil, project) } - - it { is_expected.to be_banned } - end - end - - shared_examples 'project policies as guest' do - subject { described_class.new(guest, project) } - - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - let(:reporter_public_build_permissions) do - reporter_permissions - [:read_build, :read_pipeline] - end - - it do - expect_allowed(*guest_permissions) - expect_disallowed(*reporter_public_build_permissions) - expect_disallowed(*team_member_reporter_permissions) - expect_disallowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { guest_permissions } - end - - context 'public builds enabled' do - it do - expect_allowed(*guest_permissions) - expect_allowed(:read_build, :read_pipeline) - end - end - - context 'when public builds disabled' do - before do - project.update(public_builds: false) - end - - it do - expect_allowed(*guest_permissions) - expect_disallowed(:read_build, :read_pipeline) - end - end - - context 'when builds are disabled' do - before do - project.project_feature.update(builds_access_level: ProjectFeature::DISABLED) - end - - it do - expect_disallowed(:read_build) - expect_allowed(:read_pipeline) - end - end - end - end - - shared_examples 'project policies as reporter' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(reporter, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_disallowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { reporter_permissions } - end - end - end - - shared_examples 'project policies as developer' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(developer, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { developer_permissions } - end - end - end - - shared_examples 'project policies as maintainer' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(maintainer, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_allowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { maintainer_permissions } - end - end - end - - shared_examples 'project policies as owner' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(owner, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_allowed(*maintainer_permissions) - expect_allowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { owner_permissions } - end - end - end - - shared_examples 'project policies as admin' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(admin, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_disallowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_allowed(*maintainer_permissions) - expect_allowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { owner_permissions } - end - end - end - it_behaves_like 'project policies as anonymous' it_behaves_like 'project policies as guest' it_behaves_like 'project policies as reporter' diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index d6329e84579..c44797c1e7a 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -5,7 +5,7 @@ describe ProjectSnippetPolicy do let(:regular_user) { create(:user) } let(:external_user) { create(:user, :external) } let(:project) { create(:project, :public) } - + let(:snippet) { create(:project_snippet, snippet_visibility, project: project) } let(:author_permissions) do [ :update_project_snippet, @@ -13,11 +13,7 @@ describe ProjectSnippetPolicy do ] end - def abilities(user, snippet_visibility) - snippet = create(:project_snippet, snippet_visibility, project: project) - - described_class.new(user, snippet) - end + subject { described_class.new(current_user, snippet) } def expect_allowed(*permissions) permissions.each { |p| is_expected.to be_allowed(p) } @@ -28,8 +24,10 @@ describe ProjectSnippetPolicy do end context 'public snippet' do + let(:snippet_visibility) { :public } + context 'no user' do - subject { abilities(nil, :public) } + let(:current_user) { nil } it do expect_allowed(:read_project_snippet) @@ -38,7 +36,7 @@ describe ProjectSnippetPolicy do end context 'regular user' do - subject { abilities(regular_user, :public) } + let(:current_user) { regular_user } it do expect_allowed(:read_project_snippet, :create_note) @@ -47,7 +45,7 @@ describe ProjectSnippetPolicy do end context 'external user' do - subject { abilities(external_user, :public) } + let(:current_user) { external_user } it do expect_allowed(:read_project_snippet, :create_note) @@ -57,8 +55,10 @@ describe ProjectSnippetPolicy do end context 'internal snippet' do + let(:snippet_visibility) { :internal } + context 'no user' do - subject { abilities(nil, :internal) } + let(:current_user) { nil } it do expect_disallowed(:read_project_snippet) @@ -67,7 +67,7 @@ describe ProjectSnippetPolicy do end context 'regular user' do - subject { abilities(regular_user, :internal) } + let(:current_user) { regular_user } it do expect_allowed(:read_project_snippet, :create_note) @@ -76,31 +76,31 @@ describe ProjectSnippetPolicy do end context 'external user' do - subject { abilities(external_user, :internal) } + let(:current_user) { external_user } it do expect_disallowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end - end - - context 'project team member external user' do - subject { abilities(external_user, :internal) } - before do - project.add_developer(external_user) - end + context 'project team member' do + before do + project.add_developer(external_user) + end - it do - expect_allowed(:read_project_snippet, :create_note) - expect_disallowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_disallowed(*author_permissions) + end end end end context 'private snippet' do + let(:snippet_visibility) { :private } + context 'no user' do - subject { abilities(nil, :private) } + let(:current_user) { nil } it do expect_disallowed(:read_project_snippet) @@ -109,53 +109,52 @@ describe ProjectSnippetPolicy do end context 'regular user' do - subject { abilities(regular_user, :private) } + let(:current_user) { regular_user } it do expect_disallowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end - end - - context 'snippet author' do - let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) } - subject { described_class.new(regular_user, snippet) } + context 'snippet author' do + let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) } - it do - expect_allowed(:read_project_snippet, :create_note) - expect_allowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_allowed(*author_permissions) + end end - end - context 'project team member normal user' do - subject { abilities(regular_user, :private) } + context 'project team member normal user' do + before do + project.add_developer(regular_user) + end - before do - project.add_developer(regular_user) - end - - it do - expect_allowed(:read_project_snippet, :create_note) - expect_disallowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_disallowed(*author_permissions) + end end end - context 'project team member external user' do - subject { abilities(external_user, :private) } + context 'external user' do + context 'project team member' do + let(:current_user) { external_user } - before do - project.add_developer(external_user) - end + before do + project.add_developer(external_user) + end - it do - expect_allowed(:read_project_snippet, :create_note) - expect_disallowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_disallowed(*author_permissions) + end end end context 'admin user' do - subject { abilities(create(:admin), :private) } + let(:snippet_visibility) { :private } + let(:current_user) { create(:admin) } it do expect_allowed(:read_project_snippet, :create_note) diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb new file mode 100644 index 00000000000..2c71aabd292 --- /dev/null +++ b/spec/support/shared_context/policies/project_policy_shared_context.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +RSpec.shared_context 'ProjectPolicy context' do + set(:guest) { create(:user) } + set(:reporter) { create(:user) } + set(:developer) { create(:user) } + set(:maintainer) { create(:user) } + set(:owner) { create(:user) } + set(:admin) { create(:admin) } + let(:project) { create(:project, :public, namespace: owner.namespace) } + + let(:base_guest_permissions) do + %i[ + read_project read_board read_list read_wiki read_issue + read_project_for_iids read_issue_iid read_label + read_milestone read_project_snippet read_project_member read_note + create_project create_issue create_note upload_file create_merge_request_in + award_emoji read_release + ] + end + + let(:base_reporter_permissions) do + %i[ + download_code fork_project create_project_snippet update_issue + admin_issue admin_label admin_list read_commit_status read_build + read_container_image read_pipeline read_environment read_deployment + read_merge_request download_wiki_code read_sentry_issue + ] + end + + let(:team_member_reporter_permissions) do + %i[build_download_code build_read_container_image] + end + + let(:developer_permissions) do + %i[ + admin_milestone admin_merge_request update_merge_request create_commit_status + update_commit_status create_build update_build create_pipeline + update_pipeline create_merge_request_from create_wiki push_code + resolve_note create_container_image update_container_image + create_environment create_deployment create_release update_release + ] + end + + let(:base_maintainer_permissions) do + %i[ + push_to_delete_protected_branch update_project_snippet update_environment + update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project + admin_commit_status admin_build admin_container_image + admin_pipeline admin_environment admin_deployment destroy_release add_cluster + daily_statistics + ] + end + + let(:public_permissions) do + %i[ + download_code fork_project read_commit_status read_pipeline + read_container_image build_download_code build_read_container_image + download_wiki_code read_release + ] + end + + let(:base_owner_permissions) do + %i[ + change_namespace change_visibility_level rename_project remove_project + archive_project remove_fork_project destroy_merge_request destroy_issue + set_issue_iid set_issue_created_at set_note_created_at + ] + end + + # Used in EE specs + let(:additional_guest_permissions) { [] } + let(:additional_reporter_permissions) { [] } + let(:additional_maintainer_permissions) { [] } + let(:additional_owner_permissions) { [] } + + let(:guest_permissions) { base_guest_permissions + additional_guest_permissions } + let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions } + let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions } + let(:owner_permissions) { base_owner_permissions + additional_owner_permissions } + + before do + project.add_guest(guest) + project.add_maintainer(maintainer) + project.add_developer(developer) + project.add_reporter(reporter) + end + + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } + end +end diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb new file mode 100644 index 00000000000..7a71e2ee370 --- /dev/null +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'archived project policies' do + let(:feature_write_abilities) do + described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature| + described_class.create_update_admin_destroy(feature) + end + additional_reporter_permissions + additional_maintainer_permissions + end + + let(:other_write_abilities) do + %i[ + create_merge_request_in + create_merge_request_from + push_to_delete_protected_branch + push_code + request_access + upload_file + resolve_note + award_emoji + ] + end + + context 'when the project is archived' do + before do + project.archived = true + end + + it 'disables write actions on all relevant project features' do + expect_disallowed(*feature_write_abilities) + end + + it 'disables some other important write actions' do + expect_disallowed(*other_write_abilities) + end + + it 'does not disable other abilities' do + expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities)) + end + end +end + +RSpec.shared_examples 'project policies as anonymous' do + context 'abilities for public projects' do + context 'when a project has pending invites' do + let(:group) { create(:group, :public) } + let(:project) { create(:project, :public, namespace: group) } + let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] } + let(:anonymous_permissions) { guest_permissions - user_permissions } + + subject { described_class.new(nil, project) } + + before do + create(:group_member, :invited, group: group) + end + + it 'does not grant owner access' do + expect_allowed(*anonymous_permissions) + expect_disallowed(*user_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { anonymous_permissions } + end + end + end + + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(nil, project) } + + it { is_expected.to be_banned } + end +end + +RSpec.shared_examples 'project policies as guest' do + subject { described_class.new(guest, project) } + + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + let(:reporter_public_build_permissions) do + reporter_permissions - [:read_build, :read_pipeline] + end + + it do + expect_allowed(*guest_permissions) + expect_disallowed(*reporter_public_build_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { guest_permissions } + end + + context 'public builds enabled' do + it do + expect_allowed(*guest_permissions) + expect_allowed(:read_build, :read_pipeline) + end + end + + context 'when public builds disabled' do + before do + project.update(public_builds: false) + end + + it do + expect_allowed(*guest_permissions) + expect_disallowed(:read_build, :read_pipeline) + end + end + + context 'when builds are disabled' do + before do + project.project_feature.update(builds_access_level: ProjectFeature::DISABLED) + end + + it do + expect_disallowed(:read_build) + expect_allowed(:read_pipeline) + end + end + end +end + +RSpec.shared_examples 'project policies as reporter' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(reporter, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { reporter_permissions } + end + end +end + +RSpec.shared_examples 'project policies as developer' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + subject { described_class.new(developer, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { developer_permissions } + end + end +end + +RSpec.shared_examples 'project policies as maintainer' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(maintainer, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { maintainer_permissions } + end + end +end + +RSpec.shared_examples 'project policies as owner' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(owner, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*maintainer_permissions) + expect_allowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { owner_permissions } + end + end +end + +RSpec.shared_examples 'project policies as admin' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(admin, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*maintainer_permissions) + expect_allowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { owner_permissions } + end + end +end -- cgit v1.2.1 From ce4b70ce18893f7c2bdf6a52ce66dc5197eae40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 27 Mar 2019 13:45:00 +0100 Subject: Factorize policy helpers into PolicyHelpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/policies/board_policy_spec.rb | 8 -------- spec/policies/group_policy_spec.rb | 8 -------- spec/policies/project_snippet_policy_spec.rb | 8 -------- spec/spec_helper.rb | 1 + spec/support/helpers/policy_helpers.rb | 11 +++++++++++ .../shared_context/policies/project_policy_shared_context.rb | 8 -------- 6 files changed, 12 insertions(+), 32 deletions(-) create mode 100644 spec/support/helpers/policy_helpers.rb (limited to 'spec') diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb index 4b76d65ef69..52c23951e37 100644 --- a/spec/policies/board_policy_spec.rb +++ b/spec/policies/board_policy_spec.rb @@ -17,14 +17,6 @@ describe BoardPolicy do ] end - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end - context 'group board' do subject { described_class.new(user, group_board) } diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 92bdaa8b8b8..6a5136c835e 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -51,14 +51,6 @@ describe GroupPolicy do subject { described_class.new(current_user, group) } - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end - context 'with no user' do let(:group) { create(:group, :public) } let(:current_user) { nil } diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index c44797c1e7a..2e9ef1e89fd 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -15,14 +15,6 @@ describe ProjectSnippetPolicy do subject { described_class.new(current_user, snippet) } - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end - context 'public snippet' do let(:snippet_visibility) { :public } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e8d7b18bf04..b3bd453ebd6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -96,6 +96,7 @@ RSpec.configure do |config| config.include MigrationsHelpers, :migration config.include RedisHelpers config.include Rails.application.routes.url_helpers, type: :routing + config.include PolicyHelpers, type: :policy if ENV['CI'] # This includes the first try, i.e. tests will be run 4 times before failing. diff --git a/spec/support/helpers/policy_helpers.rb b/spec/support/helpers/policy_helpers.rb new file mode 100644 index 00000000000..3d780eb5fb1 --- /dev/null +++ b/spec/support/helpers/policy_helpers.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module PolicyHelpers + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } + end +end diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb index 2c71aabd292..8bcd26ec0cd 100644 --- a/spec/support/shared_context/policies/project_policy_shared_context.rb +++ b/spec/support/shared_context/policies/project_policy_shared_context.rb @@ -85,12 +85,4 @@ RSpec.shared_context 'ProjectPolicy context' do project.add_developer(developer) project.add_reporter(reporter) end - - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end end -- cgit v1.2.1 From 65441cfdda18e1233021b2633552e101dd3b36a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 27 Mar 2019 14:10:29 +0100 Subject: [CE] Reduce the diff with EE in spec/policies/group_policy_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/policies/group_policy_spec.rb | 50 +--------------------- .../policies/group_policy_shared_context.rb | 47 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 49 deletions(-) create mode 100644 spec/support/shared_contexts/policies/group_policy_shared_context.rb (limited to 'spec') diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 6a5136c835e..dc98baca6dc 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -1,55 +1,7 @@ require 'spec_helper' describe GroupPolicy do - let(:guest) { create(:user) } - let(:reporter) { create(:user) } - let(:developer) { create(:user) } - let(:maintainer) { create(:user) } - let(:owner) { create(:user) } - let(:admin) { create(:admin) } - let(:group) { create(:group, :private) } - - let(:guest_permissions) do - [:read_label, :read_group, :upload_file, :read_namespace, :read_group_activity, - :read_group_issues, :read_group_boards, :read_group_labels, :read_group_milestones, - :read_group_merge_requests] - end - - let(:reporter_permissions) { [:admin_label] } - - let(:developer_permissions) { [:admin_milestone] } - - let(:maintainer_permissions) do - [ - :create_projects, - :read_cluster, - :create_cluster, - :update_cluster, - :admin_cluster, - :add_cluster - ] - end - - let(:owner_permissions) do - [ - :admin_group, - :admin_namespace, - :admin_group_member, - :change_visibility_level, - :set_note_created_at, - (Gitlab::Database.postgresql? ? :create_subgroup : nil) - ].compact - end - - before do - group.add_guest(guest) - group.add_reporter(reporter) - group.add_developer(developer) - group.add_maintainer(maintainer) - group.add_owner(owner) - end - - subject { described_class.new(current_user, group) } + include_context 'GroupPolicy context' context 'with no user' do let(:group) { create(:group, :public) } diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb new file mode 100644 index 00000000000..b4808ac0068 --- /dev/null +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.shared_context 'GroupPolicy context' do + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + let(:developer) { create(:user) } + let(:maintainer) { create(:user) } + let(:owner) { create(:user) } + let(:admin) { create(:admin) } + let(:group) { create(:group, :private) } + + let(:guest_permissions) do + %i[ + read_label read_group upload_file read_namespace read_group_activity + read_group_issues read_group_boards read_group_labels read_group_milestones + read_group_merge_requests + ] + end + let(:reporter_permissions) { [:admin_label] } + let(:developer_permissions) { [:admin_milestone] } + let(:maintainer_permissions) do + %i[ + create_projects + read_cluster create_cluster update_cluster admin_cluster add_cluster + ] + end + let(:owner_permissions) do + [ + :admin_group, + :admin_namespace, + :admin_group_member, + :change_visibility_level, + :set_note_created_at, + (Gitlab::Database.postgresql? ? :create_subgroup : nil) + ].compact + end + + before do + group.add_guest(guest) + group.add_reporter(reporter) + group.add_developer(developer) + group.add_maintainer(maintainer) + group.add_owner(owner) + end + + subject { described_class.new(current_user, group) } +end -- cgit v1.2.1 From 73b553a42a1dec7bd38e0aeeb5514c2a566a98c9 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 27 Mar 2019 14:59:02 +0000 Subject: Add API access check to Graphql Check if user can access API on GraphqlController --- spec/controllers/graphql_controller_spec.rb | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 spec/controllers/graphql_controller_spec.rb (limited to 'spec') diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb new file mode 100644 index 00000000000..c19a752b07b --- /dev/null +++ b/spec/controllers/graphql_controller_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GraphqlController do + before do + stub_feature_flags(graphql: true) + end + + describe 'POST #execute' do + context 'when user is logged in' do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + it 'returns 200 when user can access API' do + post :execute + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns access denied template when user cannot access API' do + # User cannot access API in a couple of cases + # * When user is internal(like ghost users) + # * When user is blocked + expect(Ability).to receive(:allowed?).with(user, :access_api, :global).and_return(false) + + post :execute + + expect(response.status).to eq(403) + expect(response).to render_template('errors/access_denied') + end + end + + context 'when user is not logged in' do + it 'returns 200' do + post :execute + + expect(response).to have_gitlab_http_status(200) + end + end + end +end -- cgit v1.2.1 From 03e0604d5ded6402c7fddc4001ab23d9712c98de Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 15 Mar 2019 14:35:34 -0300 Subject: Prepare suggestion implementation for multi-line Adds the groundwork needed in order to persist multi-line suggestions, while providing the parsing strategy which will be reused for the **Preview** as well. --- spec/factories/suggestions.rb | 6 ++ spec/lib/gitlab/diff/suggestion_spec.rb | 88 ++++++++++++++++++ spec/lib/gitlab/diff/suggestions_parser_spec.rb | 73 +++++++++++++++ spec/lib/gitlab/import_export/all_models.yml | 3 + .../gitlab/import_export/safe_model_attributes.yml | 11 +++ spec/requests/api/suggestions_spec.rb | 3 +- spec/serializers/suggestion_entity_spec.rb | 4 +- .../merge_requests/refresh_service_spec.rb | 38 ++++++-- spec/services/suggestions/apply_service_spec.rb | 93 +++++++++++-------- spec/services/suggestions/create_service_spec.rb | 73 ++++++++------- spec/services/suggestions/outdate_service_spec.rb | 102 +++++++++++++++++++++ 11 files changed, 411 insertions(+), 83 deletions(-) create mode 100644 spec/lib/gitlab/diff/suggestion_spec.rb create mode 100644 spec/lib/gitlab/diff/suggestions_parser_spec.rb create mode 100644 spec/services/suggestions/outdate_service_spec.rb (limited to 'spec') diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb index 307523cc061..b1427e0211f 100644 --- a/spec/factories/suggestions.rb +++ b/spec/factories/suggestions.rb @@ -16,5 +16,11 @@ FactoryBot.define do applied true commit_id { RepoHelpers.sample_commit.id } end + + trait :content_from_repo do + after(:build) do |suggestion, evaluator| + suggestion.from_content = suggestion.fetch_from_content + end + end end end diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb new file mode 100644 index 00000000000..71fd25df698 --- /dev/null +++ b/spec/lib/gitlab/diff/suggestion_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::Suggestion do + shared_examples 'correct suggestion raw content' do + it 'returns correct raw data' do + expect(suggestion.to_hash).to include(from_content: expected_lines.join, + to_content: "#{text}\n", + lines_above: above, + lines_below: below) + end + end + + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + let(:diff_file) do + position.diff_file(project.repository) + end + let(:text) { "# parsed suggestion content\n# with comments" } + + def blob_lines_data(from_line, to_line) + diff_file.new_blob_lines_between(from_line, to_line) + end + + def blob_data + blob = diff_file.new_blob + blob.load_all_data! + blob.data + end + + let(:suggestion) do + described_class.new(text, line: line, above: above, below: below, diff_file: diff_file) + end + + describe '#to_hash' do + context 'when changing content surpasses the top limit' do + let(:line) { 4 } + let(:above) { 5 } + let(:below) { 2 } + let(:expected_above) { line - 1 } + let(:expected_below) { below } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + + context 'when changing content surpasses the amount of lines in the blob (bottom)' do + let(:line) { 5 } + let(:above) { 1 } + let(:below) { blob_data.lines.size + 10 } + let(:expected_below) { below } + let(:expected_above) { above } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + + context 'when lines are within blob lines boundary' do + let(:line) { 5 } + let(:above) { 2 } + let(:below) { 3 } + let(:expected_below) { below } + let(:expected_above) { above } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + + context 'when no extra lines (single-line suggestion)' do + let(:line) { 5 } + let(:above) { 0 } + let(:below) { 0 } + let(:expected_below) { below } + let(:expected_above) { above } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + end +end diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb new file mode 100644 index 00000000000..1119ea04995 --- /dev/null +++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::SuggestionsParser do + describe '.parse' do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + + let(:diff_file) do + position.diff_file(project.repository) + end + + subject do + described_class.parse(markdown, project: merge_request.project, + position: position) + end + + def blob_lines_data(from_line, to_line) + diff_file.new_blob_lines_between(from_line, to_line).join + end + + context 'single-line suggestions' do + let(:markdown) do + <<-MARKDOWN.strip_heredoc + ```suggestion + foo + bar + ``` + + ``` + nothing + ``` + + ```suggestion + xpto + baz + ``` + + ```thing + this is not a suggestion, it's a thing + ``` + MARKDOWN + end + + it 'returns a list of Gitlab::Diff::Suggestion' do + expect(subject).to all(be_a(Gitlab::Diff::Suggestion)) + expect(subject.size).to eq(2) + end + + it 'parsed suggestion has correct data' do + from_line, to_line = position.new_line, position.new_line + + expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line), + to_content: " foo\n bar\n", + lines_above: 0, + lines_below: 0) + + expect(subject.second.to_hash).to include(from_content: blob_lines_data(from_line, to_line), + to_content: " xpto\n baz\n", + lines_above: 0, + lines_below: 0) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 5299ab297f6..e418516569a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -101,6 +101,7 @@ merge_requests: - latest_merge_request_diff - merge_request_pipelines - merge_request_assignees +- suggestions merge_request_diff: - merge_request - merge_request_diff_commits @@ -352,3 +353,5 @@ resource_label_events: - label error_tracking_setting: - project +suggestions: +- note diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ee96e5c4d42..496567b0036 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -608,3 +608,14 @@ ErrorTracking::ProjectErrorTrackingSetting: - project_id - project_name - organization_name +Suggestion: +- id +- note_id +- relative_order +- applied +- commit_id +- from_content +- to_content +- outdated +- lines_above +- lines_below diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb index 3c2842e5725..5b07e598b8d 100644 --- a/spec/requests/api/suggestions_spec.rb +++ b/spec/requests/api/suggestions_spec.rb @@ -42,8 +42,7 @@ describe API::Suggestions do expect(response).to have_gitlab_http_status(200) expect(json_response) - .to include('id', 'from_original_line', 'to_original_line', - 'from_line', 'to_line', 'appliable', 'applied', + .to include('id', 'from_line', 'to_line', 'appliable', 'applied', 'from_content', 'to_content') end end diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb index 047571f161c..d38fc2b132b 100644 --- a/spec/serializers/suggestion_entity_spec.rb +++ b/spec/serializers/suggestion_entity_spec.rb @@ -13,8 +13,8 @@ describe SuggestionEntity do subject { entity.as_json } it 'exposes correct attributes' do - expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line, - :to_line, :appliable, :applied, :from_content, :to_content) + expect(subject).to include(:id, :from_line, :to_line, :appliable, + :applied, :from_content, :to_content) end it 'exposes current user abilities' do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 43ceb1dcbee..6c8ff163692 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -97,6 +97,15 @@ describe MergeRequests::RefreshService do } end + it 'outdates MR suggestions' do + expect_next_instance_of(Suggestions::OutdateService) do |service| + expect(service).to receive(:execute).with(@merge_request).and_call_original + expect(service).to receive(:execute).with(@another_merge_request).and_call_original + end + + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + end + context 'when source branch ref does not exists' do before do DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch) @@ -329,14 +338,16 @@ describe MergeRequests::RefreshService do context 'push to fork repo source branch' do let(:refresh_service) { service.new(@fork_project, @user) } - context 'open fork merge request' do - before do - allow(refresh_service).to receive(:execute_hooks) - refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') - reload_mrs - end + def refresh + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + context 'open fork merge request' do it 'executes hooks with update action' do + refresh + expect(refresh_service).to have_received(:execute_hooks) .with(@fork_merge_request, 'update', old_rev: @oldrev) @@ -347,21 +358,30 @@ describe MergeRequests::RefreshService do expect(@build_failed_todo).to be_pending expect(@fork_build_failed_todo).to be_pending end + + it 'outdates opened forked MR suggestions' do + expect_next_instance_of(Suggestions::OutdateService) do |service| + expect(service).to receive(:execute).with(@fork_merge_request).and_call_original + end + + refresh + end end context 'closed fork merge request' do before do @fork_merge_request.close! - allow(refresh_service).to receive(:execute_hooks) - refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') - reload_mrs end it 'do not execute hooks with update action' do + refresh + expect(refresh_service).not_to have_received(:execute_hooks) end it 'updates merge request to closed state' do + refresh + expect(@merge_request.notes).to be_empty expect(@merge_request).to be_open expect(@fork_merge_request.notes).to be_empty diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index fe85b5c9065..80b5dcac6c7 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -5,6 +5,41 @@ require 'spec_helper' describe Suggestions::ApplyService do include ProjectForksHelper + shared_examples 'successfully creates commit and updates suggestion' do + def apply(suggestion) + result = subject.execute(suggestion) + expect(result[:status]).to eq(:success) + end + + it 'updates the file with the new contents' do + apply(suggestion) + + blob = project.repository.blob_at_branch(merge_request.source_branch, + position.new_path) + + expect(blob.data).to eq(expected_content) + end + + it 'updates suggestion applied and commit_id columns' do + expect { apply(suggestion) } + .to change(suggestion, :applied) + .from(false).to(true) + .and change(suggestion, :commit_id) + .from(nil) + end + + it 'created commit has users email and name' do + apply(suggestion) + + commit = project.repository.commit + + expect(user.commit_email).not_to eq(user.email) + expect(commit.author_email).to eq(user.commit_email) + expect(commit.committer_email).to eq(user.commit_email) + expect(commit.author_name).to eq(user.name) + end + end + let(:project) { create(:project, :repository) } let(:user) { create(:user, :commit_email) } @@ -17,9 +52,8 @@ describe Suggestions::ApplyService do end let(:suggestion) do - create(:suggestion, note: diff_note, - from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", - to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n") + create(:suggestion, :content_from_repo, note: diff_note, + to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n") end subject { described_class.new(user) } @@ -84,39 +118,7 @@ describe Suggestions::ApplyService do project.add_maintainer(user) end - it 'updates the file with the new contents' do - subject.execute(suggestion) - - blob = project.repository.blob_at_branch(merge_request.source_branch, - position.new_path) - - expect(blob.data).to eq(expected_content) - end - - it 'returns success status' do - result = subject.execute(suggestion) - - expect(result[:status]).to eq(:success) - end - - it 'updates suggestion applied and commit_id columns' do - expect { subject.execute(suggestion) } - .to change(suggestion, :applied) - .from(false).to(true) - .and change(suggestion, :commit_id) - .from(nil) - end - - it 'created commit has users email and name' do - subject.execute(suggestion) - - commit = project.repository.commit - - expect(user.commit_email).not_to eq(user.email) - expect(commit.author_email).to eq(user.commit_email) - expect(commit.committer_email).to eq(user.commit_email) - expect(commit.author_name).to eq(user.name) - end + it_behaves_like 'successfully creates commit and updates suggestion' context 'when it fails to apply because the file was changed' do it 'returns error message' do @@ -212,11 +214,13 @@ describe Suggestions::ApplyService do end def apply_suggestion(suggestion) - suggestion.note.reload + suggestion.reload merge_request.reload merge_request.clear_memoized_shas result = subject.execute(suggestion) + expect(result[:status]).to eq(:success) + refresh = MergeRequests::RefreshService.new(project, user) refresh.execute(merge_request.diff_head_sha, suggestion.commit_id, @@ -241,7 +245,7 @@ describe Suggestions::ApplyService do suggestion_2_changes = { old_line: 24, new_line: 31, - from_content: " @cmd_output << stderr.read\n", + from_content: " @cmd_output << stderr.read\n", to_content: "# v2 change\n", path: path } @@ -368,7 +372,18 @@ describe Suggestions::ApplyService do result = subject.execute(suggestion) - expect(result).to eq(message: 'The file was not found', + expect(result).to eq(message: 'Suggestion is not appliable', + status: :error) + end + end + + context 'suggestion is eligible to be outdated' do + it 'returns error message' do + expect(suggestion).to receive(:outdated?) { true } + + result = subject.execute(suggestion) + + expect(result).to eq(message: 'Suggestion is not appliable', status: :error) end end diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb index 1b4b15b8eaa..ce4990a34a4 100644 --- a/spec/services/suggestions/create_service_spec.rb +++ b/spec/services/suggestions/create_service_spec.rb @@ -40,6 +40,14 @@ describe Suggestions::CreateService do ```thing this is not a suggestion, it's a thing ``` + + ```suggestion:-3+2 + # multi-line suggestion 1 + ``` + + ```suggestion:-5 + # multi-line suggestion 1 + ``` MARKDOWN end @@ -54,7 +62,7 @@ describe Suggestions::CreateService do end it 'does not try to parse suggestions' do - expect(Banzai::SuggestionsParser).not_to receive(:parse) + expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse) subject.execute end @@ -71,7 +79,7 @@ describe Suggestions::CreateService do it 'does not try to parse suggestions' do allow(note).to receive(:on_text?) { false } - expect(Banzai::SuggestionsParser).not_to receive(:parse) + expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse) subject.execute end @@ -87,7 +95,9 @@ describe Suggestions::CreateService do end it 'creates no suggestion when diff file is not found' do - expect(note).to receive(:latest_diff_file) { nil } + expect_next_instance_of(DiffNote) do |diff_note| + expect(diff_note).to receive(:latest_diff_file).twice { nil } + end expect { subject.execute }.not_to change(Suggestion, :count) end @@ -101,43 +111,44 @@ describe Suggestions::CreateService do note: markdown) end - context 'single line suggestions' do - it 'persists suggestion records' do - expect { subject.execute } - .to change { note.suggestions.count } - .from(0) - .to(2) - end + let(:expected_suggestions) do + Gitlab::Diff::SuggestionsParser.parse(markdown, + project: note.project, + position: note.position) + end - it 'persists original from_content lines and suggested lines' do - subject.execute + it 'persists suggestion records' do + expect { subject.execute }.to change { note.suggestions.count } + .from(0).to(expected_suggestions.size) + end - suggestions = note.suggestions.order(:relative_order) + it 'persists suggestions data correctly' do + subject.execute - suggestion_1 = suggestions.first - suggestion_2 = suggestions.last + suggestions = note.suggestions.order(:relative_order) - expect(suggestion_1).to have_attributes(from_content: " vars = {\n", - to_content: " foo\n bar\n") + suggestions.zip(expected_suggestions) do |suggestion, expected_suggestion| + expected_data = expected_suggestion.to_hash - expect(suggestion_2).to have_attributes(from_content: " vars = {\n", - to_content: " xpto\n baz\n") + expect(suggestion.from_content).to eq(expected_data[:from_content]) + expect(suggestion.to_content).to eq(expected_data[:to_content]) + expect(suggestion.lines_above).to eq(expected_data[:lines_above]) + expect(suggestion.lines_below).to eq(expected_data[:lines_below]) end + end - context 'outdated position note' do - let!(:outdated_diff) { merge_request.merge_request_diff } - let!(:latest_diff) { merge_request.create_merge_request_diff } - let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) } - let(:position) { build_position(diff_refs: latest_diff.diff_refs) } + context 'outdated position note' do + let!(:outdated_diff) { merge_request.merge_request_diff } + let!(:latest_diff) { merge_request.create_merge_request_diff } + let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) } + let(:position) { build_position(diff_refs: latest_diff.diff_refs) } - it 'uses the correct position when creating the suggestion' do - expect(note.position) - .to receive(:diff_file) - .with(project_with_repo.repository) - .and_call_original + it 'uses the correct position when creating the suggestion' do + expect(Gitlab::Diff::SuggestionsParser).to receive(:parse) + .with(note.note, project: note.project, position: note.position) + .and_call_original - subject.execute - end + subject.execute end end end diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb new file mode 100644 index 00000000000..bcc627013d8 --- /dev/null +++ b/spec/services/suggestions/outdate_service_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestions::OutdateService do + describe '#execute' do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.target_project } + let(:user) { merge_request.author } + let(:file_path) { 'files/ruby/popen.rb' } + let(:branch_name) { project.default_branch } + let(:diff_file) { suggestion.diff_file } + let(:position) { build_position(file_path, comment_line) } + let(:note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + def build_position(path, line) + Gitlab::Diff::Position.new(old_path: path, + new_path: path, + old_line: nil, + new_line: line, + diff_refs: merge_request.diff_refs) + end + + def commit_changes(file_path, new_content) + params = { + file_path: file_path, + commit_message: "Update File", + file_content: new_content, + start_project: project, + start_branch: project.default_branch, + branch_name: branch_name + } + + Files::UpdateService.new(project, user, params).execute + end + + def update_file_line(diff_file, change_line, content) + new_lines = diff_file.new_blob.data.lines + new_lines[change_line..change_line] = content + result = commit_changes(diff_file.file_path, new_lines.join) + newrev = result[:result] + + expect(result[:status]).to eq(:success) + expect(newrev).to be_present + + # Ensure all memoized data is cleared in order + # to generate the new merge_request_diff. + MergeRequest.find(merge_request.id).reload_diff(user) + + note.reload + end + + before do + project.add_maintainer(user) + end + + subject { described_class.new.execute(merge_request) } + + context 'when there is a change within multi-line suggestion range' do + let(:comment_line) { 9 } + let(:lines_above) { 8 } # suggesting to change lines 1..9 + let(:change_line) { 2 } # line 2 is within the range + let!(:suggestion) do + create(:suggestion, :content_from_repo, note: note, lines_above: lines_above) + end + + it 'updates the outdatable suggestion record' do + update_file_line(diff_file, change_line, "# foo\nbar\n") + + # Make sure note is still active + expect(note.active?).to be(true) + + expect { subject }.to change { suggestion.reload.outdated } + .from(false).to(true) + end + end + + context 'when there is no change within multi-line suggestion range' do + let(:comment_line) { 9 } + let(:lines_above) { 3 } # suggesting to change lines 6..9 + let(:change_line) { 2 } # line 2 is not within the range + let!(:suggestion) do + create(:suggestion, :content_from_repo, note: note, lines_above: lines_above) + end + + subject { described_class.new.execute(merge_request) } + + it 'does not outdates suggestion record' do + update_file_line(diff_file, change_line, "# foo\nbar\n") + + # Make sure note is still active + expect(note.active?).to be(true) + + expect { subject }.not_to change { suggestion.reload.outdated }.from(false) + end + end + end +end -- cgit v1.2.1 From 0e831b0b692f2988d3c84fc01a463b08afec05ad Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 20 Feb 2019 15:35:57 +0000 Subject: Allow external diffs to be used conditionally Since external diffs are likely to be a bit slower than in-database ones, add a mode that makes diffs external after they've been obsoleted by events. This should strike a balance between performance and disk space. A background cron drives the majority of migrations, since diffs become outdated through user actions. --- spec/factories/merge_requests.rb | 16 +++ spec/models/merge_request_diff_spec.rb | 147 ++++++++++++++++++++- .../migrate_external_diffs_service_spec.rb | 43 ++++++ spec/workers/migrate_external_diffs_worker_spec.rb | 25 ++++ .../schedule_migrate_external_diffs_worker_spec.rb | 25 ++++ 5 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 spec/services/merge_requests/migrate_external_diffs_service_spec.rb create mode 100644 spec/workers/migrate_external_diffs_worker_spec.rb create mode 100644 spec/workers/schedule_migrate_external_diffs_worker_spec.rb (limited to 'spec') diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index a73f330a7a9..a1809a26265 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -46,10 +46,26 @@ FactoryBot.define do target_branch "improve/awesome" end + trait :merged_last_month do + merged + + after(:build) do |merge_request| + merge_request.build_metrics.merged_at = 1.month.ago + end + end + trait :closed do state :closed end + trait :closed_last_month do + closed + + after(:build) do |merge_request| + merge_request.build_metrics.latest_closed_at = 1.month.ago + end + end + trait :opened do state :opened end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 53f5307ea0b..0f00ea7e85e 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -51,7 +51,104 @@ describe MergeRequestDiff do end end - describe '#latest' do + describe '.ids_for_external_storage_migration' do + set(:merge_request) { create(:merge_request) } + set(:outdated) { merge_request.merge_request_diff } + set(:latest) { merge_request.create_merge_request_diff } + + set(:closed_mr) { create(:merge_request, :closed_last_month) } + let(:closed) { closed_mr.merge_request_diff } + + set(:merged_mr) { create(:merge_request, :merged_last_month) } + let(:merged) { merged_mr.merge_request_diff } + + set(:recently_closed_mr) { create(:merge_request, :closed) } + let(:closed_recently) { recently_closed_mr.merge_request_diff } + + set(:recently_merged_mr) { create(:merge_request, :merged) } + let(:merged_recently) { recently_merged_mr.merge_request_diff } + + before do + merge_request.update!(latest_merge_request_diff: latest) + end + + subject { described_class.ids_for_external_storage_migration(limit: 1000) } + + context 'external diffs are disabled' do + before do + stub_external_diffs_setting(enabled: false) + end + + it { is_expected.to be_empty } + end + + context 'external diffs are misconfigured' do + before do + stub_external_diffs_setting(enabled: true, when: 'every second tuesday') + end + + it { is_expected.to be_empty } + end + + context 'external diffs are enabled unconditionally' do + before do + stub_external_diffs_setting(enabled: true) + end + + it { is_expected.to contain_exactly(outdated.id, latest.id, closed.id, merged.id, closed_recently.id, merged_recently.id) } + end + + context 'external diffs are enabled for outdated diffs' do + before do + stub_external_diffs_setting(enabled: true, when: 'outdated') + end + + it 'returns records for outdated merge request versions' do + is_expected.to contain_exactly(outdated.id, closed.id, merged.id) + end + end + + context 'with limit' do + it 'respects the limit' do + stub_external_diffs_setting(enabled: true) + + expect(described_class.ids_for_external_storage_migration(limit: 3).count).to eq(3) + end + end + end + + describe '#migrate_files_to_external_storage!' do + let(:diff) { create(:merge_request).merge_request_diff } + + it 'converts from in-database to external storage' do + expect(diff).not_to be_stored_externally + + stub_external_diffs_setting(enabled: true) + expect(diff).to receive(:save!) + + diff.migrate_files_to_external_storage! + + expect(diff).to be_stored_externally + end + + it 'does nothing with an external diff' do + stub_external_diffs_setting(enabled: true) + + expect(diff).to be_stored_externally + expect(diff).not_to receive(:save!) + + diff.migrate_files_to_external_storage! + end + + it 'does nothing if external diffs are disabled' do + expect(diff).not_to be_stored_externally + expect(diff).not_to receive(:save!) + + diff.migrate_files_to_external_storage! + end + end + + describe '#latest?' do let!(:mr) { create(:merge_request, :with_diffs) } let!(:first_diff) { mr.merge_request_diff } let!(:last_diff) { mr.create_merge_request_diff } @@ -222,14 +319,58 @@ describe MergeRequestDiff do include_examples 'merge request diffs' end - describe 'external diffs configured' do + describe 'external diffs always enabled' do before do - stub_external_diffs_setting(enabled: true) + stub_external_diffs_setting(enabled: true, when: 'always') end include_examples 'merge request diffs' end + describe 'exernal diffs enabled for outdated diffs' do + before do + stub_external_diffs_setting(enabled: true, when: 'outdated') + end + + include_examples 'merge request diffs' + + it 'stores up-to-date diffs in the database' do + expect(diff).not_to be_stored_externally + end + + it 'stores diffs for recently closed MRs in the database' do + mr = create(:merge_request, :closed) + + expect(mr.merge_request_diff).not_to be_stored_externally + end + + it 'stores diffs for recently merged MRs in the database' do + mr = create(:merge_request, :merged) + + expect(mr.merge_request_diff).not_to be_stored_externally + end + + it 'stores diffs for old MR versions in external storage' do + old_diff = diff + merge_request.create_merge_request_diff + old_diff.migrate_files_to_external_storage! + + expect(old_diff).to be_stored_externally + end + + it 'stores diffs for old closed MRs in external storage' do + mr = create(:merge_request, :closed_last_month) + + expect(mr.merge_request_diff).to be_stored_externally + end + + it 'stores diffs for old merged MRs in external storage' do + mr = create(:merge_request, :merged_last_month) + + expect(mr.merge_request_diff).to be_stored_externally + end + end + describe '#commit_shas' do it 'returns all commit SHAs using commits from the DB' do expect(diff_with_commits.commit_shas).not_to be_empty diff --git a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb new file mode 100644 index 00000000000..40ac747e66f --- /dev/null +++ b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::MigrateExternalDiffsService do + let(:merge_request) { create(:merge_request) } + let(:diff) { merge_request.merge_request_diff } + + describe '.enqueue!', :sidekiq do + around do |example| + Sidekiq::Testing.fake! { example.run } + end + + it 'enqueues nothing if external diffs are disabled' do + expect(diff).not_to be_stored_externally + + expect { described_class.enqueue! } + .not_to change { MigrateExternalDiffsWorker.jobs.count } + end + + it 'enqueues eligible in-database diffs if external diffs are enabled' do + expect(diff).not_to be_stored_externally + + stub_external_diffs_setting(enabled: true) + + expect { described_class.enqueue! } + .to change { MigrateExternalDiffsWorker.jobs.count } + .by(1) + end + end + + describe '#execute' do + it 'migrates an in-database diff to the external store' do + expect(diff).not_to be_stored_externally + + stub_external_diffs_setting(enabled: true) + + described_class.new(diff).execute + + expect(diff).to be_stored_externally + end + end +end diff --git a/spec/workers/migrate_external_diffs_worker_spec.rb b/spec/workers/migrate_external_diffs_worker_spec.rb new file mode 100644 index 00000000000..88d48cad14b --- /dev/null +++ b/spec/workers/migrate_external_diffs_worker_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MigrateExternalDiffsWorker do + let(:worker) { described_class.new } + let(:diff) { create(:merge_request).merge_request_diff } + + describe '#perform' do + it 'migrates the listed diff' do + expect_next_instance_of(MergeRequests::MigrateExternalDiffsService) do |instance| + expect(instance.diff).to eq(diff) + expect(instance).to receive(:execute) + end + + worker.perform(diff.id) + end + + it 'does nothing if the diff is missing' do + diff.destroy + + worker.perform(diff.id) + end + end +end diff --git a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb new file mode 100644 index 00000000000..9d6fecc9f4e --- /dev/null +++ b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ScheduleMigrateExternalDiffsWorker do + include ExclusiveLeaseHelpers + + let(:worker) { described_class.new } + + describe '#perform' do + it 'triggers a scan for diffs to migrate' do + expect(MergeRequests::MigrateExternalDiffsService).to receive(:enqueue!) + + worker.perform + end + + it 'will not run if the lease is already taken' do + stub_exclusive_lease_taken('schedule_migrate_external_diffs_worker', timeout: 2.hours) + + expect(MergeRequests::MigrateExternalDiffsService).not_to receive(:enqueue!) + + worker.perform + end + end +end -- cgit v1.2.1 From c29f441663a72027f87e37f777763b35580dec46 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Wed, 27 Mar 2019 19:03:42 +0000 Subject: Refactor verify_pages_domain_service specs Add specs for new/disabled pages domain Extract contexts from examples Use `let` instead of `set` since domain is being changed in each example --- spec/services/verify_pages_domain_service_spec.rb | 142 ++++++++++++++-------- 1 file changed, 92 insertions(+), 50 deletions(-) (limited to 'spec') diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb index d974cc0226f..ddf9d2b4917 100644 --- a/spec/services/verify_pages_domain_service_spec.rb +++ b/spec/services/verify_pages_domain_service_spec.rb @@ -9,88 +9,130 @@ describe VerifyPagesDomainService do subject(:service) { described_class.new(domain) } describe '#execute' do - context 'verification code recognition (verified domain)' do - where(:domain_sym, :code_sym) do - :domain | :verification_code - :domain | :keyed_verification_code + where(:domain_sym, :code_sym) do + :domain | :verification_code + :domain | :keyed_verification_code - :verification_domain | :verification_code - :verification_domain | :keyed_verification_code - end - - with_them do - set(:domain) { create(:pages_domain) } + :verification_domain | :verification_code + :verification_domain | :keyed_verification_code + end - let(:domain_name) { domain.send(domain_sym) } - let(:verification_code) { domain.send(code_sym) } + with_them do + let(:domain_name) { domain.send(domain_sym) } + let(:verification_code) { domain.send(code_sym) } + shared_examples 'verifies and enables the domain' do it 'verifies and enables the domain' do - stub_resolver(domain_name => ['something else', verification_code]) - expect(service.execute).to eq(status: :success) + expect(domain).to be_verified expect(domain).to be_enabled end + end - it 'verifies and enables when the code is contained partway through a TXT record' do - stub_resolver(domain_name => "something #{verification_code} else") + shared_examples 'successful enablement and verification' do + context 'when txt record contains verification code' do + before do + stub_resolver(domain_name => ['something else', verification_code]) + end - expect(service.execute).to eq(status: :success) - expect(domain).to be_verified - expect(domain).to be_enabled + include_examples 'verifies and enables the domain' end - it 'does not verify when the code is not present' do - stub_resolver(domain_name => 'something else') - - expect(service.execute).to eq(error_status) + context 'when txt record contains verification code with other text' do + before do + stub_resolver(domain_name => "something #{verification_code} else") + end - expect(domain).not_to be_verified - expect(domain).to be_enabled + include_examples 'verifies and enables the domain' end end - context 'verified domain' do - set(:domain) { create(:pages_domain) } + context 'when domain is disabled(or new)' do + let(:domain) { create(:pages_domain, :disabled) } - it 'unverifies (but does not disable) when the right code is not present' do - stub_resolver(domain.domain => 'something else') + include_examples 'successful enablement and verification' - expect(service.execute).to eq(error_status) - expect(domain).not_to be_verified - expect(domain).to be_enabled + shared_examples 'unverifies and disables domain' do + it 'unverifies and disables domain' do + expect(service.execute).to eq(error_status) + + expect(domain).not_to be_verified + expect(domain).not_to be_enabled + end end - it 'unverifies (but does not disable) when no records are present' do - stub_resolver + context 'when txt record does not contain verification code' do + before do + stub_resolver(domain_name => 'something else') + end - expect(service.execute).to eq(error_status) - expect(domain).not_to be_verified - expect(domain).to be_enabled + include_examples 'unverifies and disables domain' + end + + context 'when no txt records are present' do + before do + stub_resolver + end + + include_examples 'unverifies and disables domain' end end - context 'expired domain' do - set(:domain) { create(:pages_domain, :expired) } + context 'when domain is verified' do + let(:domain) { create(:pages_domain) } - it 'verifies and enables when the right code is present' do - stub_resolver(domain.domain => domain.keyed_verification_code) + include_examples 'successful enablement and verification' - expect(service.execute).to eq(status: :success) + context 'when txt record does not contain verification code' do + before do + stub_resolver(domain_name => 'something else') + end - expect(domain).to be_verified - expect(domain).to be_enabled + it 'unverifies but does not disable domain' do + expect(service.execute).to eq(error_status) + expect(domain).not_to be_verified + expect(domain).to be_enabled + end end - it 'disables when the right code is not present' do - error_status[:message] += '. It is now disabled.' + context 'when no txt records are present' do + before do + stub_resolver + end - stub_resolver + it 'unverifies but does not disable domain' do + expect(service.execute).to eq(error_status) + expect(domain).not_to be_verified + expect(domain).to be_enabled + end + end + end - expect(service.execute).to eq(error_status) + context 'when domain is expired' do + let(:domain) { create(:pages_domain, :expired) } - expect(domain).not_to be_verified - expect(domain).not_to be_enabled + context 'when the right code is present' do + before do + stub_resolver(domain_name => domain.keyed_verification_code) + end + + include_examples 'verifies and enables the domain' + end + + context 'when the right code is not present' do + before do + stub_resolver + end + + it 'disables domain' do + error_status[:message] += '. It is now disabled.' + + expect(service.execute).to eq(error_status) + + expect(domain).not_to be_verified + expect(domain).not_to be_enabled + end end end -- cgit v1.2.1 From fc8f4b62f887abbc02e2c21b7275f53b51a5bad2 Mon Sep 17 00:00:00 2001 From: Miranda Fluharty Date: Wed, 27 Mar 2019 19:13:55 +0000 Subject: Scaffold UI elements for minimal version Add a masked switch to variable rows Copy some behavior from the protected switch --- spec/features/group_variables_spec.rb | 2 +- spec/features/project_variables_spec.rb | 2 +- spec/fixtures/api/schemas/variable.json | 2 + .../ci_variable_list/ci_variable_list_spec.js | 19 ++-- .../features/variable_list_shared_examples.rb | 123 ++++++++++++++++----- 5 files changed, 109 insertions(+), 39 deletions(-) (limited to 'spec') diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 1a53e7c9512..fc5777e8c7c 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) } + let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', masked: true, group: group) } let(:page_path) { group_settings_ci_cd_path(group) } before do diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 6bdf5df1036..76abc640077 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Project variables', :js do let(:user) { create(:user) } let(:project) { create(:project) } - let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') } + let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value', masked: true) } let(:page_path) { project_settings_ci_cd_path(project) } before do diff --git a/spec/fixtures/api/schemas/variable.json b/spec/fixtures/api/schemas/variable.json index 6f6b044115b..305071a6b3f 100644 --- a/spec/fixtures/api/schemas/variable.json +++ b/spec/fixtures/api/schemas/variable.json @@ -4,12 +4,14 @@ "id", "key", "value", + "masked", "protected" ], "properties": { "id": { "type": "integer" }, "key": { "type": "string" }, "value": { "type": "string" }, + "masked": { "type": "boolean" }, "protected": { "type": "boolean" }, "environment_scope": { "type": "string", "optional": true } }, diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 70f49469300..394e60fc22c 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -127,20 +127,25 @@ describe('VariableList', () => { variableList.init(); }); - it('should add another row when editing the last rows protected checkbox', done => { + it('should not add another row when editing the last rows protected checkbox', done => { const $row = $wrapper.find('.js-row:last-child'); $row.find('.ci-variable-protected-item .js-project-feature-toggle').click(); getSetTimeoutPromise() .then(() => { - expect($wrapper.find('.js-row').length).toBe(2); + expect($wrapper.find('.js-row').length).toBe(1); + }) + .then(done) + .catch(done.fail); + }); - // Check for the correct default in the new row - const $protectedInput = $wrapper - .find('.js-row:last-child') - .find('.js-ci-variable-input-protected'); + it('should not add another row when editing the last rows masked checkbox', done => { + const $row = $wrapper.find('.js-row:last-child'); + $row.find('.ci-variable-masked-item .js-project-feature-toggle').click(); - expect($protectedInput.val()).toBe('false'); + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find('.js-row').length).toBe(1); }) .then(done) .catch(done.fail); diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 73156d18c1b..693b796fbdc 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -23,10 +23,13 @@ shared_examples 'variable list' do end end - it 'adds empty variable' do + it 'adds a new protected variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('') + find('.js-ci-variable-input-value').set('key_value') + find('.ci-variable-protected-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end click_button('Save variables') @@ -37,17 +40,17 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end end - it 'adds new protected variable' do + it 'defaults to masked' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') find('.js-ci-variable-input-value').set('key_value') - find('.ci-variable-protected-item .js-project-feature-toggle').click - expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') end click_button('Save variables') @@ -59,7 +62,7 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') - expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') end end @@ -163,27 +166,6 @@ shared_examples 'variable list' do end end - it 'edits variable with empty value' do - page.within('.js-ci-variable-list-section') do - click_button('Reveal value') - - page.within('.js-row:nth-child(1)') do - find('.js-ci-variable-input-key').set('new_key') - find('.js-ci-variable-input-value').set('') - end - - click_button('Save variables') - wait_for_requests - - visit page_path - - page.within('.js-row:nth-child(1)') do - expect(find('.js-ci-variable-input-key').value).to eq('new_key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') - end - end - end - it 'edits variable to be protected' do # Create the unprotected variable page.within('.js-ci-variable-list-section .js-row:last-child') do @@ -251,6 +233,57 @@ shared_examples 'variable list' do end end + it 'edits variable to be unmasked' do + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + + find('.ci-variable-masked-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + end + end + + it 'edits variable to be masked' do + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + + find('.ci-variable-masked-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + + find('.ci-variable-masked-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + end + end + it 'handles multiple edits and deletion in the middle' do page.within('.js-ci-variable-list-section') do # Create 2 variables @@ -297,11 +330,11 @@ shared_examples 'variable list' do it 'shows validation error box about duplicate keys' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('samekey') - find('.js-ci-variable-input-value').set('value1') + find('.js-ci-variable-input-value').set('value123') end page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('samekey') - find('.js-ci-variable-input-value').set('value2') + find('.js-ci-variable-input-value').set('value456') end click_button('Save variables') @@ -314,4 +347,34 @@ shared_examples 'variable list' do expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/) end end + + it 'shows validation error box about empty values' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('empty_value') + find('.js-ci-variable-input-value').set('') + end + + click_button('Save variables') + wait_for_requests + + page.within('.js-ci-variable-list-section') do + expect(all('.js-ci-variable-error-box ul li').count).to eq(1) + expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/) + end + end + + it 'shows validation error box about unmaskable values' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('unmaskable_value') + find('.js-ci-variable-input-value').set('???') + end + + click_button('Save variables') + wait_for_requests + + page.within('.js-ci-variable-list-section') do + expect(all('.js-ci-variable-error-box ul li').count).to eq(1) + expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/) + end + end end -- cgit v1.2.1 From db759c5d9ca3ba9c1610b05d6725c1427d653bef Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 16 Mar 2019 23:23:11 -0700 Subject: Allow ref name caching CommitService#find_commit For a given merge request, it's quite common to see duplicate FindCommit Gitaly requests because the Gitaly CommitService caches the request by the commit SHA, not by the ref name. However, most of the duplicate requests use the ref name, so the cache is never actually used in practice. This leads to unnecessary requests that slow performance. This commit allows certain callers to bypass the ref name to OID conversion in the cache. We don't do this by default because it's possible the tip of the branch changes during the commit, which would cause the caller to get stale data. This commit also forces the Ci::Pipeline to use the full ref name so that caching can work for merge requests. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/57083 --- .../projects/merge_requests_controller_spec.rb | 4 ++++ spec/lib/gitlab/gitaly_client/commit_service_spec.rb | 15 +++++++++++++++ spec/models/ci/pipeline_spec.rb | 8 ++++++++ 3 files changed, 27 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 79f97aa4170..c8fa93a74ee 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -86,6 +86,10 @@ describe Projects::MergeRequestsController do end describe 'as json' do + before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + end + context 'with basic serializer param' do it 'renders basic MR entity as json' do go(serializer: 'basic', format: :json) diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index d7bd757149d..6d6107ca3e7 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -221,6 +221,21 @@ describe Gitlab::GitalyClient::CommitService do expect(commit).to eq(commit_dbl) end end + + context 'when caching of the ref name is enabled' do + it 'returns a cached commit' do + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).once.and_return(double(commit: commit_dbl)) + + commit = nil + 2.times do + ::Gitlab::GitalyClient.allow_ref_name_caching do + commit = described_class.new(repository).find_commit('master') + end + end + + expect(commit).to eq(commit_dbl) + end + end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 5b8097621e0..225c6812bec 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1460,6 +1460,14 @@ describe Ci::Pipeline, :mailer do end end + context 'with a branch name as the ref' do + it 'looks up commit with the full ref name' do + expect(pipeline.project).to receive(:commit).with('refs/heads/master').and_call_original + + expect(pipeline).to be_latest + end + end + context 'with not latest sha' do before do pipeline.update( -- cgit v1.2.1 From 4f0b2079dedef24778e6176c0e1f814cdb739a90 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Wed, 27 Mar 2019 21:33:20 +0000 Subject: Upgraded Vue to 2.6.8 Upgrade to latest Version --- .../ide/components/error_message_spec.js | 2 +- spec/javascripts/ide/stores/actions/file_spec.js | 74 ++++++++-------------- spec/javascripts/notes/components/note_app_spec.js | 12 ++-- .../pipelines/graph/action_component_spec.js | 17 +++-- spec/javascripts/pipelines/stage_spec.js | 16 +++-- .../components/gke_project_id_dropdown_spec.js | 44 ++++++------- 6 files changed, 73 insertions(+), 92 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js index 430e8e2baa3..80d6c7fd564 100644 --- a/spec/javascripts/ide/components/error_message_spec.js +++ b/spec/javascripts/ide/components/error_message_spec.js @@ -84,7 +84,7 @@ describe('IDE error message component', () => { expect(vm.isLoading).toBe(true); - vm.$nextTick(() => { + setTimeout(() => { expect(vm.isLoading).toBe(false); done(); diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index 7ddc734ff56..1e5b55af4ba 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -121,68 +121,48 @@ describe('IDE store file actions', () => { store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line }); - it('calls scrollToTab', done => { - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(scrollToTabSpy).toHaveBeenCalled(); - - done(); - }) - .catch(done.fail); - }); + it('calls scrollToTab', () => { + const dispatch = jasmine.createSpy(); - it('sets the file active', done => { - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(localFile.active).toBeTruthy(); + actions.setFileActive( + { commit() {}, state: store.state, getters: store.getters, dispatch }, + localFile.path, + ); - done(); - }) - .catch(done.fail); + expect(dispatch).toHaveBeenCalledWith('scrollToTab'); }); - it('returns early if file is already active', done => { - localFile.active = true; + it('commits SET_FILE_ACTIVE', () => { + const commit = jasmine.createSpy(); - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(scrollToTabSpy).not.toHaveBeenCalled(); + actions.setFileActive( + { commit, state: store.state, getters: store.getters, dispatch() {} }, + localFile.path, + ); - done(); - }) - .catch(done.fail); + expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', { + path: localFile.path, + active: true, + }); }); - it('sets current active file to not active', done => { + it('sets current active file to not active', () => { const f = file('newActive'); store.state.entries[f.path] = f; localFile.active = true; store.state.openFiles.push(localFile); - store - .dispatch('setFileActive', f.path) - .then(() => { - expect(localFile.active).toBeFalsy(); + const commit = jasmine.createSpy(); - done(); - }) - .catch(done.fail); - }); - - it('resets location.hash for line highlighting', done => { - window.location.hash = 'test'; - - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(window.location.hash).not.toBe('test'); + actions.setFileActive( + { commit, state: store.state, getters: store.getters, dispatch() {} }, + f.path, + ); - done(); - }) - .catch(done.fail); + expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', { + path: localFile.path, + active: false, + }); }); }); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index d716ece3766..ef876dc2941 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -192,9 +192,9 @@ describe('note_app', () => { expect(service.updateNote).toHaveBeenCalled(); // Wait for the requests to finish before destroying - Vue.nextTick() - .then(done) - .catch(done.fail); + setTimeout(() => { + done(); + }); }); }); @@ -227,9 +227,9 @@ describe('note_app', () => { expect(service.updateNote).toHaveBeenCalled(); // Wait for the requests to finish before destroying - Vue.nextTick() - .then(done) - .catch(done.fail); + setTimeout(() => { + done(); + }); }); }); }); diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index 3d2232ff239..95717d659b8 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -55,13 +55,16 @@ describe('pipeline graph action component', () => { component.$el.click(); - component - .$nextTick() - .then(() => { - expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete'); - }) - .then(done) - .catch(done.fail); + setTimeout(() => { + component + .$nextTick() + .then(() => { + expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete'); + }) + .catch(done.fail); + + done(); + }, 0); }); }); }); diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js index 3c8b8032de8..19ae7860333 100644 --- a/spec/javascripts/pipelines/stage_spec.js +++ b/spec/javascripts/pipelines/stage_spec.js @@ -120,13 +120,15 @@ describe('Pipelines stage component', () => { setTimeout(() => { component.$el.querySelector('.js-ci-action').click(); - component - .$nextTick() - .then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable'); - }) - .then(done) - .catch(done.fail); + setTimeout(() => { + component + .$nextTick() + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable'); + }) + .then(done) + .catch(done.fail); + }, 0); }, 0); }); }); diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js index 030662b4d90..1eb7cb4bd5b 100644 --- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js +++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js @@ -53,36 +53,32 @@ describe('GkeProjectIdDropdown', () => { }); it('returns default toggle text', done => - vm - .$nextTick() - .then(() => { - vm.setItem(emptyProjectMock); + setTimeout(() => { + vm.setItem(emptyProjectMock); - expect(vm.toggleText).toBe(LABELS.DEFAULT); - done(); - }) - .catch(done.fail)); + expect(vm.toggleText).toBe(LABELS.DEFAULT); + + done(); + })); it('returns project name if project selected', done => - vm - .$nextTick() - .then(() => { - expect(vm.toggleText).toBe(selectedProjectMock.name); - done(); - }) - .catch(done.fail)); + setTimeout(() => { + vm.isLoading = false; + + expect(vm.toggleText).toBe(selectedProjectMock.name); + + done(); + })); it('returns empty toggle text', done => - vm - .$nextTick() - .then(() => { - vm.$store.commit(SET_PROJECTS, null); - vm.setItem(emptyProjectMock); + setTimeout(() => { + vm.$store.commit(SET_PROJECTS, null); + vm.setItem(emptyProjectMock); - expect(vm.toggleText).toBe(LABELS.EMPTY); - done(); - }) - .catch(done.fail)); + expect(vm.toggleText).toBe(LABELS.EMPTY); + + done(); + })); }); describe('selectItem', () => { -- cgit v1.2.1 From 89fdf64c8a3764cf8c646cb4b9eb9b11a3871eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 27 Mar 2019 21:42:23 +0000 Subject: Revert "Merge branch '13784-validate-variables-for-masking' into 'master'" This reverts merge request !25476 --- spec/features/group_variables_spec.rb | 2 +- spec/features/project_variables_spec.rb | 2 +- spec/fixtures/api/schemas/variable.json | 2 - .../ci_variable_list/ci_variable_list_spec.js | 19 ++-- .../features/variable_list_shared_examples.rb | 123 +++++---------------- 5 files changed, 39 insertions(+), 109 deletions(-) (limited to 'spec') diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index fc5777e8c7c..1a53e7c9512 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', masked: true, group: group) } + let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) } let(:page_path) { group_settings_ci_cd_path(group) } before do diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 76abc640077..6bdf5df1036 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Project variables', :js do let(:user) { create(:user) } let(:project) { create(:project) } - let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value', masked: true) } + let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') } let(:page_path) { project_settings_ci_cd_path(project) } before do diff --git a/spec/fixtures/api/schemas/variable.json b/spec/fixtures/api/schemas/variable.json index 305071a6b3f..6f6b044115b 100644 --- a/spec/fixtures/api/schemas/variable.json +++ b/spec/fixtures/api/schemas/variable.json @@ -4,14 +4,12 @@ "id", "key", "value", - "masked", "protected" ], "properties": { "id": { "type": "integer" }, "key": { "type": "string" }, "value": { "type": "string" }, - "masked": { "type": "boolean" }, "protected": { "type": "boolean" }, "environment_scope": { "type": "string", "optional": true } }, diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 394e60fc22c..70f49469300 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -127,25 +127,20 @@ describe('VariableList', () => { variableList.init(); }); - it('should not add another row when editing the last rows protected checkbox', done => { + it('should add another row when editing the last rows protected checkbox', done => { const $row = $wrapper.find('.js-row:last-child'); $row.find('.ci-variable-protected-item .js-project-feature-toggle').click(); getSetTimeoutPromise() .then(() => { - expect($wrapper.find('.js-row').length).toBe(1); - }) - .then(done) - .catch(done.fail); - }); + expect($wrapper.find('.js-row').length).toBe(2); - it('should not add another row when editing the last rows masked checkbox', done => { - const $row = $wrapper.find('.js-row:last-child'); - $row.find('.ci-variable-masked-item .js-project-feature-toggle').click(); + // Check for the correct default in the new row + const $protectedInput = $wrapper + .find('.js-row:last-child') + .find('.js-ci-variable-input-protected'); - getSetTimeoutPromise() - .then(() => { - expect($wrapper.find('.js-row').length).toBe(1); + expect($protectedInput.val()).toBe('false'); }) .then(done) .catch(done.fail); diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 693b796fbdc..73156d18c1b 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -23,13 +23,10 @@ shared_examples 'variable list' do end end - it 'adds a new protected variable' do + it 'adds empty variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('key_value') - find('.ci-variable-protected-item .js-project-feature-toggle').click - - expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + find('.js-ci-variable-input-value').set('') end click_button('Save variables') @@ -40,17 +37,17 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') - expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') end end - it 'defaults to masked' do + it 'adds new protected variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') find('.js-ci-variable-input-value').set('key_value') + find('.ci-variable-protected-item .js-project-feature-toggle').click - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end click_button('Save variables') @@ -62,7 +59,7 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end end @@ -166,6 +163,27 @@ shared_examples 'variable list' do end end + it 'edits variable with empty value' do + page.within('.js-ci-variable-list-section') do + click_button('Reveal value') + + page.within('.js-row:nth-child(1)') do + find('.js-ci-variable-input-key').set('new_key') + find('.js-ci-variable-input-value').set('') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-key').value).to eq('new_key') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') + end + end + end + it 'edits variable to be protected' do # Create the unprotected variable page.within('.js-ci-variable-list-section .js-row:last-child') do @@ -233,57 +251,6 @@ shared_examples 'variable list' do end end - it 'edits variable to be unmasked' do - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') - - find('.ci-variable-masked-item .js-project-feature-toggle').click - - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') - end - - click_button('Save variables') - wait_for_requests - - visit page_path - - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') - end - end - - it 'edits variable to be masked' do - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') - - find('.ci-variable-masked-item .js-project-feature-toggle').click - - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') - end - - click_button('Save variables') - wait_for_requests - - visit page_path - - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') - - find('.ci-variable-masked-item .js-project-feature-toggle').click - - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') - end - - click_button('Save variables') - wait_for_requests - - visit page_path - - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') - end - end - it 'handles multiple edits and deletion in the middle' do page.within('.js-ci-variable-list-section') do # Create 2 variables @@ -330,11 +297,11 @@ shared_examples 'variable list' do it 'shows validation error box about duplicate keys' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('samekey') - find('.js-ci-variable-input-value').set('value123') + find('.js-ci-variable-input-value').set('value1') end page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('samekey') - find('.js-ci-variable-input-value').set('value456') + find('.js-ci-variable-input-value').set('value2') end click_button('Save variables') @@ -347,34 +314,4 @@ shared_examples 'variable list' do expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/) end end - - it 'shows validation error box about empty values' do - page.within('.js-ci-variable-list-section .js-row:last-child') do - find('.js-ci-variable-input-key').set('empty_value') - find('.js-ci-variable-input-value').set('') - end - - click_button('Save variables') - wait_for_requests - - page.within('.js-ci-variable-list-section') do - expect(all('.js-ci-variable-error-box ul li').count).to eq(1) - expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/) - end - end - - it 'shows validation error box about unmaskable values' do - page.within('.js-ci-variable-list-section .js-row:last-child') do - find('.js-ci-variable-input-key').set('unmaskable_value') - find('.js-ci-variable-input-value').set('???') - end - - click_button('Save variables') - wait_for_requests - - page.within('.js-ci-variable-list-section') do - expect(all('.js-ci-variable-error-box ul li').count).to eq(1) - expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/) - end - end end -- cgit v1.2.1 From f234d9f12d6a054412067b955d3b765325d4ccc6 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Thu, 28 Mar 2019 05:48:53 +0800 Subject: Properly handle `ee` migration specs --- .../cop/migration/update_column_in_batches_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'spec') diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb index 1c8ab0ad5d2..cba01400d85 100644 --- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb +++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb @@ -93,4 +93,22 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do it_behaves_like 'a migration file with no spec file' it_behaves_like 'a migration file with a spec file' end + + context 'EE migrations' do + let(:spec_filepath) { tmp_rails_root.join('ee', 'spec', 'migrations', 'my_super_migration_spec.rb') } + + context 'in a migration' do + let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'migrate', '20121220064453_my_super_migration.rb') } + + it_behaves_like 'a migration file with no spec file' + it_behaves_like 'a migration file with a spec file' + end + + context 'in a post migration' do + let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'post_migrate', '20121220064453_my_super_migration.rb') } + + it_behaves_like 'a migration file with no spec file' + it_behaves_like 'a migration file with a spec file' + end + end end -- cgit v1.2.1 From 43afcd03332b03ef6b51ac8bc67e8f121d71cc8c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 28 Mar 2019 07:05:12 +0000 Subject: Apply suggestion to spec/lib/gitlab/current_settings_spec.rb --- spec/lib/gitlab/current_settings_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index cdf2ad1d962..909dbffa38f 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -115,7 +115,7 @@ describe Gitlab::CurrentSettings do shared_examples 'a non-persisted ApplicationSetting object' do let(:current_settings) { described_class.current_application_settings } - it 'returns a non-persisted ApplicationSetting object' do + it 'returns a FakeApplicationSettings object' do expect(current_settings).to be_a(Gitlab::FakeApplicationSettings) end -- cgit v1.2.1 From ae49284bfa7afbaa82cf88fd7e437354bedb01d2 Mon Sep 17 00:00:00 2001 From: Chris Baumbauer Date: Thu, 28 Mar 2019 10:16:09 +0000 Subject: Knative version bump 0.2.2 -> 0.3.0 --- spec/models/clusters/applications/knative_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index bf425a2617c..054ed0be240 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -107,7 +107,7 @@ describe Clusters::Applications::Knative do subject { knative.install_command } it 'should be initialized with latest version' do - expect(subject.version).to eq('0.2.2') + expect(subject.version).to eq('0.3.0') end it_behaves_like 'a command' -- cgit v1.2.1 From 96153f2055dc0bcd2ee8b34a36f33529f6ccc50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Tue, 12 Feb 2019 09:35:34 +0100 Subject: Check if labels are available for target issuable - labels have to be in the same project/group as an issuable --- .../labels/available_labels_service_spec.rb | 86 ++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 spec/services/labels/available_labels_service_spec.rb (limited to 'spec') diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb new file mode 100644 index 00000000000..4d5c87ecc53 --- /dev/null +++ b/spec/services/labels/available_labels_service_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Labels::AvailableLabelsService do + let(:user) { create(:user) } + let(:project) { create(:project, :public, group: group) } + let(:group) { create(:group) } + + let(:project_label) { create(:label, project: project) } + let(:other_project_label) { create(:label) } + let(:group_label) { create(:group_label, group: group) } + let(:other_group_label) { create(:group_label) } + let(:labels) { [project_label, other_project_label, group_label, other_group_label] } + + context '#find_or_create_by_titles' do + let(:label_titles) { labels.map(&:title).push('non existing title') } + + context 'when parent is a project' do + context 'when a user is not a project member' do + it 'returns only relevant label ids' do + result = described_class.new(user, project, labels: label_titles).find_or_create_by_titles + + expect(result).to match_array([project_label, group_label]) + end + end + + context 'when a user is a project member' do + before do + project.add_developer(user) + end + + it 'creates new labels for not found titles' do + result = described_class.new(user, project, labels: label_titles).find_or_create_by_titles + + expect(result.count).to eq(5) + expect(result).to include(project_label, group_label) + expect(result).not_to include(other_project_label, other_group_label) + end + end + end + + context 'when parent is a group' do + context 'when a user is not a group member' do + it 'returns only relevant label ids' do + result = described_class.new(user, group, labels: label_titles).find_or_create_by_titles + + expect(result).to match_array([group_label]) + end + end + + context 'when a user is a group member' do + before do + group.add_developer(user) + end + + it 'creates new labels for not found titles' do + result = described_class.new(user, group, labels: label_titles).find_or_create_by_titles + + expect(result.count).to eq(5) + expect(result).to include(group_label) + expect(result).not_to include(project_label, other_project_label, other_group_label) + end + end + end + end + + context '#filter_labels_ids_in_param' do + let(:label_ids) { labels.map(&:id).push(99999) } + + context 'when parent is a project' do + it 'returns only relevant label ids' do + result = described_class.new(user, project, ids: label_ids).filter_labels_ids_in_param(:ids) + + expect(result).to match_array([project_label.id, group_label.id]) + end + end + + context 'when parent is a group' do + it 'returns only relevant label ids' do + result = described_class.new(user, group, ids: label_ids).filter_labels_ids_in_param(:ids) + + expect(result).to match_array([group_label.id]) + end + end + end +end -- cgit v1.2.1 From 9d4061efdf7357109b6ad5f7372a8f69d0ca25a4 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 28 Mar 2019 14:17:52 +0100 Subject: Disable Vue tips when running Jest --- spec/frontend/test_setup.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'spec') diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 006fc60ef57..f5b91d0e1c3 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -22,4 +22,7 @@ beforeEach(done => { done(); }); +Vue.config.devtools = false; +Vue.config.productionTip = false; + Vue.use(Translate); -- cgit v1.2.1 From 3f16b445b7d9395f67791ba3cf871fbb6df7485d Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Thu, 28 Mar 2019 15:18:50 +0200 Subject: Add no-op license helper to CE Add no-op license helper to CE to make it compatible with EE codebase. Helper will be overwritten in EE to allow for CE specs to be run under EE codebase. https://gitlab.com/gitlab-org/gitlab-ee/issues/6539 --- spec/services/quick_actions/interpret_service_spec.rb | 1 + spec/spec_helper.rb | 1 + spec/support/helpers/license_helper.rb | 8 ++++++++ 3 files changed, 10 insertions(+) create mode 100644 spec/support/helpers/license_helper.rb (limited to 'spec') diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index ea33d156c8a..8b0f9c8ade2 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -15,6 +15,7 @@ describe QuickActions::InterpretService do let(:service) { described_class.new(project, developer) } before do + stub_licensed_features(multiple_issue_assignees: false) project.add_developer(developer) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e8d7b18bf04..f1e7bfca499 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -66,6 +66,7 @@ RSpec.configure do |config| metadata[:type] = match[1].singularize.to_sym if match end + config.include LicenseHelpers config.include ActiveJob::TestHelper config.include ActiveSupport::Testing::TimeHelpers config.include CycleAnalyticsHelpers diff --git a/spec/support/helpers/license_helper.rb b/spec/support/helpers/license_helper.rb new file mode 100644 index 00000000000..4aaad55a8ef --- /dev/null +++ b/spec/support/helpers/license_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Placeholder module for EE implementation needed for CE specs to be run in EE codebase +module LicenseHelpers + def stub_licensed_features(features) + # do nothing + end +end -- cgit v1.2.1 From cc2e849afd1a2b7c5c0ad1dcb5b675d072800b8b Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 28 Mar 2019 13:05:03 +0200 Subject: Use a fixed git abbrev parameter when we fetch a git revision --- spec/lib/gitlab_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 8232715d00e..767b5779a79 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -10,7 +10,7 @@ describe Gitlab do end describe '.revision' do - let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] } + let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h --abbrev=11 -n 1] } around do |example| described_class.instance_variable_set(:@_revision, nil) -- cgit v1.2.1 From b2fb3a9c62862476a7591ecb5ab4a6a3146e0c49 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 28 Mar 2019 11:31:14 -0300 Subject: Address review comments --- .../schedule_sync_issuables_state_id_spec.rb | 27 ++++++++-------------- 1 file changed, 10 insertions(+), 17 deletions(-) (limited to 'spec') diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb index 6b1ea804342..bf974d60b24 100644 --- a/spec/migrations/schedule_sync_issuables_state_id_spec.rb +++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb @@ -9,11 +9,8 @@ describe ScheduleSyncIssuablesStateId, :migration do let(:merge_requests) { table(:merge_requests) } let(:issues) { table(:issues) } let(:migration) { described_class.new } - - before do - @group = namespaces.create!(name: 'gitlab', path: 'gitlab') - @project = projects.create!(namespace_id: @group.id) - end + let(:group) { namespaces.create!(name: 'gitlab', path: 'gitlab') } + let(:project) { projects.create!(namespace_id: group.id) } shared_examples 'scheduling migrations' do before do @@ -40,14 +37,12 @@ describe ScheduleSyncIssuablesStateId, :migration do opened_issue = issues.create!(description: 'first', state: 'opened') closed_issue = issues.create!(description: 'second', state: 'closed') invalid_state_issue = issues.create!(description: 'fourth', state: 'not valid') - nil_state_issue = issues.create!(description: 'third', state: nil) migrate! expect(opened_issue.reload.state_id).to eq(Issue.available_states[:opened]) expect(closed_issue.reload.state_id).to eq(Issue.available_states[:closed]) expect(invalid_state_issue.reload.state_id).to be_nil - expect(nil_state_issue.reload.state_id).to be_nil end it_behaves_like 'scheduling migrations' do @@ -61,11 +56,10 @@ describe ScheduleSyncIssuablesStateId, :migration do context 'merge requests' do it 'migrates state column to integer' do - opened_merge_request = merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') - closed_merge_request = merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') - merged_merge_request = merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') - locked_merge_request = merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') - invalid_state_merge_request = merge_requests.create!(state: 'not valid', target_project_id: @project.id, target_branch: 'feature5', source_branch: 'master') + opened_merge_request = merge_requests.create!(state: 'opened', target_project_id: project.id, target_branch: 'feature1', source_branch: 'master') + closed_merge_request = merge_requests.create!(state: 'closed', target_project_id: project.id, target_branch: 'feature2', source_branch: 'master') + merged_merge_request = merge_requests.create!(state: 'merged', target_project_id: project.id, target_branch: 'feature3', source_branch: 'master') + locked_merge_request = merge_requests.create!(state: 'locked', target_project_id: project.id, target_branch: 'feature4', source_branch: 'master') migrate! @@ -73,15 +67,14 @@ describe ScheduleSyncIssuablesStateId, :migration do expect(closed_merge_request.reload.state_id).to eq(MergeRequest.available_states[:closed]) expect(merged_merge_request.reload.state_id).to eq(MergeRequest.available_states[:merged]) expect(locked_merge_request.reload.state_id).to eq(MergeRequest.available_states[:locked]) - expect(invalid_state_merge_request.reload.state_id).to be_nil end it_behaves_like 'scheduling migrations' do let(:migration) { described_class::MERGE_REQUESTS_MIGRATION } - let!(:resource_1) { merge_requests.create!(state: 'opened', target_project_id: @project.id, target_branch: 'feature1', source_branch: 'master') } - let!(:resource_2) { merge_requests.create!(state: 'closed', target_project_id: @project.id, target_branch: 'feature2', source_branch: 'master') } - let!(:resource_3) { merge_requests.create!(state: 'merged', target_project_id: @project.id, target_branch: 'feature3', source_branch: 'master') } - let!(:resource_4) { merge_requests.create!(state: 'locked', target_project_id: @project.id, target_branch: 'feature4', source_branch: 'master') } + let!(:resource_1) { merge_requests.create!(state: 'opened', target_project_id: project.id, target_branch: 'feature1', source_branch: 'master') } + let!(:resource_2) { merge_requests.create!(state: 'closed', target_project_id: project.id, target_branch: 'feature2', source_branch: 'master') } + let!(:resource_3) { merge_requests.create!(state: 'merged', target_project_id: project.id, target_branch: 'feature3', source_branch: 'master') } + let!(:resource_4) { merge_requests.create!(state: 'locked', target_project_id: project.id, target_branch: 'feature4', source_branch: 'master') } end end end -- cgit v1.2.1 From 4e5a9838fa97c9637ac36d5fd339dc311a8443b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Fri, 1 Mar 2019 11:44:11 +0000 Subject: Refactor subbed_worker calls - Creates a support/helper file and moves all the duplication defs into it --- ..._cluster_configure_worker_sidekiq_queue_spec.rb | 26 +++++++++------------- ...ate_create_trace_artifact_sidekiq_queue_spec.rb | 24 ++++++++------------ ...ate_object_storage_upload_sidekiq_queue_spec.rb | 12 +++------- .../migrate_pipeline_sidekiq_queues_spec.rb | 20 ++++++----------- .../migrate_storage_migrator_sidekiq_queue_spec.rb | 12 +++------- ...ipeline_for_merge_request_sidekiq_queue_spec.rb | 22 +++++++----------- spec/support/helpers/stub_worker.rb | 9 ++++++++ 7 files changed, 49 insertions(+), 76 deletions(-) create mode 100644 spec/support/helpers/stub_worker.rb (limited to 'spec') diff --git a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb index b2d8f476bb2..a1f243651b5 100644 --- a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20181219145520_migrate_cluster_co describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queue' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) described_class.new.up @@ -19,12 +20,12 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do it 'does not affect other queues under the same namespace' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1]) described_class.new.up @@ -39,7 +40,7 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) described_class.new.down @@ -58,11 +59,4 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb index c18ae3b76d3..66555118a43 100644 --- a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_tra describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) described_class.new.up @@ -19,11 +20,11 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do it 'does not affect other queues under the same namespace' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) described_class.new.up @@ -37,7 +38,7 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) described_class.new.down @@ -56,11 +57,4 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb index 1ee6c440cf4..6ce04805e5d 100644 --- a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180603190921_migrate_object_sto describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queue' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'object_storage_upload').perform_async('Something', [1]) - stubbed_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1]) + stub_worker(queue: 'object_storage_upload').perform_async('Something', [1]) + stub_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1]) described_class.new.up @@ -23,11 +24,4 @@ describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do expect { described_class.new.up }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb index e02bcd2f4da..e38044ccceb 100644 --- a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb +++ b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20170822101017_migrate_pipeline_s describe MigratePipelineSidekiqQueues, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :pipeline).perform_async('Something', [1]) - stubbed_worker(queue: :build).perform_async('Something', [1]) + stub_worker(queue: :pipeline).perform_async('Something', [1]) + stub_worker(queue: :build).perform_async('Something', [1]) described_class.new.up @@ -20,10 +21,10 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :pipeline_default).perform_async('Class', [1]) - stubbed_worker(queue: :pipeline_processing).perform_async('Class', [2]) - stubbed_worker(queue: :pipeline_hooks).perform_async('Class', [3]) - stubbed_worker(queue: :pipeline_cache).perform_async('Class', [4]) + stub_worker(queue: :pipeline_default).perform_async('Class', [1]) + stub_worker(queue: :pipeline_processing).perform_async('Class', [2]) + stub_worker(queue: :pipeline_hooks).perform_async('Class', [3]) + stub_worker(queue: :pipeline_cache).perform_async('Class', [4]) described_class.new.down @@ -45,11 +46,4 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb index f8cf76cb339..94de208e53e 100644 --- a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb @@ -3,11 +3,12 @@ require Rails.root.join('db', 'post_migrate', '20190124200344_migrate_storage_mi describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :storage_migrator).perform_async(1, 5) + stub_worker(queue: :storage_migrator).perform_async(1, 5) described_class.new.up @@ -18,7 +19,7 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5) + stub_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5) described_class.new.down @@ -37,11 +38,4 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb index 5e3b20ab4a8..976f3ce07d7 100644 --- a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180307012445_migrate_update_hea describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) described_class.new.up @@ -19,10 +20,10 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis it 'does not affect other queues under the same namespace' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) described_class.new.up @@ -35,7 +36,7 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) described_class.new.down @@ -54,11 +55,4 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb new file mode 100644 index 00000000000..58b7ee93dff --- /dev/null +++ b/spec/support/helpers/stub_worker.rb @@ -0,0 +1,9 @@ +# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb +module StubWorker + def stub_worker(queue:) + Class.new do + include Sidekiq::Worker + sidekiq_options queue: queue + end + end +end -- cgit v1.2.1 From cf07f9fd006a3ecec1ea8e2275cf07e35fcf7540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Mar 2019 10:58:53 +0100 Subject: [CE] Reduce the diff with EE in spec/helpers/appearances_helper_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/helpers/appearances_helper_spec.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb index 8d717b968dd..a3511e078ce 100644 --- a/spec/helpers/appearances_helper_spec.rb +++ b/spec/helpers/appearances_helper_spec.rb @@ -65,12 +65,10 @@ describe AppearancesHelper do end describe '#brand_title' do - it 'returns the default CE title when no appearance is present' do - allow(helper) - .to receive(:current_appearance) - .and_return(nil) + it 'returns the default title when no appearance is present' do + allow(helper).to receive(:current_appearance).and_return(nil) - expect(helper.brand_title).to eq('GitLab Community Edition') + expect(helper.brand_title).to eq(helper.default_brand_title) end end end -- cgit v1.2.1 From 1067c87b03f9232bf77028ab1ea7e96ce2361acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Mar 2019 11:22:37 +0100 Subject: [CE] Reduce the diff with EE in spec/helpers/auth_helper_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/helpers/auth_helper_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index 2ba8b3dbf22..aae515def0c 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe AuthHelper do -- cgit v1.2.1 From af8aa24666e448752ab4012673fe9951688a20d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Mar 2019 11:24:08 +0100 Subject: [CE] Reduce the diff with EE in spec/helpers/group_helper_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/helpers/groups_helper_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 540a8674ec2..91541a16c13 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupsHelper do -- cgit v1.2.1 From dbe27cb428fbdbd4107365c11736fd21aaa659ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Mar 2019 11:17:58 +0100 Subject: [CE] Reduce the diff with EE in spec/helpers/issuables_helper_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/helpers/issuables_helper_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 8b82dea2524..1d1446eaa30 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssuablesHelper do @@ -176,7 +178,7 @@ describe IssuablesHelper do stub_commonmark_sourcepos_disabled end - it 'returns the correct json for an issue' do + it 'returns the correct data for an issue' do issue = create(:issue, author: user, description: 'issue text') @project = issue.project @@ -198,7 +200,7 @@ describe IssuablesHelper do initialDescriptionText: 'issue text', initialTaskStatus: '0 of 0 tasks completed' } - expect(helper.issuable_initial_data(issue)).to eq(expected_data) + expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data)) end end end -- cgit v1.2.1 From 0b804812660f616c15a1f998adbad8c84307cd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Mar 2019 11:33:06 +0100 Subject: [CE] Reduce the diff with EE in spec/helpers/merge_requests_helper_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/helpers/merge_requests_helper_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 885204062fe..193390d2f2c 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequestsHelper do -- cgit v1.2.1 From f6aa22fc9a6eaba4accad8d069b9054b2e26fb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Mar 2019 11:37:02 +0100 Subject: [CE] Reduce the diff with EE in spec/helpers/projects_helper_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/helpers/projects_helper_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 291eafece94..37c63807c82 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectsHelper do -- cgit v1.2.1 From 63f6fcd0b7876b74f7d42f3c787a8fdb53a264e2 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 28 Mar 2019 13:38:29 +0100 Subject: Set gon.ee in Jest --- spec/frontend/environment.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index cb128c7d880..1067a53906a 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -6,6 +6,7 @@ const JSDOMEnvironment = require('jest-environment-jsdom'); class CustomEnvironment extends JSDOMEnvironment { constructor(config, context) { super(config, context); + Object.assign(context.console, { error(...args) { throw new ErrorWithStack( @@ -21,6 +22,11 @@ class CustomEnvironment extends JSDOMEnvironment { ); }, }); + + const { testEnvironmentOptions } = config; + this.global.gon = { + ee: testEnvironmentOptions.IS_EE, + }; } } -- cgit v1.2.1 From 2e7486681abb5c707f62f8ac73506228e8f4f4e8 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 28 Mar 2019 20:07:40 +0100 Subject: Copy missing helpers from Karma to Jest Copy missing helpers from Karma to Jest --- spec/frontend/helpers/class_spec_helper.js | 9 ++ spec/frontend/helpers/locale_helper.js | 11 +++ spec/frontend/helpers/scroll_into_view_promise.js | 28 ++++++ .../frontend/helpers/set_timeout_promise_helper.js | 4 + spec/frontend/helpers/user_mock_data_helper.js | 14 +++ spec/frontend/helpers/vue_component_helper.js | 18 ++++ spec/frontend/helpers/vue_resource_helper.js | 11 +++ spec/frontend/helpers/vue_test_utils_helper.js | 19 ++++ spec/frontend/helpers/vuex_action_helper.js | 104 +++++++++++++++++++++ spec/frontend/helpers/wait_for_attribute_change.js | 16 ++++ spec/frontend/helpers/wait_for_promises.js | 1 + 11 files changed, 235 insertions(+) create mode 100644 spec/frontend/helpers/class_spec_helper.js create mode 100644 spec/frontend/helpers/locale_helper.js create mode 100644 spec/frontend/helpers/scroll_into_view_promise.js create mode 100644 spec/frontend/helpers/set_timeout_promise_helper.js create mode 100644 spec/frontend/helpers/user_mock_data_helper.js create mode 100644 spec/frontend/helpers/vue_component_helper.js create mode 100644 spec/frontend/helpers/vue_resource_helper.js create mode 100644 spec/frontend/helpers/vue_test_utils_helper.js create mode 100644 spec/frontend/helpers/vuex_action_helper.js create mode 100644 spec/frontend/helpers/wait_for_attribute_change.js create mode 100644 spec/frontend/helpers/wait_for_promises.js (limited to 'spec') diff --git a/spec/frontend/helpers/class_spec_helper.js b/spec/frontend/helpers/class_spec_helper.js new file mode 100644 index 00000000000..7a60d33b471 --- /dev/null +++ b/spec/frontend/helpers/class_spec_helper.js @@ -0,0 +1,9 @@ +export default class ClassSpecHelper { + static itShouldBeAStaticMethod(base, method) { + return it('should be a static method', () => { + expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy(); + }); + } +} + +window.ClassSpecHelper = ClassSpecHelper; diff --git a/spec/frontend/helpers/locale_helper.js b/spec/frontend/helpers/locale_helper.js new file mode 100644 index 00000000000..80047b06003 --- /dev/null +++ b/spec/frontend/helpers/locale_helper.js @@ -0,0 +1,11 @@ +/* eslint-disable import/prefer-default-export */ + +export const setLanguage = languageCode => { + const htmlElement = document.querySelector('html'); + + if (languageCode) { + htmlElement.setAttribute('lang', languageCode); + } else { + htmlElement.removeAttribute('lang'); + } +}; diff --git a/spec/frontend/helpers/scroll_into_view_promise.js b/spec/frontend/helpers/scroll_into_view_promise.js new file mode 100644 index 00000000000..0edea2103da --- /dev/null +++ b/spec/frontend/helpers/scroll_into_view_promise.js @@ -0,0 +1,28 @@ +export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) { + return new Promise((resolve, reject) => { + let intersectionObserver; + let retry = 0; + + const intervalId = setInterval(() => { + if (retry >= maxTries) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`)); + } + retry += 1; + intersectionTarget.scrollIntoView(); + }, timeout); + + intersectionObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + resolve(); + } + }); + + intersectionObserver.observe(intersectionTarget); + + intersectionTarget.scrollIntoView(); + }); +} diff --git a/spec/frontend/helpers/set_timeout_promise_helper.js b/spec/frontend/helpers/set_timeout_promise_helper.js new file mode 100644 index 00000000000..47087619187 --- /dev/null +++ b/spec/frontend/helpers/set_timeout_promise_helper.js @@ -0,0 +1,4 @@ +export default (time = 0) => + new Promise(resolve => { + setTimeout(resolve, time); + }); diff --git a/spec/frontend/helpers/user_mock_data_helper.js b/spec/frontend/helpers/user_mock_data_helper.js new file mode 100644 index 00000000000..6999fa1f8a1 --- /dev/null +++ b/spec/frontend/helpers/user_mock_data_helper.js @@ -0,0 +1,14 @@ +export default { + createNumberRandomUsers(numberUsers) { + const users = []; + for (let i = 0; i < numberUsers; i += 1) { + users.push({ + avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: i + 1, + name: `GitLab User ${i}`, + username: `gitlab${i}`, + }); + } + return users; + }, +}; diff --git a/spec/frontend/helpers/vue_component_helper.js b/spec/frontend/helpers/vue_component_helper.js new file mode 100644 index 00000000000..e0fe18e5560 --- /dev/null +++ b/spec/frontend/helpers/vue_component_helper.js @@ -0,0 +1,18 @@ +/** + * Replaces line break with an empty space + * @param {*} data + */ +export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' '); + +/** + * Removes line breaks, spaces and trims the given text + * @param {String} str + * @returns {String} + */ +export const trimText = str => + str + .replace(/\r?\n|\r/g, '') + .replace(/\s\s+/g, ' ') + .trim(); + +export const removeWhitespace = str => str.replace(/\s\s+/g, ' '); diff --git a/spec/frontend/helpers/vue_resource_helper.js b/spec/frontend/helpers/vue_resource_helper.js new file mode 100644 index 00000000000..0f58af09933 --- /dev/null +++ b/spec/frontend/helpers/vue_resource_helper.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line import/prefer-default-export +export const headersInterceptor = (request, next) => { + next(response => { + const headers = {}; + response.headers.forEach((value, key) => { + headers[key] = value; + }); + // eslint-disable-next-line no-param-reassign + response.headers = headers; + }); +}; diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js new file mode 100644 index 00000000000..19e27388eeb --- /dev/null +++ b/spec/frontend/helpers/vue_test_utils_helper.js @@ -0,0 +1,19 @@ +/* eslint-disable import/prefer-default-export */ + +const vNodeContainsText = (vnode, text) => + (vnode.text && vnode.text.includes(text)) || + (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length); + +/** + * Determines whether a `shallowMount` Wrapper contains text + * within one of it's slots. This will also work on Wrappers + * acquired with `find()`, but only if it's parent Wrapper + * was shallowMounted. + * NOTE: Prefer checking the rendered output of a component + * wherever possible using something like `text()` instead. + * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted) + * @param {String} slotName + * @param {String} text + */ +export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) => + !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length; diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js new file mode 100644 index 00000000000..88652202a8e --- /dev/null +++ b/spec/frontend/helpers/vuex_action_helper.js @@ -0,0 +1,104 @@ +const noop = () => {}; + +/** + * Helper for testing action with expected mutations inspired in + * https://vuex.vuejs.org/en/testing.html + * + * @param {Function} action to be tested + * @param {Object} payload will be provided to the action + * @param {Object} state will be provided to the action + * @param {Array} [expectedMutations=[]] mutations expected to be committed + * @param {Array} [expectedActions=[]] actions expected to be dispatched + * @param {Function} [done=noop] to be executed after the tests + * @return {Promise} + * + * @example + * testAction( + * actions.actionName, // action + * { }, // mocked payload + * state, //state + * // expected mutations + * [ + * { type: types.MUTATION} + * { type: types.MUTATION_1, payload: jasmine.any(Number)} + * ], + * // expected actions + * [ + * { type: 'actionName', payload: {param: 'foobar'}}, + * { type: 'actionName1'} + * ] + * done, + * ); + * + * @example + * testAction( + * actions.actionName, // action + * { }, // mocked payload + * state, //state + * [ { type: types.MUTATION} ], // expected mutations + * [], // expected actions + * ).then(done) + * .catch(done.fail); + */ +export default ( + action, + payload, + state, + expectedMutations = [], + expectedActions = [], + done = noop, +) => { + const mutations = []; + const actions = []; + + // mock commit + const commit = (type, mutationPayload) => { + const mutation = { type }; + + if (typeof mutationPayload !== 'undefined') { + mutation.payload = mutationPayload; + } + + mutations.push(mutation); + }; + + // mock dispatch + const dispatch = (type, actionPayload) => { + const dispatchedAction = { type }; + + if (typeof actionPayload !== 'undefined') { + dispatchedAction.payload = actionPayload; + } + + actions.push(dispatchedAction); + }; + + const validateResults = () => { + expect({ + mutations, + actions, + }).toEqual({ + mutations: expectedMutations, + actions: expectedActions, + }); + done(); + }; + + const result = action( + { commit, state, dispatch, rootState: state, rootGetters: state, getters: state }, + payload, + ); + + return new Promise(resolve => { + setImmediate(resolve); + }) + .then(() => result) + .catch(error => { + validateResults(); + throw error; + }) + .then(data => { + validateResults(); + return data; + }); +}; diff --git a/spec/frontend/helpers/wait_for_attribute_change.js b/spec/frontend/helpers/wait_for_attribute_change.js new file mode 100644 index 00000000000..8f22d569222 --- /dev/null +++ b/spec/frontend/helpers/wait_for_attribute_change.js @@ -0,0 +1,16 @@ +export default (domElement, attributes, timeout = 1500) => + new Promise((resolve, reject) => { + let observer; + const timeoutId = setTimeout(() => { + observer.disconnect(); + reject(new Error(`Could not see an attribute update within ${timeout} ms`)); + }, timeout); + + observer = new MutationObserver(() => { + clearTimeout(timeoutId); + observer.disconnect(); + resolve(); + }); + + observer.observe(domElement, { attributes: true, attributeFilter: attributes }); + }); diff --git a/spec/frontend/helpers/wait_for_promises.js b/spec/frontend/helpers/wait_for_promises.js new file mode 100644 index 00000000000..1d2b53fc770 --- /dev/null +++ b/spec/frontend/helpers/wait_for_promises.js @@ -0,0 +1 @@ +export default () => new Promise(resolve => requestAnimationFrame(resolve)); -- cgit v1.2.1 From 7a7866a54a99bb97f97c9b85ce0a59f3078c5f1f Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 28 Mar 2019 20:42:17 +0100 Subject: Move abuse_reports_spec.js to Jest --- .../admin/abuse_reports/abuse_reports_spec.js | 46 ++++++++++++++++++++++ .../admin/abuse_reports/abuse_reports_spec.js | 46 ---------------------- 2 files changed, 46 insertions(+), 46 deletions(-) create mode 100644 spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js delete mode 100644 spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js (limited to 'spec') diff --git a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js new file mode 100644 index 00000000000..1e0bc708c31 --- /dev/null +++ b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js @@ -0,0 +1,46 @@ +import $ from 'jquery'; +import '~/lib/utils/text_utility'; +import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; + +describe('Abuse Reports', () => { + const FIXTURE = 'abuse_reports/abuse_reports_list.html'; + const MAX_MESSAGE_LENGTH = 500; + + let $messages; + + const assertMaxLength = $message => { + expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); + }; + const findMessage = searchText => + $messages.filter((index, element) => element.innerText.indexOf(searchText) > -1).first(); + + preloadFixtures(FIXTURE); + + beforeEach(() => { + loadFixtures(FIXTURE); + new AbuseReports(); // eslint-disable-line no-new + $messages = $('.abuse-reports .message'); + }); + + it('should truncate long messages', () => { + const $longMessage = findMessage('LONG MESSAGE'); + + expect($longMessage.data('originalMessage')).toEqual(jasmine.anything()); + assertMaxLength($longMessage); + }); + + it('should not truncate short messages', () => { + const $shortMessage = findMessage('SHORT MESSAGE'); + + expect($shortMessage.data('originalMessage')).not.toEqual(jasmine.anything()); + }); + + it('should allow clicking a truncated message to expand and collapse the full message', () => { + const $longMessage = findMessage('LONG MESSAGE'); + $longMessage.click(); + + expect($longMessage.data('originalMessage').length).toEqual($longMessage.text().length); + $longMessage.click(); + assertMaxLength($longMessage); + }); +}); diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js deleted file mode 100644 index f7637964c60..00000000000 --- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import $ from 'jquery'; -import '~/lib/utils/text_utility'; -import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; - -describe('Abuse Reports', () => { - const FIXTURE = 'abuse_reports/abuse_reports_list.html'; - const MAX_MESSAGE_LENGTH = 500; - - let $messages; - - const assertMaxLength = $message => { - expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); - }; - const findMessage = searchText => - $messages.filter((index, element) => element.innerText.indexOf(searchText) > -1).first(); - - preloadFixtures(FIXTURE); - - beforeEach(function() { - loadFixtures(FIXTURE); - this.abuseReports = new AbuseReports(); - $messages = $('.abuse-reports .message'); - }); - - it('should truncate long messages', () => { - const $longMessage = findMessage('LONG MESSAGE'); - - expect($longMessage.data('originalMessage')).toEqual(jasmine.anything()); - assertMaxLength($longMessage); - }); - - it('should not truncate short messages', () => { - const $shortMessage = findMessage('SHORT MESSAGE'); - - expect($shortMessage.data('originalMessage')).not.toEqual(jasmine.anything()); - }); - - it('should allow clicking a truncated message to expand and collapse the full message', () => { - const $longMessage = findMessage('LONG MESSAGE'); - $longMessage.click(); - - expect($longMessage.data('originalMessage').length).toEqual($longMessage.text().length); - $longMessage.click(); - assertMaxLength($longMessage); - }); -}); -- cgit v1.2.1 From 346f7027e2285b3ff1280197045c4db541f4a700 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 28 Mar 2019 20:42:32 +0100 Subject: Add workaround for innerText in Jest --- spec/frontend/test_setup.js | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 006fc60ef57..1edb2450b4c 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -23,3 +23,12 @@ beforeEach(done => { }); Vue.use(Translate); + +// workaround for JSDOM not supporting innerText +// see https://github.com/jsdom/jsdom/issues/1245 +Object.defineProperty(global.Element.prototype, 'innerText', { + get() { + return this.textContent; + }, + configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch +}); -- cgit v1.2.1 From 9c2237569d9ff9dcca8182de794506b5500ef024 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 27 Mar 2019 14:50:11 +0100 Subject: Add helpers for HTML fixtures to Jest --- spec/frontend/helpers/fixtures.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js index de9058d7832..f0351aa31c6 100644 --- a/spec/frontend/helpers/fixtures.js +++ b/spec/frontend/helpers/fixtures.js @@ -1,5 +1,3 @@ -/* eslint-disable import/prefer-default-export, global-require, import/no-dynamic-require */ - import fs from 'fs'; import path from 'path'; @@ -7,16 +5,32 @@ import { ErrorWithStack } from 'jest-util'; const fixturesBasePath = path.join(process.cwd(), 'spec', 'javascripts', 'fixtures'); -export function getJSONFixture(relativePath, ee = false) { - const absolutePath = path.join(fixturesBasePath, ee ? 'ee' : '', relativePath); +export function getFixture(relativePath) { + const absolutePath = path.join(fixturesBasePath, relativePath); if (!fs.existsSync(absolutePath)) { throw new ErrorWithStack( `Fixture file ${relativePath} does not exist. Did you run bin/rake karma:fixtures?`, - getJSONFixture, + getFixture, ); } - return require(absolutePath); + return fs.readFileSync(absolutePath, 'utf8'); } + +export const getJSONFixture = relativePath => JSON.parse(getFixture(relativePath)); + +export const resetHTMLFixture = () => { + document.body.textContent = ''; +}; + +export const setHTMLFixture = (htmlContent, resetHook = afterEach) => { + document.body.outerHTML = htmlContent; + resetHook(resetHTMLFixture); +}; + +export const loadHTMLFixture = (relativePath, resetHook = afterEach) => { + const fileContent = getFixture(relativePath); + setHTMLFixture(fileContent, resetHook); +}; -- cgit v1.2.1 From b0e26ed5cedc9452b63616a58d52c404ff5b5c98 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 27 Mar 2019 14:55:40 +0100 Subject: Add Karma-style fixture helpers to Jest --- spec/frontend/.eslintrc.yml | 9 +++++++-- spec/frontend/environment.js | 1 + spec/frontend/test_setup.js | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml index 046215e4c93..054dc27cda6 100644 --- a/spec/frontend/.eslintrc.yml +++ b/spec/frontend/.eslintrc.yml @@ -2,8 +2,13 @@ env: jest/globals: true plugins: -- jest + - jest settings: import/resolver: jest: - jestConfigFile: "jest.config.js" + jestConfigFile: 'jest.config.js' +globals: + getJSONFixture: false + loadFixtures: false + preloadFixtures: false + setFixtures: false diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index cb128c7d880..d38f8f3bac3 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -6,6 +6,7 @@ const JSDOMEnvironment = require('jest-environment-jsdom'); class CustomEnvironment extends JSDOMEnvironment { constructor(config, context) { super(config, context); + Object.assign(context.console, { error(...args) { throw new ErrorWithStack( diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 1edb2450b4c..2986e5d6f86 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; import { initializeTestTimeout } from './helpers/timeout'; +import { getJSONFixture, loadHTMLFixture, setHTMLFixture } from './helpers/fixtures'; // wait for pending setTimeout()s afterEach(() => { @@ -32,3 +33,11 @@ Object.defineProperty(global.Element.prototype, 'innerText', { }, configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch }); + +// convenience wrapper for migration from Karma +Object.assign(global, { + loadFixtures: loadHTMLFixture, + loadJSONFixtures: getJSONFixture, + preloadFixtures() {}, + setFixtures: setHTMLFixture, +}); -- cgit v1.2.1 From bf639fd504c84929ff8542eef81578a6745bf428 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 21 Mar 2019 20:08:32 +0700 Subject: Create detached merge request pipelines By using `refs/merge-requests/:iid/head` ok ok Improve naming nicely Add nice tests add nice tests fix some more revert --- spec/factories/merge_requests.rb | 17 ++++++-- spec/lib/gitlab/ci/pipeline/chain/command_spec.rb | 18 ++++++++ .../ci/pipeline/chain/validate/abilities_spec.rb | 47 ++++++++++++++++++++- .../ci/pipeline/chain/validate/repository_spec.rb | 19 +++++++++ spec/models/ci/build_spec.rb | 20 +++++++++ spec/models/ci/pipeline_spec.rb | 36 ++++++++++++++++ spec/models/merge_request_spec.rb | 28 +++++++++++++ spec/presenters/ci/build_runner_presenter_spec.rb | 38 +++++++++++++++++ .../services/merge_requests/create_service_spec.rb | 48 ++++++++++++++++++---- .../merge_requests/refresh_service_spec.rb | 41 ++++++++++++++---- 10 files changed, 294 insertions(+), 18 deletions(-) (limited to 'spec') diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index a1809a26265..abf0e6bccb7 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -117,9 +117,20 @@ FactoryBot.define do end end + trait :with_legacy_detached_merge_request_pipeline do + after(:create) do |merge_request| + merge_request.merge_request_pipelines << create(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, + project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.source_branch_sha) + end + end + trait :with_detached_merge_request_pipeline do - after(:build) do |merge_request| - merge_request.merge_request_pipelines << build(:ci_pipeline, + after(:create) do |merge_request| + merge_request.merge_request_pipelines << create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, project: merge_request.source_project, @@ -135,7 +146,7 @@ FactoryBot.define do target_sha { target_branch_sha } end - after(:build) do |merge_request, evaluator| + after(:create) do |merge_request, evaluator| merge_request.merge_request_pipelines << create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index dab0fb51bcc..5181e9c1583 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -48,6 +48,24 @@ describe Gitlab::Ci::Pipeline::Chain::Command do end end + describe '#merge_request_ref_exists?' do + subject { command.merge_request_ref_exists? } + + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + context 'for existing merge request ref' do + let(:origin_ref) { merge_request.ref_path } + + it { is_expected.to eq(true) } + end + + context 'for branch ref' do + let(:origin_ref) { merge_request.source_branch } + + it { is_expected.to eq(false) } + end + end + describe '#ref' do subject { command.ref } diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb index 8ba56d73838..7d750877d09 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb @@ -10,12 +10,33 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( - project: project, current_user: user, origin_ref: ref) + project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request) end let(:step) { described_class.new(pipeline, command) } let(:ref) { 'master' } + let(:origin_ref) { ref } + let(:merge_request) { nil } + + shared_context 'detached merge request pipeline' do + let(:merge_request) do + create(:merge_request, + source_project: project, + source_branch: ref, + target_project: project, + target_branch: 'feature') + end + + let(:pipeline) do + build(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, + project: project) + end + + let(:origin_ref) { merge_request.ref_path } + end context 'when users has no ability to run a pipeline' do before do @@ -58,6 +79,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do it { is_expected.to be_truthy } + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_truthy } + end + context 'when the branch is protected' do let!(:protected_branch) do create(:protected_branch, project: project, name: ref) @@ -65,6 +92,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do it { is_expected.to be_falsey } + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_falsey } + end + context 'when developers are allowed to merge' do let!(:protected_branch) do create(:protected_branch, @@ -74,6 +107,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do end it { is_expected.to be_truthy } + + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_truthy } + end end end @@ -112,6 +151,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do end it { is_expected.to be_truthy } + + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_truthy } + end end context 'when the tag is protected' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb index a7cad423d09..2e8c9d70098 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb @@ -42,6 +42,25 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do end end + context 'when origin ref is a merge request ref' do + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, current_user: user, origin_ref: origin_ref, checkout_sha: checkout_sha) + end + + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:origin_ref) { merge_request.ref_path } + let(:checkout_sha) { project.repository.commit(merge_request.ref_path).id } + + it 'does not break the chain' do + expect(step.break?).to be false + end + + it 'does not append pipeline errors' do + expect(pipeline.errors).to be_empty + end + end + context 'when ref is ambiguous' do let(:project) do create(:project, :repository).tap do |proj| diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7500e6ae5b1..3ec07143e93 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -24,6 +24,8 @@ describe Ci::Build do it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:trace) } it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) } + it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) } + it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } it { is_expected.to be_a(ArtifactMigratable) } @@ -3626,6 +3628,24 @@ describe Ci::Build do it { is_expected.to be_falsey } end end + + context 'when refspecs feature is required by build' do + before do + allow(build).to receive(:merge_request_ref?) { true } + end + + context 'when runner provides given feature' do + let(:runner_features) { { refspecs: true } } + + it { is_expected.to be_truthy } + end + + context 'when runner does not provide given feature' do + let(:runner_features) { {} } + + it { is_expected.to be_falsey } + end + end end describe '#deployment_status' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 5b8097621e0..eb6ed3b658d 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -362,6 +362,42 @@ describe Ci::Pipeline, :mailer do end end + describe '#merge_request_ref?' do + subject { pipeline.merge_request_ref? } + + it 'calls MergeRequest#merge_request_ref?' do + expect(MergeRequest).to receive(:merge_request_ref?).with(pipeline.ref) + + subject + end + end + + describe '#legacy_detached_merge_request_pipeline?' do + subject { pipeline.legacy_detached_merge_request_pipeline? } + + set(:merge_request) { create(:merge_request) } + let(:ref) { 'feature' } + let(:target_sha) { nil } + + let(:pipeline) do + build(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: target_sha) + end + + it { is_expected.to be_truthy } + + context 'when pipeline ref is a merge request ref' do + let(:ref) { 'refs/merge-requests/1/head' } + + it { is_expected.to be_falsy } + end + + context 'when target sha is set' do + let(:target_sha) { 'target-sha' } + + it { is_expected.to be_falsy } + end + end + describe '#matches_sha_or_source_sha?' do subject { pipeline.matches_sha_or_source_sha?(sample_sha) } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index a1de0c63623..5adeb616e84 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3115,4 +3115,32 @@ describe MergeRequest do end end end + + describe '.merge_request_ref?' do + subject { described_class.merge_request_ref?(ref) } + + context 'when ref is ref name of a branch' do + let(:ref) { 'feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a branch' do + let(:ref) { 'refs/heads/feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/head' } + + it { is_expected.to be_truthy } + end + + context 'when ref is merge ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/merge' } + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index f50bcf54b46..ad6cb012d0b 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -136,6 +136,24 @@ describe Ci::BuildRunnerPresenter do is_expected.to eq(1) end end + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.first } + let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } + + it 'returns the default git depth for pipelines for merge requests' do + is_expected.to eq(described_class::DEFAULT_GIT_DEPTH_MERGE_REQUEST) + end + + context 'when pipeline is legacy detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } + + it 'behaves as branch pipeline' do + is_expected.to eq(0) + end + end + end end describe '#refspecs' do @@ -165,5 +183,25 @@ describe Ci::BuildRunnerPresenter do end end end + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.first } + let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } + + it 'returns the correct refspecs' do + is_expected + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') + end + + context 'when pipeline is legacy detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } + + it 'returns the correct refspecs' do + is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', + '+refs/heads/*:refs/remotes/origin/*') + end + end + end end end diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index a04a4d5fc36..55e7b46248b 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -173,7 +173,7 @@ describe MergeRequests::CreateService do end end - describe 'Merge request pipelines' do + describe 'Pipelines for merge requests' do before do stub_ci_pipeline_yaml_file(YAML.dump(config)) end @@ -189,12 +189,46 @@ describe MergeRequests::CreateService do } end - it 'creates a merge request pipeline and sets it as a head pipeline' do + it 'creates a detached merge request pipeline and sets it as a head pipeline' do expect(merge_request).to be_persisted merge_request.reload expect(merge_request.merge_request_pipelines.count).to eq(1) - expect(merge_request.actual_head_pipeline).to be_merge_request_event + expect(merge_request.actual_head_pipeline).to be_detached_merge_request_pipeline + end + + context 'when merge request is submitted from forked project' do + let(:target_project) { fork_project(project, nil, repository: true) } + + let(:opts) do + { + title: 'Awesome merge_request', + source_branch: 'feature', + target_branch: 'master', + target_project_id: target_project.id + } + end + + before do + target_project.add_developer(assignee) + target_project.add_maintainer(user) + end + + it 'create legacy detached merge request pipeline for fork merge request' do + expect(merge_request.actual_head_pipeline) + .to be_legacy_detached_merge_request_pipeline + end + end + + context 'when ci_use_merge_request_ref feature flag is false' do + before do + stub_feature_flags(ci_use_merge_request_ref: false) + end + + it 'create legacy detached merge request pipeline for non-fork merge request' do + expect(merge_request.actual_head_pipeline) + .to be_legacy_detached_merge_request_pipeline + end end context 'when there are no commits between source branch and target branch' do @@ -207,7 +241,7 @@ describe MergeRequests::CreateService do } end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(merge_request).to be_persisted merge_request.reload @@ -225,7 +259,7 @@ describe MergeRequests::CreateService do merge_request end - it 'sets the latest merge request pipeline as the head pipeline' do + it 'sets the latest detached merge request pipeline as the head pipeline' do expect(merge_request.actual_head_pipeline).to be_merge_request_event end end @@ -235,7 +269,7 @@ describe MergeRequests::CreateService do stub_feature_flags(ci_merge_request_pipeline: false) end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(merge_request).to be_persisted merge_request.reload @@ -254,7 +288,7 @@ describe MergeRequests::CreateService do } end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(merge_request).to be_persisted merge_request.reload diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 6c8ff163692..25cbac6d7ee 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -141,7 +141,7 @@ describe MergeRequests::RefreshService do end end - describe 'Merge request pipelines' do + describe 'Pipelines for merge requests' do before do stub_ci_pipeline_yaml_file(YAML.dump(config)) end @@ -159,7 +159,7 @@ describe MergeRequests::RefreshService do } end - it 'create merge request pipeline with commits' do + it 'create detached merge request pipeline with commits' do expect { subject } .to change { @merge_request.merge_request_pipelines.count }.by(1) .and change { @fork_merge_request.merge_request_pipelines.count }.by(1) @@ -170,7 +170,34 @@ describe MergeRequests::RefreshService do expect(@another_merge_request.has_commits?).to be_falsy end - context "when branch pipeline was created before a merge request pipline has been created" do + it 'create detached merge request pipeline for non-fork merge request' do + subject + + expect(@merge_request.merge_request_pipelines.first) + .to be_detached_merge_request_pipeline + end + + it 'create legacy detached merge request pipeline for fork merge request' do + subject + + expect(@fork_merge_request.merge_request_pipelines.first) + .to be_legacy_detached_merge_request_pipeline + end + + context 'when ci_use_merge_request_ref feature flag is false' do + before do + stub_feature_flags(ci_use_merge_request_ref: false) + end + + it 'create legacy detached merge request pipeline for non-fork merge request' do + subject + + expect(@merge_request.merge_request_pipelines.first) + .to be_legacy_detached_merge_request_pipeline + end + end + + context "when branch pipeline was created before a detaced merge request pipeline has been created" do before do create(:ci_pipeline, project: @merge_request.source_project, sha: @merge_request.diff_head_sha, @@ -180,7 +207,7 @@ describe MergeRequests::RefreshService do subject end - it 'sets the latest merge request pipeline as a head pipeline' do + it 'sets the latest detached merge request pipeline as a head pipeline' do @merge_request.reload expect(@merge_request.actual_head_pipeline).to be_merge_request_event end @@ -193,7 +220,7 @@ describe MergeRequests::RefreshService do end context "when MergeRequestUpdateWorker is retried by an exception" do - it 'does not re-create a duplicate merge request pipeline' do + it 'does not re-create a duplicate detached merge request pipeline' do expect do service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') end.to change { @merge_request.merge_request_pipelines.count }.by(1) @@ -209,7 +236,7 @@ describe MergeRequests::RefreshService do stub_feature_flags(ci_merge_request_pipeline: false) end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect { subject } .not_to change { @merge_request.merge_request_pipelines.count } end @@ -226,7 +253,7 @@ describe MergeRequests::RefreshService do } end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect { subject } .not_to change { @merge_request.merge_request_pipelines.count } end -- cgit v1.2.1 From f4b8ec7ea1ecc517bd4a15e56cce6f7cd9eeb70c Mon Sep 17 00:00:00 2001 From: Tristan Read Date: Fri, 29 Mar 2019 08:33:10 +0000 Subject: Add requested resources to cluster health - ce port --- spec/javascripts/monitoring/charts/area_spec.js | 4 ++++ spec/javascripts/monitoring/mock_data.js | 5 +++++ 2 files changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index fb49290be19..549a7935c0f 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -203,6 +203,10 @@ describe('Area component', () => { .length, ).toBe(data.length); }); + + it('formats line width correctly', () => { + expect(chartData[0].lineStyle.width).toBe(2); + }); }); describe('scatterSeries', () => { diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index ffc7148fde2..6d4ef960c1a 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -330,6 +330,11 @@ export const metricsGroupsAPIResponse = { weight: 1, queries: [ { + appearance: { + line: { + width: 2, + }, + }, query_range: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100', label: 'Core Usage', -- cgit v1.2.1 From 0b68b17c3bf5d93fe942eed2ef0d16b6824f5499 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 25 Mar 2019 21:15:25 +0100 Subject: Fail for unhandled Promise rejections in Jest --- spec/frontend/environment.js | 19 +++++++++++++++++++ spec/frontend/test_setup.js | 2 ++ 2 files changed, 21 insertions(+) (limited to 'spec') diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index 1067a53906a..34df8019a2e 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -27,6 +27,25 @@ class CustomEnvironment extends JSDOMEnvironment { this.global.gon = { ee: testEnvironmentOptions.IS_EE, }; + + this.rejectedPromises = []; + + this.global.promiseRejectionHandler = error => { + this.rejectedPromises.push(error); + }; + } + + async teardown() { + await new Promise(setImmediate); + + if (this.rejectedPromises.length > 0) { + throw new ErrorWithStack( + `Unhandled Promise rejections: ${this.rejectedPromises.join(', ')}`, + this.teardown, + ); + } + + await super.teardown(); } } diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 006fc60ef57..00d7873ab2b 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -3,6 +3,8 @@ import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; import { initializeTestTimeout } from './helpers/timeout'; +process.on('unhandledRejection', global.promiseRejectionHandler); + // wait for pending setTimeout()s afterEach(() => { jest.runAllTimers(); -- cgit v1.2.1 From 0e9c092d003fb9febcbaf9fd33ddbbb40ed3191d Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 25 Mar 2019 21:32:48 +0100 Subject: Fail correct test for Promise --- spec/frontend/test_setup.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 00d7873ab2b..d13e8a1908c 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -5,10 +5,13 @@ import { initializeTestTimeout } from './helpers/timeout'; process.on('unhandledRejection', global.promiseRejectionHandler); -// wait for pending setTimeout()s -afterEach(() => { - jest.runAllTimers(); -}); +afterEach(() => + // give Promises a bit more time so they fail the right test + new Promise(setImmediate).then(() => { + // wait for pending setTimeout()s + jest.runAllTimers(); + }), +); initializeTestTimeout(300); -- cgit v1.2.1 From 4b9ff4d2fe2eb22e3abed32f56aef6b24d40aa31 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 13 Mar 2019 12:45:43 +0200 Subject: Extend quick actions dsl * Extend quick actions dsl to allow specifying model types to which given quick action applies * Rename issuable attribute to noteable in interpret_service.rb * Refactor and reorder quick action specs * Create a shared example spec for each quick action to be used for issue, merge request and commit specs. https://gitlab.com/gitlab-org/gitlab-ce/issues/50199 --- .../issues/user_uses_quick_actions_spec.rb | 97 +++-- .../merge_request/user_uses_quick_actions_spec.rb | 57 ++- .../quick_actions/command_definition_spec.rb | 30 ++ spec/lib/gitlab/quick_actions/dsl_spec.rb | 26 +- .../issuable_quick_actions_shared_examples.rb | 389 --------------------- .../commit/tag_quick_action_shared_examples.rb | 4 + .../assign_quick_action_shared_examples.rb | 110 ++++++ .../issuable/award_quick_action_shared_examples.rb | 63 ++++ .../issuable/close_quick_action_shared_examples.rb | 89 +++++ .../copy_metadata_quick_action_shared_examples.rb | 88 +++++ .../issuable/done_quick_action_shared_examples.rb | 103 ++++++ .../estimate_quick_action_shared_examples.rb | 81 +++++ .../issuable/label_quick_action_shared_examples.rb | 83 +++++ .../issuable/lock_quick_action_shared_examples.rb | 84 +++++ .../milestone_quick_action_shared_examples.rb | 83 +++++ .../relabel_quick_action_shared_examples.rb | 95 +++++ ...remove_estimate_quick_action_shared_examples.rb | 82 +++++ ...emove_milestone_quick_action_shared_examples.rb | 86 +++++ ...move_time_spent_quick_action_shared_examples.rb | 82 +++++ .../reopen_quick_action_shared_examples.rb | 86 +++++ .../issuable/shrug_quick_action_shared_examples.rb | 60 ++++ .../issuable/spend_quick_action_shared_examples.rb | 81 +++++ .../subscribe_quick_action_shared_examples.rb | 84 +++++ .../tableflip_quick_action_shared_examples.rb | 60 ++++ .../time_tracking_quick_action_shared_examples.rb | 98 ++++++ .../issuable/title_quick_action_shared_examples.rb | 85 +++++ .../issuable/todo_quick_action_shared_examples.rb | 92 +++++ .../unassign_quick_action_shared_examples.rb | 120 +++++++ .../unlabel_quick_action_shared_examples.rb | 102 ++++++ .../unlock_quick_action_shared_examples.rb | 87 +++++ .../unsubscribe_quick_action_shared_examples.rb | 86 +++++ .../board_move_quick_action_shared_examples.rb | 4 + .../confidential_quick_action_shared_examples.rb | 4 + ...e_merge_request_quick_action_shared_examples.rb | 4 + .../issue/due_quick_action_shared_examples.rb | 25 ++ .../duplicate_quick_action_shared_examples.rb | 4 + .../issue/move_quick_action_shared_examples.rb | 4 + ...remove_due_date_quick_action_shared_examples.rb | 25 ++ .../merge_quick_action_shared_examples.rb | 4 + .../target_branch_quick_action_shared_examples.rb | 4 + .../wip_quick_action_shared_examples.rb | 4 + 41 files changed, 2396 insertions(+), 459 deletions(-) delete mode 100644 spec/support/features/issuable_quick_actions_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb create mode 100644 spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb (limited to 'spec') diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index b5e7c3954e2..362f8a468ec 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -3,8 +3,41 @@ require 'rails_helper' describe 'Issues > User uses quick actions', :js do include Spec::Support::Helpers::Features::NotesHelpers - it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do + context "issuable common quick actions" do + let(:new_url_opts) { {} } + let(:maintainer) { create(:user) } + let(:project) { create(:project, :public) } + let!(:label_bug) { create(:label, project: project, title: 'bug') } + let!(:label_feature) { create(:label, project: project, title: 'feature') } + let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } let(:issuable) { create(:issue, project: project) } + let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])} + + it_behaves_like 'assign quick action', :issue + it_behaves_like 'unassign quick action', :issue + it_behaves_like 'close quick action', :issue + it_behaves_like 'reopen quick action', :issue + it_behaves_like 'title quick action', :issue + it_behaves_like 'todo quick action', :issue + it_behaves_like 'done quick action', :issue + it_behaves_like 'subscribe quick action', :issue + it_behaves_like 'unsubscribe quick action', :issue + it_behaves_like 'lock quick action', :issue + it_behaves_like 'unlock quick action', :issue + it_behaves_like 'milestone quick action', :issue + it_behaves_like 'remove_milestone quick action', :issue + it_behaves_like 'label quick action', :issue + it_behaves_like 'unlabel quick action', :issue + it_behaves_like 'relabel quick action', :issue + it_behaves_like 'award quick action', :issue + it_behaves_like 'estimate quick action', :issue + it_behaves_like 'remove_estimate quick action', :issue + it_behaves_like 'spend quick action', :issue + it_behaves_like 'remove_time_spent quick action', :issue + it_behaves_like 'shrug quick action', :issue + it_behaves_like 'tableflip quick action', :issue + it_behaves_like 'copy_metadata quick action', :issue + it_behaves_like 'issuable time tracker', :issue end describe 'issue-only commands' do @@ -15,37 +48,17 @@ describe 'Issues > User uses quick actions', :js do project.add_maintainer(user) sign_in(user) visit project_issue_path(project, issue) + wait_for_all_requests end after do wait_for_requests end - describe 'time tracking' do - let(:issue) { create(:issue, project: project) } - - before do - visit project_issue_path(project, issue) - end - - it_behaves_like 'issuable time tracker' - end - describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } - context 'when the current user can update the due date' do - it 'does not create a note, and sets the due date accordingly' do - add_note("/due 2016-08-28") - - expect(page).not_to have_content '/due 2016-08-28' - expect(page).to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to eq Date.new(2016, 8, 28) - end - end + it_behaves_like 'due quick action available and date can be added' context 'when the current user cannot update the due date' do let(:guest) { create(:user) } @@ -56,35 +69,14 @@ describe 'Issues > User uses quick actions', :js do visit project_issue_path(project, issue) end - it 'does not create a note, and sets the due date accordingly' do - add_note("/due 2016-08-28") - - expect(page).not_to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to be_nil - end + it_behaves_like 'due quick action not available' end end describe 'removing a due date from note' do let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } - context 'when the current user can update the due date' do - it 'does not create a note, and removes the due date accordingly' do - expect(issue.due_date).to eq Date.new(2016, 8, 28) - - add_note("/remove_due_date") - - expect(page).not_to have_content '/remove_due_date' - expect(page).to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to be_nil - end - end + it_behaves_like 'remove_due_date action available and due date can be removed' context 'when the current user cannot update the due date' do let(:guest) { create(:user) } @@ -95,15 +87,7 @@ describe 'Issues > User uses quick actions', :js do visit project_issue_path(project, issue) end - it 'does not create a note, and sets the due date accordingly' do - add_note("/remove_due_date") - - expect(page).not_to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to eq Date.new(2016, 8, 28) - end + it_behaves_like 'remove_due_date action not available' end end @@ -200,6 +184,7 @@ describe 'Issues > User uses quick actions', :js do gitlab_sign_out sign_in(user) visit project_issue_path(project, issue) + wait_for_requests end it 'moves the issue' do @@ -221,6 +206,7 @@ describe 'Issues > User uses quick actions', :js do gitlab_sign_out sign_in(user) visit project_issue_path(project, issue) + wait_for_requests end it 'does not move the issue' do @@ -238,6 +224,7 @@ describe 'Issues > User uses quick actions', :js do gitlab_sign_out sign_in(user) visit project_issue_path(project, issue) + wait_for_requests end it 'does not move the issue' do diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index b81478a481f..a2b5859bd1e 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -9,9 +9,41 @@ describe 'Merge request > User uses quick actions', :js do let(:merge_request) { create(:merge_request, source_project: project) } let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } - it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do + context "issuable common quick actions" do + let!(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } + let(:maintainer) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let!(:label_bug) { create(:label, project: project, title: 'bug') } + let!(:label_feature) { create(:label, project: project, title: 'feature') } + let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } let(:issuable) { create(:merge_request, source_project: project) } - let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } + let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])} + + it_behaves_like 'assign quick action', :merge_request + it_behaves_like 'unassign quick action', :merge_request + it_behaves_like 'close quick action', :merge_request + it_behaves_like 'reopen quick action', :merge_request + it_behaves_like 'title quick action', :merge_request + it_behaves_like 'todo quick action', :merge_request + it_behaves_like 'done quick action', :merge_request + it_behaves_like 'subscribe quick action', :merge_request + it_behaves_like 'unsubscribe quick action', :merge_request + it_behaves_like 'lock quick action', :merge_request + it_behaves_like 'unlock quick action', :merge_request + it_behaves_like 'milestone quick action', :merge_request + it_behaves_like 'remove_milestone quick action', :merge_request + it_behaves_like 'label quick action', :merge_request + it_behaves_like 'unlabel quick action', :merge_request + it_behaves_like 'relabel quick action', :merge_request + it_behaves_like 'award quick action', :merge_request + it_behaves_like 'estimate quick action', :merge_request + it_behaves_like 'remove_estimate quick action', :merge_request + it_behaves_like 'spend quick action', :merge_request + it_behaves_like 'remove_time_spent quick action', :merge_request + it_behaves_like 'shrug quick action', :merge_request + it_behaves_like 'tableflip quick action', :merge_request + it_behaves_like 'copy_metadata quick action', :merge_request + it_behaves_like 'issuable time tracker', :merge_request end describe 'merge-request-only commands' do @@ -24,20 +56,12 @@ describe 'Merge request > User uses quick actions', :js do project.add_maintainer(user) end - describe 'time tracking' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it_behaves_like 'issuable time tracker' - end - describe 'toggling the WIP prefix in the title from note' do context 'when the current user can toggle the WIP prefix' do before do sign_in(user) visit project_merge_request_path(project, merge_request) + wait_for_requests end it 'adds the WIP: prefix to the title' do @@ -135,11 +159,16 @@ describe 'Merge request > User uses quick actions', :js do visit project_merge_request_path(project, merge_request) end - it 'does not recognize the command nor create a note' do - add_note('/due 2016-08-28') + it_behaves_like 'due quick action not available' + end - expect(page).not_to have_content '/due 2016-08-28' + describe 'removing a due date from note' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) end + + it_behaves_like 'remove_due_date action not available' end describe '/target_branch command in merge request' do diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb index 136cfb5bcc5..b6e0adbc1c2 100644 --- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb +++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb @@ -69,6 +69,36 @@ describe Gitlab::QuickActions::CommandDefinition do expect(subject.available?(opts)).to be true end end + + context "when the command has types" do + before do + subject.types = [Issue, Commit] + end + + context "when the command target type is allowed" do + it "returns true" do + opts[:quick_action_target] = Issue.new + expect(subject.available?(opts)).to be true + end + end + + context "when the command target type is not allowed" do + it "returns true" do + opts[:quick_action_target] = MergeRequest.new + expect(subject.available?(opts)).to be false + end + end + end + + context "when the command has no types" do + it "any target type is allowed" do + opts[:quick_action_target] = Issue.new + expect(subject.available?(opts)).to be true + + opts[:quick_action_target] = MergeRequest.new + expect(subject.available?(opts)).to be true + end + end end describe "#execute" do diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb index fd4df8694ba..185adab1ff6 100644 --- a/spec/lib/gitlab/quick_actions/dsl_spec.rb +++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb @@ -48,13 +48,19 @@ describe Gitlab::QuickActions::Dsl do substitution :something do |text| "#{text} Some complicated thing you want in here" end + + desc 'A command with types' + types Issue, Commit + command :has_types do + "Has Issue and Commit types" + end end end describe '.command_definitions' do it 'returns an array with commands definitions' do no_args_def, explanation_with_aliases_def, dynamic_description_def, - cc_def, cond_action_def, with_params_parsing_def, substitution_def = + cc_def, cond_action_def, with_params_parsing_def, substitution_def, has_types = DummyClass.command_definitions expect(no_args_def.name).to eq(:no_args) @@ -63,6 +69,7 @@ describe Gitlab::QuickActions::Dsl do expect(no_args_def.explanation).to eq('') expect(no_args_def.params).to eq([]) expect(no_args_def.condition_block).to be_nil + expect(no_args_def.types).to eq([]) expect(no_args_def.action_block).to be_a_kind_of(Proc) expect(no_args_def.parse_params_block).to be_nil expect(no_args_def.warning).to eq('') @@ -73,6 +80,7 @@ describe Gitlab::QuickActions::Dsl do expect(explanation_with_aliases_def.explanation).to eq('Static explanation') expect(explanation_with_aliases_def.params).to eq(['The first argument']) expect(explanation_with_aliases_def.condition_block).to be_nil + expect(explanation_with_aliases_def.types).to eq([]) expect(explanation_with_aliases_def.action_block).to be_a_kind_of(Proc) expect(explanation_with_aliases_def.parse_params_block).to be_nil expect(explanation_with_aliases_def.warning).to eq('Possible problem!') @@ -83,6 +91,7 @@ describe Gitlab::QuickActions::Dsl do expect(dynamic_description_def.explanation).to eq('') expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument']) expect(dynamic_description_def.condition_block).to be_nil + expect(dynamic_description_def.types).to eq([]) expect(dynamic_description_def.action_block).to be_a_kind_of(Proc) expect(dynamic_description_def.parse_params_block).to be_nil expect(dynamic_description_def.warning).to eq('') @@ -93,6 +102,7 @@ describe Gitlab::QuickActions::Dsl do expect(cc_def.explanation).to eq('') expect(cc_def.params).to eq([]) expect(cc_def.condition_block).to be_nil + expect(cc_def.types).to eq([]) expect(cc_def.action_block).to be_nil expect(cc_def.parse_params_block).to be_nil expect(cc_def.warning).to eq('') @@ -103,6 +113,7 @@ describe Gitlab::QuickActions::Dsl do expect(cond_action_def.explanation).to be_a_kind_of(Proc) expect(cond_action_def.params).to eq([]) expect(cond_action_def.condition_block).to be_a_kind_of(Proc) + expect(cond_action_def.types).to eq([]) expect(cond_action_def.action_block).to be_a_kind_of(Proc) expect(cond_action_def.parse_params_block).to be_nil expect(cond_action_def.warning).to eq('') @@ -113,6 +124,7 @@ describe Gitlab::QuickActions::Dsl do expect(with_params_parsing_def.explanation).to eq('') expect(with_params_parsing_def.params).to eq([]) expect(with_params_parsing_def.condition_block).to be_nil + expect(with_params_parsing_def.types).to eq([]) expect(with_params_parsing_def.action_block).to be_a_kind_of(Proc) expect(with_params_parsing_def.parse_params_block).to be_a_kind_of(Proc) expect(with_params_parsing_def.warning).to eq('') @@ -123,9 +135,21 @@ describe Gitlab::QuickActions::Dsl do expect(substitution_def.explanation).to eq('') expect(substitution_def.params).to eq(['']) expect(substitution_def.condition_block).to be_nil + expect(substitution_def.types).to eq([]) expect(substitution_def.action_block.call('text')).to eq('text Some complicated thing you want in here') expect(substitution_def.parse_params_block).to be_nil expect(substitution_def.warning).to eq('') + + expect(has_types.name).to eq(:has_types) + expect(has_types.aliases).to eq([]) + expect(has_types.description).to eq('A command with types') + expect(has_types.explanation).to eq('') + expect(has_types.params).to eq([]) + expect(has_types.condition_block).to be_nil + expect(has_types.types).to eq([Issue, Commit]) + expect(has_types.action_block).to be_a_kind_of(Proc) + expect(has_types.parse_params_block).to be_nil + expect(has_types.warning).to eq('') end end end diff --git a/spec/support/features/issuable_quick_actions_shared_examples.rb b/spec/support/features/issuable_quick_actions_shared_examples.rb deleted file mode 100644 index 2a883ce1074..00000000000 --- a/spec/support/features/issuable_quick_actions_shared_examples.rb +++ /dev/null @@ -1,389 +0,0 @@ -# Specifications for behavior common to all objects with executable attributes. -# It takes a `issuable_type`, and expect an `issuable`. - -shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type| - include Spec::Support::Helpers::Features::NotesHelpers - - let(:maintainer) { create(:user) } - let(:project) do - case issuable_type - when :merge_request - create(:project, :public, :repository) - when :issue - create(:project, :public) - end - end - let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } - let!(:label_bug) { create(:label, project: project, title: 'bug') } - let!(:label_feature) { create(:label, project: project, title: 'feature') } - let(:new_url_opts) { {} } - - before do - project.add_maintainer(maintainer) - - gitlab_sign_in(maintainer) - end - - after do - # Ensure all outstanding Ajax requests are complete to avoid database deadlocks - wait_for_requests - end - - describe "new #{issuable_type}", :js do - context 'with commands in the description' do - it "creates the #{issuable_type} and interpret commands accordingly" do - case issuable_type - when :merge_request - visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts) - when :issue - visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts) - end - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\"" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq "bug description" - expect(issuable.labels).to eq [label_bug] - expect(issuable.milestone).to eq milestone - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - end - end - - describe "note on #{issuable_type}", :js do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - context 'with a note containing commands' do - it 'creates a note without the commands and interpret the commands accordingly' do - assignee = create(:user, username: 'bob') - add_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"") - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/assign @bob' - expect(page).not_to have_content '/label ~bug' - expect(page).not_to have_content '/milestone %"ASAP"' - - wait_for_requests - issuable.reload - note = issuable.notes.user.first - - expect(note.note).to eq "Awesome!" - expect(issuable.assignees).to eq [assignee] - expect(issuable.labels).to eq [label_bug] - expect(issuable.milestone).to eq milestone - end - - it 'removes the quick action from note and explains it in the preview' do - preview_note("Awesome!\n\n/close") - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/close' - issuable_name = issuable.is_a?(Issue) ? 'issue' : 'merge request' - expect(page).to have_content "Closes this #{issuable_name}." - end - end - - context 'with a note containing only commands' do - it 'does not create a note but interpret the commands accordingly' do - assignee = create(:user, username: 'bob') - add_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"") - - expect(page).not_to have_content '/assign @bob' - expect(page).not_to have_content '/label ~bug' - expect(page).not_to have_content '/milestone %"ASAP"' - expect(page).to have_content 'Commands applied' - - issuable.reload - - expect(issuable.notes.user).to be_empty - expect(issuable.assignees).to eq [assignee] - expect(issuable.labels).to eq [label_bug] - expect(issuable.milestone).to eq milestone - end - end - - context "with a note closing the #{issuable_type}" do - before do - expect(issuable).to be_open - end - - context "when current user can close #{issuable_type}" do - it "closes the #{issuable_type}" do - add_note("/close") - - expect(page).not_to have_content '/close' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).to be_closed - end - end - - context "when current user cannot close #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not close the #{issuable_type}" do - add_note("/close") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).to be_open - end - end - end - - context "with a note reopening the #{issuable_type}" do - before do - issuable.close - expect(issuable).to be_closed - end - - context "when current user can reopen #{issuable_type}" do - it "reopens the #{issuable_type}" do - add_note("/reopen") - - expect(page).not_to have_content '/reopen' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).to be_open - end - end - - context "when current user cannot reopen #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not reopen the #{issuable_type}" do - add_note("/reopen") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).to be_closed - end - end - end - - context "with a note changing the #{issuable_type}'s title" do - context "when current user can change title of #{issuable_type}" do - it "reopens the #{issuable_type}" do - add_note("/title Awesome new title") - - expect(page).not_to have_content '/title' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload.title).to eq 'Awesome new title' - end - end - - context "when current user cannot change title of #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not change the #{issuable_type} title" do - add_note("/title Awesome new title") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable.reload.title).not_to eq 'Awesome new title' - end - end - end - - context "with a note marking the #{issuable_type} as todo" do - it "creates a new todo for the #{issuable_type}" do - add_note("/todo") - - expect(page).not_to have_content '/todo' - expect(page).to have_content 'Commands applied' - - todos = TodosFinder.new(maintainer).execute - todo = todos.first - - expect(todos.size).to eq 1 - expect(todo).to be_pending - expect(todo.target).to eq issuable - expect(todo.author).to eq maintainer - expect(todo.user).to eq maintainer - end - end - - context "with a note marking the #{issuable_type} as done" do - before do - TodoService.new.mark_todo(issuable, maintainer) - end - - it "creates a new todo for the #{issuable_type}" do - todos = TodosFinder.new(maintainer).execute - todo = todos.first - - expect(todos.size).to eq 1 - expect(todos.first).to be_pending - expect(todo.target).to eq issuable - expect(todo.author).to eq maintainer - expect(todo.user).to eq maintainer - - add_note("/done") - - expect(page).not_to have_content '/done' - expect(page).to have_content 'Commands applied' - - expect(todo.reload).to be_done - end - end - - context "with a note subscribing to the #{issuable_type}" do - it "creates a new todo for the #{issuable_type}" do - expect(issuable.subscribed?(maintainer, project)).to be_falsy - - add_note("/subscribe") - - expect(page).not_to have_content '/subscribe' - expect(page).to have_content 'Commands applied' - - expect(issuable.subscribed?(maintainer, project)).to be_truthy - end - end - - context "with a note unsubscribing to the #{issuable_type} as done" do - before do - issuable.subscribe(maintainer, project) - end - - it "creates a new todo for the #{issuable_type}" do - expect(issuable.subscribed?(maintainer, project)).to be_truthy - - add_note("/unsubscribe") - - expect(page).not_to have_content '/unsubscribe' - expect(page).to have_content 'Commands applied' - - expect(issuable.subscribed?(maintainer, project)).to be_falsy - end - end - - context "with a note assigning the #{issuable_type} to the current user" do - it "assigns the #{issuable_type} to the current user" do - add_note("/assign me") - - expect(page).not_to have_content '/assign me' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload.assignees).to eq [maintainer] - end - end - - context "with a note locking the #{issuable_type} discussion" do - before do - issuable.update(discussion_locked: false) - expect(issuable).not_to be_discussion_locked - end - - context "when current user can lock #{issuable_type} discussion" do - it "locks the #{issuable_type} discussion" do - add_note("/lock") - - expect(page).not_to have_content '/lock' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).to be_discussion_locked - end - end - - context "when current user cannot lock #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not lock the #{issuable_type} discussion" do - add_note("/lock") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).not_to be_discussion_locked - end - end - end - - context "with a note unlocking the #{issuable_type} discussion" do - before do - issuable.update(discussion_locked: true) - expect(issuable).to be_discussion_locked - end - - context "when current user can unlock #{issuable_type} discussion" do - it "unlocks the #{issuable_type} discussion" do - add_note("/unlock") - - expect(page).not_to have_content '/unlock' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).not_to be_discussion_locked - end - end - - context "when current user cannot unlock #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not unlock the #{issuable_type} discussion" do - add_note("/unlock") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).to be_discussion_locked - end - end - end - end - - describe "preview of note on #{issuable_type}", :js do - it 'removes quick actions from note and explains them' do - create(:user, username: 'bob') - - visit public_send("project_#{issuable_type}_path", project, issuable) - - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Awesome!\n/assign @bob " - click_on 'Preview' - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/assign @bob' - expect(page).to have_content 'Assigns @bob.' - end - end - end -end diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb new file mode 100644 index 00000000000..4604d867507 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'tag quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb new file mode 100644 index 00000000000..d97da6be192 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +shared_examples 'assign quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets assign quick action accordingly" do + assignee = create(:user, username: 'bob') + + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/assign @bob" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [assignee] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + + it "creates the #{issuable_type} and interprets assign quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/assign me" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [maintainer] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the assign quick action accordingly' do + assignee = create(:user, username: 'bob') + add_note("Awesome!\n\n/assign @bob") + + expect(page).to have_content 'Awesome!' + expect(page).not_to have_content '/assign @bob' + + wait_for_requests + issuable.reload + note = issuable.notes.user.first + + expect(note.note).to eq 'Awesome!' + expect(issuable.assignees).to eq [assignee] + end + + it "assigns the #{issuable_type} to the current user" do + add_note("/assign me") + + expect(page).not_to have_content '/assign me' + expect(page).to have_content 'Commands applied' + + expect(issuable.reload.assignees).to eq [maintainer] + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains assign quick action to bob' do + create(:user, username: 'bob') + + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/assign @bob " + click_on 'Preview' + + expect(page).not_to have_content '/assign @bob' + expect(page).to have_content 'Awesome!' + expect(page).to have_content 'Assigns @bob.' + end + end + + it 'explains assign quick action to me' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/assign me" + click_on 'Preview' + + expect(page).not_to have_content '/assign me' + expect(page).to have_content 'Awesome!' + expect(page).to have_content "Assigns @#{maintainer.username}." + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb new file mode 100644 index 00000000000..74cbfa3f4b4 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +shared_examples 'award quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets award quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/award :100:" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.award_emoji).to eq [] + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.award_emoji).to eq [] + end + + it 'creates the note and interprets the award quick action accordingly' do + add_note("/award :100:") + + wait_for_requests + expect(page).not_to have_content '/award' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.award_emoji.last.name).to eq('100') + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains label quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/award :100:') + + expect(page).not_to have_content '/award' + expect(page).to have_selector "gl-emoji[data-name='100']" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb new file mode 100644 index 00000000000..e0d0b790a0e --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +shared_examples 'close quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets close quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/close" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + expect(issuable).to be_opened + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the close quick action accordingly' do + add_note("this is done, close\n\n/close") + + wait_for_requests + expect(page).not_to have_content '/close' + expect(page).to have_content 'this is done, close' + + issuable.reload + note = issuable.notes.user.first + + expect(note.note).to eq 'this is done, close' + expect(issuable).to be_closed + end + + context "when current user cannot close #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + end + + it "does not close the #{issuable_type}" do + add_note('/close') + + expect(page).not_to have_content 'Commands applied' + expect(issuable).to be_open + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains close quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "this is done, close\n/close" + click_on 'Preview' + + expect(page).not_to have_content '/close' + expect(page).to have_content 'this is done, close' + expect(page).to have_content "Closes this #{issuable_type.to_s.humanize.downcase}." + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb new file mode 100644 index 00000000000..1e1e3c7bc95 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +shared_examples 'copy_metadata quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets copy_metadata quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/copy_metadata #{source_issuable.to_reference(project)}" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).last + + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + issuable.reload + expect(issuable.description).to eq 'bug description' + expect(issuable.milestone).to eq milestone + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets copy_metadata quick action accordingly' do + add_note("/copy_metadata #{source_issuable.to_reference(project)}") + + wait_for_requests + expect(page).not_to have_content '/copy_metadata' + expect(page).to have_content 'Commands applied' + issuable.reload + expect(issuable.milestone).to eq milestone + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + + context "when current user cannot copy_metadata" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not copy_metadata' do + add_note("/copy_metadata #{source_issuable.to_reference(project)}") + + wait_for_requests + expect(page).not_to have_content '/copy_metadata' + expect(page).not_to have_content 'Commands applied' + issuable.reload + expect(issuable.milestone).not_to eq milestone + expect(issuable.labels).to eq [] + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains copy_metadata quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note("/copy_metadata #{source_issuable.to_reference(project)}") + + expect(page).not_to have_content '/copy_metadata' + expect(page).to have_content "Copy labels and milestone from #{source_issuable.to_reference(project)}." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb new file mode 100644 index 00000000000..8a72bbc13bf --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +shared_examples 'done quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets done quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/done" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + + todos = TodosFinder.new(maintainer).execute + expect(todos.size).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + TodoService.new.mark_todo(issuable, maintainer) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the done quick action accordingly' do + todos = TodosFinder.new(maintainer).execute + todo = todos.first + expect(todo.reload).to be_pending + + expect(todos.size).to eq 1 + expect(todo.target).to eq issuable + expect(todo.author).to eq maintainer + expect(todo.user).to eq maintainer + + add_note('/done') + + wait_for_requests + expect(page).not_to have_content '/done' + expect(page).to have_content 'Commands applied' + expect(todo.reload).to be_done + end + + context "when current user cannot mark #{issuable_type} todo as done" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not set the #{issuable_type} todo as done" do + todos = TodosFinder.new(maintainer).execute + todo = todos.first + expect(todo.reload).to be_pending + + expect(todos.size).to eq 1 + expect(todo.target).to eq issuable + expect(todo.author).to eq maintainer + expect(todo.user).to eq maintainer + + add_note('/done') + + expect(page).not_to have_content 'Commands applied' + expect(todo.reload).to be_pending + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains done quick action' do + TodoService.new.mark_todo(issuable, maintainer) + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/done') + + expect(page).not_to have_content '/done' + expect(page).to have_content "Marks todo as done." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb new file mode 100644 index 00000000000..648755d7e55 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +shared_examples 'estimate quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets estimate quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/estimate 1d 2h 3m" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.time_estimate).to eq 36180 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the estimate quick action accordingly' do + add_note("/estimate 1d 2h 3m") + + wait_for_requests + expect(page).not_to have_content '/estimate' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.time_estimate).to eq 36180 + end + + context "when current user cannot set estimate to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set estimate' do + add_note("/estimate ~bug ~feature") + + wait_for_requests + expect(page).not_to have_content '/estimate' + expect(issuable.reload.time_estimate).to eq 0 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains estimate quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/estimate 1d 2h 3m') + + expect(page).not_to have_content '/estimate' + expect(page).to have_content 'Sets time estimate to 1d 2h 3m.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb new file mode 100644 index 00000000000..9066e382b70 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +shared_examples 'label quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets label quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug ~feature" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.labels).to eq [] + end + + it 'creates the note and interprets the label quick action accordingly' do + add_note("/label ~bug ~feature") + + wait_for_requests + expect(page).not_to have_content '/label' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_bug, label_feature]) + end + + context "when current user cannot set label to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set label' do + add_note("/label ~bug ~feature") + + wait_for_requests + expect(page).not_to have_content '/label' + expect(issuable.labels).to eq [] + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains label quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/label ~bug ~feature') + + expect(page).not_to have_content '/label' + expect(page).to have_content 'Adds bug feature labels.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb new file mode 100644 index 00000000000..d3197f2a459 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +shared_examples 'lock quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets lock quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/lock" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable).not_to be_discussion_locked + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.update(discussion_locked: false) + expect(issuable).not_to be_discussion_locked + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the lock quick action accordingly' do + add_note('/lock') + + wait_for_requests + expect(page).not_to have_content '/lock' + expect(page).to have_content 'Commands applied' + expect(issuable.reload).to be_discussion_locked + end + + context "when current user cannot lock to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not lock the #{issuable_type}" do + add_note('/lock') + + wait_for_requests + expect(page).not_to have_content '/lock' + expect(issuable).not_to be_discussion_locked + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains lock quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/lock') + + expect(page).not_to have_content '/lock' + expect(page).to have_content "Locks the discussion" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb new file mode 100644 index 00000000000..7f16ce93b6a --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +shared_examples 'milestone quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets milestone quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/milestone %\"ASAP\"" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.milestone).to eq milestone + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.milestone).to be_nil + end + + it 'creates the note and interprets the milestone quick action accordingly' do + add_note("/milestone %\"ASAP\"") + + wait_for_requests + expect(page).not_to have_content '/milestone' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.milestone).to eq milestone + end + + context "when current user cannot set milestone to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set milestone' do + add_note('/milestone') + + wait_for_requests + expect(page).not_to have_content '/milestone' + expect(issuable.reload.milestone).to be_nil + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains milestone quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note("/milestone %\"ASAP\"") + + expect(page).not_to have_content '/milestone' + expect(page).to have_content 'Sets the milestone to %ASAP' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb new file mode 100644 index 00000000000..643ae77516a --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +shared_examples 'relabel quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets relabel quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /relabel ~feature" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.labels).to eq [label_bug, label_feature] + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update(labels: [label_bug]) + end + + it 'creates the note and interprets the relabel quick action accordingly' do + add_note('/relabel ~feature') + + wait_for_requests + expect(page).not_to have_content '/relabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_feature]) + end + + it 'creates the note and interprets the relabel quick action with empty param' do + add_note('/relabel') + + wait_for_requests + expect(page).not_to have_content '/relabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_bug]) + end + + context "when current user cannot relabel to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not relabel' do + add_note('/relabel ~feature') + + wait_for_requests + expect(page).not_to have_content '/relabel' + expect(issuable.labels).to match_array([label_bug]) + end + end + end + + context "preview of note on #{issuable_type}", :js do + before do + issuable.update(labels: [label_bug]) + visit public_send("project_#{issuable_type}_path", project, issuable) + end + + it 'explains relabel all quick action' do + preview_note('/relabel ~feature') + + expect(page).not_to have_content '/relabel' + expect(page).to have_content 'Replaces all labels with feature label.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb new file mode 100644 index 00000000000..24f6f8d5bf4 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +shared_examples 'remove_estimate quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets estimate quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/remove_estimate" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.time_estimate).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update_attribute(:time_estimate, 36180) + end + + it 'creates the note and interprets the remove_estimate quick action accordingly' do + add_note("/remove_estimate") + + wait_for_requests + expect(page).not_to have_content '/remove_estimate' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.time_estimate).to eq 0 + end + + context "when current user cannot remove_estimate" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not remove_estimate' do + add_note('/remove_estimate') + + wait_for_requests + expect(page).not_to have_content '/remove_estimate' + expect(issuable.reload.time_estimate).to eq 36180 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains remove_estimate quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/remove_estimate') + + expect(page).not_to have_content '/remove_estimate' + expect(page).to have_content 'Removes time estimate.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb new file mode 100644 index 00000000000..edd92d5cdbc --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +shared_examples 'remove_milestone quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets remove_milestone quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/remove_milestone" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.milestone).to be_nil + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update(milestone: milestone) + expect(issuable.milestone).to eq(milestone) + end + + it 'creates the note and interprets the remove_milestone quick action accordingly' do + add_note("/remove_milestone") + + wait_for_requests + expect(page).not_to have_content '/remove_milestone' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.milestone).to be_nil + end + + context "when current user cannot remove milestone to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not remove milestone' do + add_note('/remove_milestone') + + wait_for_requests + expect(page).not_to have_content '/remove_milestone' + expect(issuable.reload.milestone).to eq(milestone) + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains remove_milestone quick action' do + issuable.update(milestone: milestone) + expect(issuable.milestone).to eq(milestone) + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note("/remove_milestone") + + expect(page).not_to have_content '/remove_milestone' + expect(page).to have_content 'Removes %ASAP milestone.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb new file mode 100644 index 00000000000..6d5894b2318 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +shared_examples 'remove_time_spent quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets remove_time_spent quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/remove_time_spent" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.total_time_spent).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.update!(spend_time: { duration: 36180, user_id: maintainer.id }) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the remove_time_spent quick action accordingly' do + add_note("/remove_time_spent") + + wait_for_requests + expect(page).not_to have_content '/remove_time_spent' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.total_time_spent).to eq 0 + end + + context "when current user cannot set remove_time_spent time" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set remove_time_spent time' do + add_note("/remove_time_spent") + + wait_for_requests + expect(page).not_to have_content '/remove_time_spent' + expect(issuable.reload.total_time_spent).to eq 36180 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains remove_time_spent quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/remove_time_spent') + + expect(page).not_to have_content '/remove_time_spent' + expect(page).to have_content 'Removes spent time.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb new file mode 100644 index 00000000000..af173e93bb5 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +shared_examples 'reopen quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets reopen quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/reopen" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.close + expect(issuable).to be_closed + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the reopen quick action accordingly' do + add_note('/reopen') + + wait_for_requests + expect(page).not_to have_content '/reopen' + expect(page).to have_content 'Commands applied' + + issuable.reload + expect(issuable).to be_opened + end + + context "when current user cannot reopen #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not reopen the #{issuable_type}" do + add_note('/reopen') + + expect(page).not_to have_content 'Commands applied' + expect(issuable).to be_closed + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains reopen quick action' do + issuable.close + expect(issuable).to be_closed + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/reopen') + + expect(page).not_to have_content '/reopen' + expect(page).to have_content "Reopens this #{issuable_type.to_s.humanize.downcase}." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb new file mode 100644 index 00000000000..0a526808585 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +shared_examples 'shrug quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets shrug quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/shrug oops" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq "bug description\noops ¯\\_(ツ)_/¯" + expect(page).to have_content 'bug 345' + expect(page).to have_content "bug description\noops ¯\\_(ツ)_/¯" + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets shrug quick action accordingly' do + add_note("/shrug oops") + + wait_for_requests + expect(page).not_to have_content '/shrug oops' + expect(page).to have_content "oops ¯\\_(ツ)_/¯" + expect(issuable.notes.last.note).to eq "oops ¯\\_(ツ)_/¯" + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains shrug quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/shrug oops') + + expect(page).not_to have_content '/shrug' + expect(page).to have_content "oops ¯\\_(ツ)_/¯" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb new file mode 100644 index 00000000000..97b4885eba0 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +shared_examples 'spend quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets spend quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/spend 1d 2h 3m" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.total_time_spent).to eq 36180 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the spend quick action accordingly' do + add_note("/spend 1d 2h 3m") + + wait_for_requests + expect(page).not_to have_content '/spend' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.total_time_spent).to eq 36180 + end + + context "when current user cannot set spend time" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set spend time' do + add_note("/spend 1s 2h 3m") + + wait_for_requests + expect(page).not_to have_content '/spend' + expect(issuable.reload.total_time_spent).to eq 0 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains spend quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/spend 1d 2h 3m') + + expect(page).not_to have_content '/spend' + expect(page).to have_content 'Adds 1d 2h 3m spent time.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb new file mode 100644 index 00000000000..15aefd511a5 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +shared_examples 'subscribe quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets subscribe quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/subscribe" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.subscribed?(maintainer, project)).to be_falsy + end + + it 'creates the note and interprets the subscribe quick action accordingly' do + add_note('/subscribe') + + wait_for_requests + expect(page).not_to have_content '/subscribe' + expect(page).to have_content 'Commands applied' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + + context "when current user cannot subscribe to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not subscribe to the #{issuable_type}" do + add_note('/subscribe') + + wait_for_requests + expect(page).not_to have_content '/subscribe' + expect(page).to have_content 'Commands applied' + expect(issuable.subscribed?(maintainer, project)).to be_falsy + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains subscribe quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/subscribe') + + expect(page).not_to have_content '/subscribe' + expect(page).to have_content "Subscribes to this #{issuable_type.to_s.humanize.downcase}" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ef831e39872 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +shared_examples 'tableflip quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets tableflip quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/tableflip oops" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq "bug description\noops (╯°□°)╯︵ ┻━┻" + expect(page).to have_content 'bug 345' + expect(page).to have_content "bug description\noops (╯°□°)╯︵ ┻━┻" + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets tableflip quick action accordingly' do + add_note("/tableflip oops") + + wait_for_requests + expect(page).not_to have_content '/tableflip oops' + expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻" + expect(issuable.notes.last.note).to eq "oops (╯°□°)╯︵ ┻━┻" + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains tableflip quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/tableflip oops') + + expect(page).not_to have_content '/tableflip' + expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ed904c8d539 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +shared_examples 'issuable time tracker' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + after do + wait_for_requests + end + + it 'renders the sidebar component empty state' do + page.within '.time-tracking-no-tracking-pane' do + expect(page).to have_content 'No estimate or time spent' + end + end + + it 'updates the sidebar component when estimate is added' do + submit_time('/estimate 3w 1d 1h') + + wait_for_requests + page.within '.time-tracking-estimate-only-pane' do + expect(page).to have_content '3w 1d 1h' + end + end + + it 'updates the sidebar component when spent is added' do + submit_time('/spend 3w 1d 1h') + + wait_for_requests + page.within '.time-tracking-spend-only-pane' do + expect(page).to have_content '3w 1d 1h' + end + end + + it 'shows the comparison when estimate and spent are added' do + submit_time('/estimate 3w 1d 1h') + submit_time('/spend 3w 1d 1h') + + wait_for_requests + page.within '.time-tracking-comparison-pane' do + expect(page).to have_content '3w 1d 1h' + end + end + + it 'updates the sidebar component when estimate is removed' do + submit_time('/estimate 3w 1d 1h') + submit_time('/remove_estimate') + + page.within '.time-tracking-component-wrap' do + expect(page).to have_content 'No estimate or time spent' + end + end + + it 'updates the sidebar component when spent is removed' do + submit_time('/spend 3w 1d 1h') + submit_time('/remove_time_spent') + + page.within '.time-tracking-component-wrap' do + expect(page).to have_content 'No estimate or time spent' + end + end + + it 'shows the help state when icon is clicked' do + page.within '.time-tracking-component-wrap' do + find('.help-button').click + expect(page).to have_content 'Track time with quick actions' + expect(page).to have_content 'Learn more' + end + end + + it 'hides the help state when close icon is clicked' do + page.within '.time-tracking-component-wrap' do + find('.help-button').click + find('.close-help-button').click + + expect(page).not_to have_content 'Track time with quick actions' + expect(page).not_to have_content 'Learn more' + end + end + + it 'displays the correct help url' do + page.within '.time-tracking-component-wrap' do + find('.help-button').click + + expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md') + end + end +end + +def submit_time(quick_action) + fill_in 'note[note]', with: quick_action + find('.js-comment-submit-button').click + wait_for_requests +end diff --git a/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb new file mode 100644 index 00000000000..93a69093dde --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +shared_examples 'title quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets title quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/title new title" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(issuable.title).to eq 'bug 345' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the title quick action accordingly' do + add_note('/title New title') + + wait_for_requests + expect(page).not_to have_content '/title new title' + expect(page).to have_content 'Commands applied' + expect(page).to have_content 'New title' + + issuable.reload + expect(issuable.title).to eq 'New title' + end + + context "when current user cannot set title #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not set title to the #{issuable_type}" do + add_note('/title New title') + + expect(page).not_to have_content 'Commands applied' + expect(issuable.title).not_to eq 'New title' + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains title quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/title New title') + wait_for_requests + + expect(page).not_to have_content '/title New title' + expect(page).to have_content 'Changes the title to "New title".' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb new file mode 100644 index 00000000000..cccc28127ce --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +shared_examples 'todo quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets todo quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/todo" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + + todos = TodosFinder.new(maintainer).execute + expect(todos.size).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the todo quick action accordingly' do + add_note('/todo') + + wait_for_requests + expect(page).not_to have_content '/todo' + expect(page).to have_content 'Commands applied' + + todos = TodosFinder.new(maintainer).execute + todo = todos.first + + expect(todos.size).to eq 1 + expect(todo).to be_pending + expect(todo.target).to eq issuable + expect(todo.author).to eq maintainer + expect(todo.user).to eq maintainer + end + + context "when current user cannot add todo #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not add todo the #{issuable_type}" do + add_note('/todo') + + expect(page).not_to have_content 'Commands applied' + todos = TodosFinder.new(maintainer).execute + expect(todos.size).to eq 0 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains todo quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/todo') + + expect(page).not_to have_content '/todo' + expect(page).to have_content "Adds a todo." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb new file mode 100644 index 00000000000..0b1a52bc860 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +shared_examples 'unassign quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unassign quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unassign @bob" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + + it "creates the #{issuable_type} and interprets unassign quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unassign me" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the unassign quick action accordingly' do + assignee = create(:user, username: 'bob') + issuable.update(assignee_ids: [assignee.id]) + expect(issuable.assignees).to eq [assignee] + + add_note("Awesome!\n\n/unassign @bob") + + expect(page).to have_content 'Awesome!' + expect(page).not_to have_content '/unassign @bob' + + wait_for_requests + issuable.reload + note = issuable.notes.user.first + + expect(note.note).to eq 'Awesome!' + expect(issuable.assignees).to eq [] + end + + it "unassigns the #{issuable_type} from current user" do + issuable.update(assignee_ids: [maintainer.id]) + expect(issuable.reload.assignees).to eq [maintainer] + expect(issuable.assignees).to eq [maintainer] + + add_note("/unassign me") + + expect(page).not_to have_content '/unassign me' + expect(page).to have_content 'Commands applied' + + expect(issuable.reload.assignees).to eq [] + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains unassign quick action: from bob' do + assignee = create(:user, username: 'bob') + issuable.update(assignee_ids: [assignee.id]) + expect(issuable.assignees).to eq [assignee] + + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/unassign @bob " + click_on 'Preview' + + expect(page).not_to have_content '/unassign @bob' + expect(page).to have_content 'Awesome!' + expect(page).to have_content 'Removes assignee @bob.' + end + end + + it 'explains unassign quick action: from me' do + issuable.update(assignee_ids: [maintainer.id]) + expect(issuable.assignees).to eq [maintainer] + + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/unassign me" + click_on 'Preview' + + expect(page).not_to have_content '/unassign me' + expect(page).to have_content 'Awesome!' + expect(page).to have_content "Removes assignee @#{maintainer.username}." + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb new file mode 100644 index 00000000000..1a1ee05841f --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +shared_examples 'unlabel quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unlabel quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /unlabel" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.labels).to eq [label_bug] + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update(labels: [label_bug, label_feature]) + end + + it 'creates the note and interprets the unlabel all quick action accordingly' do + add_note("/unlabel") + + wait_for_requests + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to eq [] + end + + it 'creates the note and interprets the unlabel some quick action accordingly' do + add_note("/unlabel ~bug") + + wait_for_requests + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_feature]) + end + + context "when current user cannot unlabel to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not unlabel' do + add_note("/unlabel") + + wait_for_requests + expect(page).not_to have_content '/unlabel' + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + end + end + + context "preview of note on #{issuable_type}", :js do + before do + issuable.update(labels: [label_bug, label_feature]) + visit public_send("project_#{issuable_type}_path", project, issuable) + end + + it 'explains unlabel all quick action' do + preview_note('/unlabel') + + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Removes all labels.' + end + + it 'explains unlabel some quick action' do + preview_note('/unlabel ~bug') + + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Removes bug label.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb new file mode 100644 index 00000000000..998ff99b32e --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +shared_examples 'unlock quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unlock quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unlock" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable).not_to be_discussion_locked + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.update(discussion_locked: true) + expect(issuable).to be_discussion_locked + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the unlock quick action accordingly' do + add_note('/unlock') + + wait_for_requests + expect(page).not_to have_content '/unlock' + expect(page).to have_content 'Commands applied' + expect(issuable.reload).not_to be_discussion_locked + end + + context "when current user cannot unlock to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not lock the #{issuable_type}" do + add_note('/unlock') + + wait_for_requests + expect(page).not_to have_content '/unlock' + expect(issuable).to be_discussion_locked + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains unlock quick action' do + issuable.update(discussion_locked: true) + expect(issuable).to be_discussion_locked + + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/unlock') + + expect(page).not_to have_content '/unlock' + expect(page).to have_content 'Unlocks the discussion' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb new file mode 100644 index 00000000000..bd92f133889 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +shared_examples 'unsubscribe quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unsubscribe quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unsubscribe" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.subscribe(maintainer, project) + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + + it 'creates the note and interprets the unsubscribe quick action accordingly' do + add_note('/unsubscribe') + + wait_for_requests + expect(page).not_to have_content '/unsubscribe' + expect(page).to have_content 'Commands applied' + expect(issuable.subscribed?(maintainer, project)).to be_falsey + end + + context "when current user cannot unsubscribe to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not unsubscribe to the #{issuable_type}" do + add_note('/unsubscribe') + + wait_for_requests + expect(page).not_to have_content '/unsubscribe' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains unsubscribe quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + issuable.subscribe(maintainer, project) + expect(issuable.subscribed?(maintainer, project)).to be_truthy + + preview_note('/unsubscribe') + + expect(page).not_to have_content '/unsubscribe' + expect(page).to have_content "Unsubscribes from this #{issuable_type.to_s.humanize.downcase}." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb new file mode 100644 index 00000000000..6edd20bb024 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'board_move quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb new file mode 100644 index 00000000000..c68e5aee842 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'confidential quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb new file mode 100644 index 00000000000..5bfc3bb222f --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'create_merge_request quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb new file mode 100644 index 00000000000..db3ecccc339 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +shared_examples 'due quick action not available' do + it 'does not set the due date' do + add_note('/due 2016-08-28') + + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content '/due 2016-08-28' + end +end + +shared_examples 'due quick action available and date can be added' do + it 'sets the due date accordingly' do + add_note('/due 2016-08-28') + + expect(page).not_to have_content '/due 2016-08-28' + expect(page).to have_content 'Commands applied' + + visit project_issue_path(project, issue) + + page.within '.due_date' do + expect(page).to have_content 'Aug 28, 2016' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb new file mode 100644 index 00000000000..24576fe0021 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'duplicate quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb new file mode 100644 index 00000000000..953e67b0423 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'move quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb new file mode 100644 index 00000000000..5904164fcfc --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +shared_examples 'remove_due_date action not available' do + it 'does not remove the due date' do + add_note("/remove_due_date") + + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content '/remove_due_date' + end +end + +shared_examples 'remove_due_date action available and due date can be removed' do + it 'removes the due date accordingly' do + add_note('/remove_due_date') + + expect(page).not_to have_content '/remove_due_date' + expect(page).to have_content 'Commands applied' + + visit project_issue_path(project, issue) + + page.within '.due_date' do + expect(page).to have_content 'No due date' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb new file mode 100644 index 00000000000..31d88183f0d --- /dev/null +++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'merge quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ccb4a85325b --- /dev/null +++ b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'target_branch quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb new file mode 100644 index 00000000000..6abb12b41b2 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'wip quick action' do +end -- cgit v1.2.1 From c558d72b5bca44984c32b6f65f4b56332ee828f6 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Fri, 29 Mar 2019 14:53:40 +0000 Subject: Handle missing keys in sentry api response - Do not raise error when there are missing non-essential keys in sentry api response. - Add specs for to check for missing keys behavior. --- spec/lib/sentry/client_spec.rb | 85 ++++++++++++++++++---- .../project_error_tracking_setting_spec.rb | 28 ++++++- .../error_tracking/list_issues_service_spec.rb | 24 +++++- 3 files changed, 120 insertions(+), 17 deletions(-) (limited to 'spec') diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index 88e7e2e5ebb..3333f8307ae 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -65,7 +65,9 @@ describe Sentry::Client do let(:issue_status) { 'unresolved' } let(:limit) { 20 } - let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: issues_sample_response) } + let(:sentry_api_response) { issues_sample_response } + + let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: sentry_api_response) } subject { client.list_issues(issue_status: issue_status, limit: limit) } @@ -74,6 +76,14 @@ describe Sentry::Client do it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error it_behaves_like 'has correct length', 1 + shared_examples 'has correct external_url' do + context 'external_url' do + it 'is constructed correctly' do + expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') + end + end + end + context 'error object created from sentry response' do using RSpec::Parameterized::TableSyntax @@ -96,14 +106,10 @@ describe Sentry::Client do end with_them do - it { expect(subject[0].public_send(error_object)).to eq(issues_sample_response[0].dig(*sentry_response)) } + it { expect(subject[0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) } end - context 'external_url' do - it 'is constructed correctly' do - expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') - end - end + it_behaves_like 'has correct external_url' end context 'redirects' do @@ -135,12 +141,42 @@ describe Sentry::Client do expect(valid_req_stub).to have_been_requested end end + + context 'Older sentry versions where keys are not present' do + let(:sentry_api_response) do + issues_sample_response[0...1].map do |issue| + issue[:project].delete(:id) + issue + end + end + + it_behaves_like 'calls sentry api' + + it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'has correct length', 1 + + it_behaves_like 'has correct external_url' + end + + context 'essential keys missing in API response' do + let(:sentry_api_response) do + issues_sample_response[0...1].map do |issue| + issue.except(:id) + end + end + + it 'raises exception' do + expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') + end + end end describe '#list_projects' do let(:sentry_list_projects_url) { 'https://sentrytest.gitlab.com/api/0/projects/' } - let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: projects_sample_response) } + let(:sentry_api_response) { projects_sample_response } + + let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: sentry_api_response) } subject { client.list_projects } @@ -149,14 +185,31 @@ describe Sentry::Client do it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project it_behaves_like 'has correct length', 2 - context 'keys missing in API response' do - it 'raises exception' do - projects_sample_response[0].delete(:slug) + context 'essential keys missing in API response' do + let(:sentry_api_response) do + projects_sample_response[0...1].map do |project| + project.except(:slug) + end + end - stub_sentry_request(sentry_list_projects_url, body: projects_sample_response) + it 'raises exception' do + expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "slug"') + end + end - expect { subject }.to raise_error(Sentry::Client::SentryError, 'Sentry API response is missing keys. key not found: "slug"') + context 'optional keys missing in sentry response' do + let(:sentry_api_response) do + projects_sample_response[0...1].map do |project| + project[:organization].delete(:id) + project.delete(:id) + project.except(:status) + end end + + it_behaves_like 'calls sentry api' + + it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project + it_behaves_like 'has correct length', 1 end context 'error object created from sentry response' do @@ -173,7 +226,11 @@ describe Sentry::Client do end with_them do - it { expect(subject[0].public_send(sentry_project_object)).to eq(projects_sample_response[0].dig(*sentry_response)) } + it do + expect(subject[0].public_send(sentry_project_object)).to( + eq(sentry_api_response[0].dig(*sentry_response)) + ) + end end end diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index cbde13a2c7a..21e381d9fb7 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -167,7 +167,7 @@ describe ErrorTracking::ProjectErrorTrackingSetting do end end - context 'when sentry client raises exception' do + context 'when sentry client raises Sentry::Client::Error' do let(:sentry_client) { spy(:sentry_client) } before do @@ -179,7 +179,31 @@ describe ErrorTracking::ProjectErrorTrackingSetting do end it 'returns error' do - expect(result).to eq(error: 'error message') + expect(result).to eq( + error: 'error message', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE + ) + expect(subject).to have_received(:sentry_client) + expect(sentry_client).to have_received(:list_issues) + end + end + + context 'when sentry client raises Sentry::Client::MissingKeysError' do + let(:sentry_client) { spy(:sentry_client) } + + before do + synchronous_reactive_cache(subject) + + allow(subject).to receive(:sentry_client).and_return(sentry_client) + allow(sentry_client).to receive(:list_issues).with(opts) + .and_raise(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') + end + + it 'returns error' do + expect(result).to eq( + error: 'Sentry API response is missing keys. key not found: "id"', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS + ) expect(subject).to have_received(:sentry_client) expect(sentry_client).to have_received(:list_issues) end diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb index 9d4fc62f923..3a8f3069911 100644 --- a/spec/services/error_tracking/list_issues_service_spec.rb +++ b/spec/services/error_tracking/list_issues_service_spec.rb @@ -53,7 +53,10 @@ describe ErrorTracking::ListIssuesService do before do allow(error_tracking_setting) .to receive(:list_sentry_issues) - .and_return(error: 'Sentry response status code: 401') + .and_return( + error: 'Sentry response status code: 401', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE + ) end it 'returns the error' do @@ -64,6 +67,25 @@ describe ErrorTracking::ListIssuesService do ) end end + + context 'when list_sentry_issues returns error with http_status' do + before do + allow(error_tracking_setting) + .to receive(:list_sentry_issues) + .and_return( + error: 'Sentry API response is missing keys. key not found: "id"', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS + ) + end + + it 'returns the error with correct http_status' do + expect(result).to eq( + status: :error, + http_status: :internal_server_error, + message: 'Sentry API response is missing keys. key not found: "id"' + ) + end + end end context 'with unauthorized user' do -- cgit v1.2.1 From ab06d1eda2fa222b4de400d2b18eab611ffefa68 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Fri, 29 Mar 2019 18:23:18 +0000 Subject: Renames Cluster#managed? to provided_by_user? This will allow to user the term managed? on https://gitlab.com/gitlab-org/gitlab-ce/issues/56557. Managed? will be used to distinct clusters that are automatically managed by GitLab --- spec/features/clusters/cluster_detail_page_spec.rb | 80 ++++++++++++++++++++++ spec/models/clusters/cluster_spec.rb | 16 +++++ spec/models/clusters/platforms/kubernetes_spec.rb | 2 +- spec/presenters/clusters/cluster_presenter_spec.rb | 16 +++++ 4 files changed, 113 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index b9fc52d0dce..d2e46d15730 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -53,12 +53,80 @@ describe 'Clusterable > Show page' do end end + shared_examples 'editing a GCP cluster' do + before do + clusterable.add_maintainer(current_user) + visit cluster_path + end + + it 'is not able to edit the name, API url, CA certificate nor token' do + within('#js-cluster-details') do + cluster_name_field = find('.cluster-name') + api_url_field = find('#cluster_platform_kubernetes_attributes_api_url') + ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert') + token_field = find('#cluster_platform_kubernetes_attributes_token') + + expect(cluster_name_field).to be_readonly + expect(api_url_field).to be_readonly + expect(ca_certificate_field).to be_readonly + expect(token_field).to be_readonly + end + end + + it 'displays GKE information' do + within('#advanced-settings-section') do + expect(page).to have_content('Google Kubernetes Engine') + expect(page).to have_content('Manage your Kubernetes cluster by visiting') + end + end + end + + shared_examples 'editing a user-provided cluster' do + before do + clusterable.add_maintainer(current_user) + visit cluster_path + end + + it 'is able to edit the name, API url, CA certificate and token' do + within('#js-cluster-details') do + cluster_name_field = find('#cluster_name') + api_url_field = find('#cluster_platform_kubernetes_attributes_api_url') + ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert') + token_field = find('#cluster_platform_kubernetes_attributes_token') + + expect(cluster_name_field).not_to be_readonly + expect(api_url_field).not_to be_readonly + expect(ca_certificate_field).not_to be_readonly + expect(token_field).not_to be_readonly + end + end + + it 'does not display GKE information' do + within('#advanced-settings-section') do + expect(page).not_to have_content('Google Kubernetes Engine') + expect(page).not_to have_content('Manage your Kubernetes cluster by visiting') + end + end + end + context 'when clusterable is a project' do it_behaves_like 'editing domain' do let(:clusterable) { create(:project) } let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) } let(:cluster_path) { project_cluster_path(clusterable, cluster) } end + + it_behaves_like 'editing a GCP cluster' do + let(:clusterable) { create(:project) } + let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) } + let(:cluster_path) { project_cluster_path(clusterable, cluster) } + end + + it_behaves_like 'editing a user-provided cluster' do + let(:clusterable) { create(:project) } + let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [clusterable]) } + let(:cluster_path) { project_cluster_path(clusterable, cluster) } + end end context 'when clusterable is a group' do @@ -67,5 +135,17 @@ describe 'Clusterable > Show page' do let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) } let(:cluster_path) { group_cluster_path(clusterable, cluster) } end + + it_behaves_like 'editing a GCP cluster' do + let(:clusterable) { create(:group) } + let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) } + let(:cluster_path) { group_cluster_path(clusterable, cluster) } + end + + it_behaves_like 'editing a user-provided cluster' do + let(:clusterable) { create(:group) } + let(:cluster) { create(:cluster, :provided_by_user, :group, groups: [clusterable]) } + let(:cluster_path) { group_cluster_path(clusterable, cluster) } + end end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index acbcdc7d170..fabd2806d9a 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -620,4 +620,20 @@ describe Clusters::Cluster do end end end + + describe '#provided_by_user?' do + subject { cluster.provided_by_user? } + + context 'with a GCP provider' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + it { is_expected.to be_falsy } + end + + context 'with an user provider' do + let(:cluster) { create(:cluster, :provided_by_user) } + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index af65530e663..a79b436c22a 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -15,7 +15,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to delegate_method(:project).to(:cluster) } it { is_expected.to delegate_method(:enabled?).to(:cluster) } - it { is_expected.to delegate_method(:managed?).to(:cluster) } + it { is_expected.to delegate_method(:provided_by_user?).to(:cluster) } it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) } it_behaves_like 'having unique enum values' diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index 754ba0a594c..a9d786bc872 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -228,4 +228,20 @@ describe Clusters::ClusterPresenter do it { is_expected.to eq(group_cluster_path(group, cluster)) } end end + + describe '#read_only_kubernetes_platform_fields?' do + subject { described_class.new(cluster).read_only_kubernetes_platform_fields? } + + context 'with a user-provided cluster' do + let(:cluster) { build_stubbed(:cluster, :provided_by_user) } + + it { is_expected.to be_falsy } + end + + context 'with a GCP-provided cluster' do + let(:cluster) { build_stubbed(:cluster, :provided_by_gcp) } + + it { is_expected.to be_truthy } + end + end end -- cgit v1.2.1 From 0481d699075ea48c67037088713689b8d66f1983 Mon Sep 17 00:00:00 2001 From: mfluharty Date: Fri, 29 Mar 2019 09:46:31 -0600 Subject: Add control for variable value masking Show masked switch for each variable When toggled on, the variable value will be masked in runner logs Show warning message if the switch is on but the value is not maskable --- spec/features/group_variables_spec.rb | 2 +- spec/features/project_variables_spec.rb | 2 +- spec/fixtures/api/schemas/variable.json | 2 + .../ci_variable_list/ci_variable_list_spec.js | 19 ++-- .../features/variable_list_shared_examples.rb | 123 ++++++++++++++++----- 5 files changed, 109 insertions(+), 39 deletions(-) (limited to 'spec') diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 1a53e7c9512..fc5777e8c7c 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) } + let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', masked: true, group: group) } let(:page_path) { group_settings_ci_cd_path(group) } before do diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 6bdf5df1036..76abc640077 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Project variables', :js do let(:user) { create(:user) } let(:project) { create(:project) } - let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') } + let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value', masked: true) } let(:page_path) { project_settings_ci_cd_path(project) } before do diff --git a/spec/fixtures/api/schemas/variable.json b/spec/fixtures/api/schemas/variable.json index 6f6b044115b..305071a6b3f 100644 --- a/spec/fixtures/api/schemas/variable.json +++ b/spec/fixtures/api/schemas/variable.json @@ -4,12 +4,14 @@ "id", "key", "value", + "masked", "protected" ], "properties": { "id": { "type": "integer" }, "key": { "type": "string" }, "value": { "type": "string" }, + "masked": { "type": "boolean" }, "protected": { "type": "boolean" }, "environment_scope": { "type": "string", "optional": true } }, diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 70f49469300..394e60fc22c 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -127,20 +127,25 @@ describe('VariableList', () => { variableList.init(); }); - it('should add another row when editing the last rows protected checkbox', done => { + it('should not add another row when editing the last rows protected checkbox', done => { const $row = $wrapper.find('.js-row:last-child'); $row.find('.ci-variable-protected-item .js-project-feature-toggle').click(); getSetTimeoutPromise() .then(() => { - expect($wrapper.find('.js-row').length).toBe(2); + expect($wrapper.find('.js-row').length).toBe(1); + }) + .then(done) + .catch(done.fail); + }); - // Check for the correct default in the new row - const $protectedInput = $wrapper - .find('.js-row:last-child') - .find('.js-ci-variable-input-protected'); + it('should not add another row when editing the last rows masked checkbox', done => { + const $row = $wrapper.find('.js-row:last-child'); + $row.find('.ci-variable-masked-item .js-project-feature-toggle').click(); - expect($protectedInput.val()).toBe('false'); + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find('.js-row').length).toBe(1); }) .then(done) .catch(done.fail); diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 73156d18c1b..693b796fbdc 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -23,10 +23,13 @@ shared_examples 'variable list' do end end - it 'adds empty variable' do + it 'adds a new protected variable' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') - find('.js-ci-variable-input-value').set('') + find('.js-ci-variable-input-value').set('key_value') + find('.ci-variable-protected-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end click_button('Save variables') @@ -37,17 +40,17 @@ shared_examples 'variable list' do # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') + expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') + expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') end end - it 'adds new protected variable' do + it 'defaults to masked' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') find('.js-ci-variable-input-value').set('key_value') - find('.ci-variable-protected-item .js-project-feature-toggle').click - expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') end click_button('Save variables') @@ -59,7 +62,7 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') - expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') end end @@ -163,27 +166,6 @@ shared_examples 'variable list' do end end - it 'edits variable with empty value' do - page.within('.js-ci-variable-list-section') do - click_button('Reveal value') - - page.within('.js-row:nth-child(1)') do - find('.js-ci-variable-input-key').set('new_key') - find('.js-ci-variable-input-value').set('') - end - - click_button('Save variables') - wait_for_requests - - visit page_path - - page.within('.js-row:nth-child(1)') do - expect(find('.js-ci-variable-input-key').value).to eq('new_key') - expect(find('.js-ci-variable-input-value', visible: false).value).to eq('') - end - end - end - it 'edits variable to be protected' do # Create the unprotected variable page.within('.js-ci-variable-list-section .js-row:last-child') do @@ -251,6 +233,57 @@ shared_examples 'variable list' do end end + it 'edits variable to be unmasked' do + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + + find('.ci-variable-masked-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + end + end + + it 'edits variable to be masked' do + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + + find('.ci-variable-masked-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + + find('.ci-variable-masked-item .js-project-feature-toggle').click + + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + end + end + it 'handles multiple edits and deletion in the middle' do page.within('.js-ci-variable-list-section') do # Create 2 variables @@ -297,11 +330,11 @@ shared_examples 'variable list' do it 'shows validation error box about duplicate keys' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('samekey') - find('.js-ci-variable-input-value').set('value1') + find('.js-ci-variable-input-value').set('value123') end page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('samekey') - find('.js-ci-variable-input-value').set('value2') + find('.js-ci-variable-input-value').set('value456') end click_button('Save variables') @@ -314,4 +347,34 @@ shared_examples 'variable list' do expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/) end end + + it 'shows validation error box about empty values' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('empty_value') + find('.js-ci-variable-input-value').set('') + end + + click_button('Save variables') + wait_for_requests + + page.within('.js-ci-variable-list-section') do + expect(all('.js-ci-variable-error-box ul li').count).to eq(1) + expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/) + end + end + + it 'shows validation error box about unmaskable values' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('unmaskable_value') + find('.js-ci-variable-input-value').set('???') + end + + click_button('Save variables') + wait_for_requests + + page.within('.js-ci-variable-list-section') do + expect(all('.js-ci-variable-error-box ul li').count).to eq(1) + expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/) + end + end end -- cgit v1.2.1 From a9441396daac861a381c50cef0766f929b1b26b6 Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Mon, 25 Mar 2019 11:13:22 -0400 Subject: Scroll to diff file when clicking on file name - Add an ID to the diff content - handle clicking on file name in diffFileHeader when it is not a link to another page but rather a link to an element on the page --- .../diffs/components/diff_file_header_spec.js | 71 +++++++++++++++++++++- spec/javascripts/lib/utils/common_utils_spec.js | 33 ++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 66c5b17b825..2d10b8383e2 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -3,7 +3,7 @@ import Vuex from 'vuex'; import diffsModule from '~/diffs/store/modules'; import notesModule from '~/notes/stores/modules'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import mountComponent, { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import diffDiscussionsMockData from '../mock_data/diff_discussions'; import { diffViewerModes } from '~/ide/constants'; @@ -252,6 +252,75 @@ describe('diff_file_header', () => { expect(vm.$emit).not.toHaveBeenCalled(); }); }); + + describe('handleFileNameClick', () => { + let e; + + beforeEach(() => { + e = { preventDefault: () => {} }; + spyOn(e, 'preventDefault'); + }); + + describe('when file name links to other page', () => { + it('does not call preventDefault if submodule tree url exists', () => { + vm = mountComponent(Component, { + ...props, + diffFile: { ...props.diffFile, submodule_tree_url: 'foobar.com' }, + }); + + vm.handleFileNameClick(e); + + expect(e.preventDefault).not.toHaveBeenCalled(); + }); + + it('does not call preventDefault if submodule_link exists', () => { + vm = mountComponent(Component, { + ...props, + diffFile: { ...props.diffFile, submodule_link: 'foobar.com' }, + }); + vm.handleFileNameClick(e); + + expect(e.preventDefault).not.toHaveBeenCalled(); + }); + + it('does not call preventDefault if discussionPath exists', () => { + vm = mountComponent(Component, { + ...props, + discussionPath: 'Foo bar', + }); + + vm.handleFileNameClick(e); + + expect(e.preventDefault).not.toHaveBeenCalled(); + }); + }); + + describe('scrolling to diff', () => { + let scrollToElement; + let el; + + beforeEach(() => { + el = document.createElement('div'); + spyOn(document, 'querySelector').and.returnValue(el); + scrollToElement = spyOnDependency(DiffFileHeader, 'scrollToElement'); + vm = mountComponent(Component, props); + + vm.handleFileNameClick(e); + }); + + it('calls scrollToElement with file content', () => { + expect(scrollToElement).toHaveBeenCalledWith(el); + }); + + it('element adds the content id to the window location', () => { + expect(window.location.hash).toContain(props.diffFile.file_hash); + }); + + it('calls preventDefault when button does not link to other page', () => { + expect(e.preventDefault).toHaveBeenCalled(); + }); + }); + }); }); describe('template', () => { diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 0bb43c94f6a..2084c36e484 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -2,6 +2,7 @@ import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; import MockAdapter from 'axios-mock-adapter'; import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data'; +import BreakpointInstance from '~/breakpoints'; const PIXEL_TOLERANCE = 0.2; @@ -380,6 +381,38 @@ describe('common_utils', () => { }); }); + describe('contentTop', () => { + it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => { + spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('sm'); + + setFixtures(` +
      + blah blah blah +
      +
      + more blah blah blah +
      + `); + + expect(commonUtils.contentTop()).toBe(0); + }); + + it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => { + spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('lg'); + + setFixtures(` +
      + blah blah blah +
      +
      + more blah blah blah +
      + `); + + expect(commonUtils.contentTop()).toBe(18); + }); + }); + describe('parseBoolean', () => { const { parseBoolean } = commonUtils; -- cgit v1.2.1 From bf286d1172430051cf12ad2e1bbe27c063b7d74d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Mar 2019 20:57:45 -0700 Subject: Fix failing spec in spec/features/users/login_spec.rb This spec would start to fail on the first of the month because the generated text would have a zero-padded day (e.g. 01 vs 1), whereas the expected text check used an unpadded day via the `-d` parameter in strftime (https://apidock.com/ruby/Date/strftime). To fix this, we use use the `d` parameter to pad zeros. This spec was introduced recently in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25731. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59731 --- spec/features/users/login_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 368a814874f..9d5780d29b0 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -447,7 +447,7 @@ describe 'Login' do 'You can leave Group 1 and leave Group 2. '\ 'You need to do this '\ 'before '\ - "#{(Time.zone.now + 2.days).strftime("%a, %-d %b %Y %H:%M:%S %z")}" + "#{(Time.zone.now + 2.days).strftime("%a, %d %b %Y %H:%M:%S %z")}" ) end end -- cgit v1.2.1 From cedbb3366bf3dd9bafe95dde366c1e28ee70c615 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Mar 2019 15:16:31 -0700 Subject: Fix API /project/:id/branches not returning correct merge status https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24034 introduced a regression where only the first 20 branches were used to determine whether a branch has been merged because the pagination was applied incorrectly. Requesting the second page of branches via the API would always have the wrong merge status. We fix this by properly paginating the branches before requesting their merge status. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/56250 --- spec/requests/api/branches_spec.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index b38cd66986f..75dcedabf96 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -36,10 +36,30 @@ describe API::Branches do expect(branch_names).to match_array(project.repository.branch_names) end + def check_merge_status(json_response) + merged, unmerged = json_response.partition { |branch| branch['merged'] } + merged_branches = merged.map { |branch| branch['name'] } + unmerged_branches = unmerged.map { |branch| branch['name'] } + expect(Set.new(merged_branches)).to eq(project.repository.merged_branch_names(merged_branches + unmerged_branches)) + expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty + end + it 'determines only a limited number of merged branch names' do - expect(API::Entities::Branch).to receive(:represent).with(anything, has_merged_branch_names_count(2)) + expect(API::Entities::Branch).to receive(:represent).with(anything, has_merged_branch_names_count(1)).and_call_original get api(route, current_user), params: { per_page: 2 } + + expect(response).to have_gitlab_http_status(200) + + check_merge_status(json_response) + end + + it 'merge status matches reality on paginated input' do + get api(route, current_user), params: { per_page: 20, page: 2 } + + expect(response).to have_gitlab_http_status(200) + + check_merge_status(json_response) end context 'when repository is disabled' do -- cgit v1.2.1 From 2675581cb9dbc37c81732ac69e2166da6ced0429 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 30 Mar 2019 05:28:37 -0700 Subject: Revise merged branch check to green light up to N branches The main point of this check is to ensure we aren't checking all branches, not that we have an exact count. --- spec/requests/api/branches_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 75dcedabf96..8b503777443 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -20,9 +20,9 @@ describe API::Branches do let(:route) { "/projects/#{project_id}/repository/branches" } shared_examples_for 'repository branches' do - RSpec::Matchers.define :has_merged_branch_names_count do |expected| + RSpec::Matchers.define :has_up_to_merged_branch_names_count do |expected| match do |actual| - actual[:merged_branch_names].count == expected + expected >= actual[:merged_branch_names].count end end @@ -45,7 +45,7 @@ describe API::Branches do end it 'determines only a limited number of merged branch names' do - expect(API::Entities::Branch).to receive(:represent).with(anything, has_merged_branch_names_count(1)).and_call_original + expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original get api(route, current_user), params: { per_page: 2 } -- cgit v1.2.1 From b82ccf2cbc074e36c78dd1e331c6745cb9b71349 Mon Sep 17 00:00:00 2001 From: Bastian Blank Date: Fri, 29 Mar 2019 23:54:44 +0100 Subject: Don't try to set any ACL on uploaded objects Set ACL of uploaded objects to not-public (fog-aws) or no ACL at all (fog-google). Value is ignored by other supported backends (fog-aliyun, fog-openstack, fog-rackspace) This allows uploads to GCS with ACL support disabled. --- spec/uploaders/object_storage_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 533e9d87ea6..9ce9a353913 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -375,7 +375,7 @@ describe ObjectStorage do describe '#fog_public' do subject { uploader.fog_public } - it { is_expected.to eq(false) } + it { is_expected.to eq(nil) } end describe '.workhorse_authorize' do -- cgit v1.2.1 From de6bab915cc080ecbbce5eb827017d46e1f95e1d Mon Sep 17 00:00:00 2001 From: Elias Werberich Date: Sun, 31 Mar 2019 16:09:51 +0200 Subject: Disable inaccessible navigation links upon archiving a project --- .../layouts/nav/sidebar/_project.html.haml_spec.rb | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'spec') diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index 2c60ccfb754..c6c10001bc5 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -113,4 +113,56 @@ describe 'layouts/nav/sidebar/_project' do end end end + + describe 'ci/cd settings tab' do + before do + project.update!(archived: project_archived) + end + + context 'when project is archived' do + let(:project_archived) { true } + + it 'does not show the ci/cd settings tab' do + render + + expect(rendered).not_to have_link('CI / CD', href: project_settings_ci_cd_path(project)) + end + end + + context 'when project is active' do + let(:project_archived) { false } + + it 'shows the ci/cd settings tab' do + render + + expect(rendered).to have_link('CI / CD', href: project_settings_ci_cd_path(project)) + end + end + end + + describe 'operations settings tab' do + before do + project.update!(archived: project_archived) + end + + context 'when project is archived' do + let(:project_archived) { true } + + it 'does not show the operations settings tab' do + render + + expect(rendered).not_to have_link('Operations', href: project_settings_operations_path(project)) + end + end + + context 'when project is active' do + let(:project_archived) { false } + + it 'shows the operations settings tab' do + render + + expect(rendered).to have_link('Operations', href: project_settings_operations_path(project)) + end + end + end end -- cgit v1.2.1 From a2cfc150cec677b27728a4758e5e40fff5b4c284 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Sat, 30 Mar 2019 20:23:56 +1300 Subject: Add # frozen_string_literal to spec/models Adds `# frozen_string_literal: true` to spec/models ruby files --- spec/models/ability_spec.rb | 2 ++ spec/models/abuse_report_spec.rb | 2 ++ spec/models/active_session_spec.rb | 2 ++ spec/models/appearance_spec.rb | 2 ++ spec/models/application_setting/term_spec.rb | 2 ++ spec/models/application_setting_spec.rb | 2 ++ spec/models/award_emoji_spec.rb | 2 ++ spec/models/badge_spec.rb | 2 ++ spec/models/badges/group_badge_spec.rb | 2 ++ spec/models/badges/project_badge_spec.rb | 2 ++ spec/models/blob_spec.rb | 2 ++ spec/models/blob_viewer/base_spec.rb | 2 ++ spec/models/blob_viewer/changelog_spec.rb | 2 ++ spec/models/blob_viewer/composer_json_spec.rb | 2 ++ spec/models/blob_viewer/gemspec_spec.rb | 2 ++ spec/models/blob_viewer/gitlab_ci_yml_spec.rb | 2 ++ spec/models/blob_viewer/license_spec.rb | 2 ++ spec/models/blob_viewer/package_json_spec.rb | 2 ++ spec/models/blob_viewer/podspec_json_spec.rb | 2 ++ spec/models/blob_viewer/podspec_spec.rb | 2 ++ spec/models/blob_viewer/readme_spec.rb | 2 ++ spec/models/blob_viewer/route_map_spec.rb | 2 ++ spec/models/blob_viewer/server_side_spec.rb | 2 ++ spec/models/board_spec.rb | 2 ++ spec/models/broadcast_message_spec.rb | 2 ++ spec/models/chat_name_spec.rb | 2 ++ spec/models/chat_team_spec.rb | 2 ++ spec/models/ci/artifact_blob_spec.rb | 2 ++ spec/models/ci/bridge_spec.rb | 2 ++ spec/models/ci/build_metadata_spec.rb | 2 ++ spec/models/ci/build_runner_session_spec.rb | 2 ++ spec/models/ci/build_spec.rb | 2 ++ spec/models/ci/build_trace_chunk_spec.rb | 2 ++ spec/models/ci/build_trace_chunks/database_spec.rb | 2 ++ spec/models/ci/build_trace_chunks/fog_spec.rb | 2 ++ spec/models/ci/build_trace_chunks/redis_spec.rb | 2 ++ spec/models/ci/build_trace_section_name_spec.rb | 2 ++ spec/models/ci/build_trace_section_spec.rb | 2 ++ spec/models/ci/group_spec.rb | 2 ++ spec/models/ci/group_variable_spec.rb | 2 ++ spec/models/ci/job_artifact_spec.rb | 2 ++ spec/models/ci/legacy_stage_spec.rb | 2 ++ spec/models/ci/pipeline_schedule_spec.rb | 2 ++ spec/models/ci/pipeline_schedule_variable_spec.rb | 2 ++ spec/models/ci/pipeline_spec.rb | 2 ++ spec/models/ci/pipeline_variable_spec.rb | 2 ++ spec/models/ci/runner_spec.rb | 2 ++ spec/models/ci/stage_spec.rb | 2 ++ spec/models/ci/trigger_request_spec.rb | 2 ++ spec/models/ci/trigger_spec.rb | 2 ++ spec/models/ci/variable_spec.rb | 2 ++ spec/models/clusters/applications/cert_manager_spec.rb | 2 ++ spec/models/clusters/applications/helm_spec.rb | 2 ++ spec/models/clusters/applications/ingress_spec.rb | 2 ++ spec/models/clusters/applications/jupyter_spec.rb | 2 ++ spec/models/clusters/applications/knative_spec.rb | 2 ++ spec/models/clusters/applications/prometheus_spec.rb | 2 ++ spec/models/clusters/applications/runner_spec.rb | 2 ++ spec/models/clusters/platforms/kubernetes_spec.rb | 2 ++ spec/models/clusters/project_spec.rb | 2 ++ spec/models/clusters/providers/gcp_spec.rb | 2 ++ spec/models/commit_collection_spec.rb | 2 ++ spec/models/commit_range_spec.rb | 2 ++ spec/models/commit_spec.rb | 2 ++ spec/models/commit_status_spec.rb | 2 ++ spec/models/compare_spec.rb | 2 ++ spec/models/concerns/access_requestable_spec.rb | 2 ++ spec/models/concerns/avatarable_spec.rb | 2 ++ spec/models/concerns/awardable_spec.rb | 2 ++ spec/models/concerns/batch_destroy_dependent_associations_spec.rb | 2 ++ spec/models/concerns/blocks_json_serialization_spec.rb | 2 ++ spec/models/concerns/cache_markdown_field_spec.rb | 2 ++ spec/models/concerns/cacheable_attributes_spec.rb | 2 ++ spec/models/concerns/case_sensitivity_spec.rb | 2 ++ spec/models/concerns/chronic_duration_attribute_spec.rb | 2 ++ spec/models/concerns/deployable_spec.rb | 2 ++ spec/models/concerns/deployment_platform_spec.rb | 2 ++ spec/models/concerns/discussion_on_diff_spec.rb | 2 ++ spec/models/concerns/each_batch_spec.rb | 2 ++ spec/models/concerns/editable_spec.rb | 2 ++ spec/models/concerns/expirable_spec.rb | 2 ++ spec/models/concerns/faster_cache_keys_spec.rb | 2 ++ spec/models/concerns/feature_gate_spec.rb | 2 ++ spec/models/concerns/group_descendant_spec.rb | 2 ++ spec/models/concerns/has_status_spec.rb | 2 ++ spec/models/concerns/has_variable_spec.rb | 2 ++ spec/models/concerns/ignorable_column_spec.rb | 2 ++ spec/models/concerns/issuable_spec.rb | 2 ++ spec/models/concerns/loaded_in_group_list_spec.rb | 2 ++ spec/models/concerns/manual_inverse_association_spec.rb | 2 ++ spec/models/concerns/mentionable_spec.rb | 2 ++ spec/models/concerns/milestoneish_spec.rb | 2 ++ spec/models/concerns/noteable_spec.rb | 2 ++ spec/models/concerns/participable_spec.rb | 2 ++ spec/models/concerns/presentable_spec.rb | 2 ++ spec/models/concerns/project_features_compatibility_spec.rb | 2 ++ spec/models/concerns/prometheus_adapter_spec.rb | 2 ++ spec/models/concerns/protected_ref_access_spec.rb | 2 ++ spec/models/concerns/reactive_caching_spec.rb | 2 ++ spec/models/concerns/redactable_spec.rb | 2 ++ spec/models/concerns/redis_cacheable_spec.rb | 2 ++ spec/models/concerns/relative_positioning_spec.rb | 2 ++ spec/models/concerns/resolvable_discussion_spec.rb | 2 ++ spec/models/concerns/resolvable_note_spec.rb | 2 ++ spec/models/concerns/routable_spec.rb | 2 ++ spec/models/concerns/sha_attribute_spec.rb | 2 ++ spec/models/concerns/sortable_spec.rb | 2 ++ spec/models/concerns/spammable_spec.rb | 2 ++ spec/models/concerns/strip_attribute_spec.rb | 2 ++ spec/models/concerns/subscribable_spec.rb | 2 ++ spec/models/concerns/token_authenticatable_spec.rb | 2 ++ spec/models/concerns/token_authenticatable_strategies/base_spec.rb | 2 ++ spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb | 2 ++ spec/models/concerns/triggerable_hooks_spec.rb | 2 ++ spec/models/concerns/uniquify_spec.rb | 2 ++ spec/models/container_repository_spec.rb | 2 ++ spec/models/conversational_development_index/metric_spec.rb | 2 ++ spec/models/cycle_analytics/code_spec.rb | 2 ++ spec/models/cycle_analytics/issue_spec.rb | 2 ++ spec/models/cycle_analytics/plan_spec.rb | 2 ++ spec/models/cycle_analytics/production_spec.rb | 2 ++ spec/models/cycle_analytics/review_spec.rb | 2 ++ spec/models/cycle_analytics/staging_spec.rb | 2 ++ spec/models/cycle_analytics/test_spec.rb | 2 ++ spec/models/cycle_analytics_spec.rb | 2 ++ spec/models/deploy_key_spec.rb | 2 ++ spec/models/deploy_keys_project_spec.rb | 2 ++ spec/models/deploy_token_spec.rb | 2 ++ spec/models/deployment_spec.rb | 2 ++ spec/models/diff_discussion_spec.rb | 2 ++ spec/models/diff_note_spec.rb | 2 ++ spec/models/diff_viewer/base_spec.rb | 2 ++ spec/models/diff_viewer/server_side_spec.rb | 2 ++ spec/models/discussion_spec.rb | 2 ++ spec/models/email_spec.rb | 2 ++ spec/models/environment_spec.rb | 2 ++ spec/models/environment_status_spec.rb | 2 ++ spec/models/event_collection_spec.rb | 2 ++ spec/models/event_spec.rb | 2 ++ spec/models/external_issue_spec.rb | 2 ++ spec/models/fork_network_member_spec.rb | 2 ++ spec/models/fork_network_spec.rb | 2 ++ spec/models/generic_commit_status_spec.rb | 2 ++ spec/models/global_milestone_spec.rb | 2 ++ spec/models/gpg_key_spec.rb | 2 ++ spec/models/gpg_key_subkey_spec.rb | 2 ++ spec/models/gpg_signature_spec.rb | 2 ++ spec/models/group_custom_attribute_spec.rb | 2 ++ spec/models/group_label_spec.rb | 2 ++ spec/models/group_milestone_spec.rb | 2 ++ spec/models/group_spec.rb | 2 ++ spec/models/guest_spec.rb | 2 ++ spec/models/hooks/active_hook_filter_spec.rb | 2 ++ spec/models/hooks/project_hook_spec.rb | 2 ++ spec/models/hooks/service_hook_spec.rb | 2 ++ spec/models/hooks/system_hook_spec.rb | 2 ++ spec/models/hooks/web_hook_log_spec.rb | 2 ++ spec/models/hooks/web_hook_spec.rb | 2 ++ spec/models/identity_spec.rb | 2 ++ spec/models/import_export_upload_spec.rb | 2 ++ spec/models/instance_configuration_spec.rb | 2 ++ spec/models/internal_id_spec.rb | 2 ++ spec/models/issue/metrics_spec.rb | 2 ++ spec/models/issue_collection_spec.rb | 2 ++ spec/models/issue_spec.rb | 2 ++ spec/models/key_spec.rb | 2 ++ spec/models/label_link_spec.rb | 2 ++ spec/models/label_priority_spec.rb | 2 ++ spec/models/label_spec.rb | 2 ++ spec/models/legacy_diff_discussion_spec.rb | 2 ++ spec/models/lfs_download_object_spec.rb | 2 ++ spec/models/lfs_file_lock_spec.rb | 2 ++ spec/models/lfs_object_spec.rb | 2 ++ spec/models/lfs_objects_project_spec.rb | 2 ++ spec/models/license_template_spec.rb | 2 ++ spec/models/list_spec.rb | 2 ++ spec/models/member_spec.rb | 2 ++ spec/models/members/group_member_spec.rb | 2 ++ spec/models/members/project_member_spec.rb | 2 ++ spec/models/merge_request/metrics_spec.rb | 2 ++ spec/models/merge_request_diff_commit_spec.rb | 2 ++ spec/models/merge_request_diff_file_spec.rb | 2 ++ spec/models/merge_request_diff_spec.rb | 2 ++ spec/models/merge_request_spec.rb | 2 ++ spec/models/milestone_spec.rb | 2 ++ spec/models/namespace_spec.rb | 2 ++ spec/models/network/graph_spec.rb | 2 ++ spec/models/note_diff_file_spec.rb | 2 ++ spec/models/note_spec.rb | 2 ++ spec/models/notification_recipient_spec.rb | 2 ++ spec/models/notification_setting_spec.rb | 2 ++ spec/models/pages_domain_spec.rb | 2 ++ spec/models/personal_access_token_spec.rb | 2 ++ spec/models/programming_language_spec.rb | 2 ++ spec/models/project_authorization_spec.rb | 2 ++ spec/models/project_auto_devops_spec.rb | 2 ++ spec/models/project_custom_attribute_spec.rb | 2 ++ spec/models/project_deploy_token_spec.rb | 2 ++ spec/models/project_feature_spec.rb | 2 ++ spec/models/project_group_link_spec.rb | 2 ++ spec/models/project_import_state_spec.rb | 2 ++ spec/models/project_label_spec.rb | 2 ++ spec/models/project_services/asana_service_spec.rb | 2 ++ spec/models/project_services/assembla_service_spec.rb | 2 ++ spec/models/project_services/bamboo_service_spec.rb | 2 ++ spec/models/project_services/bugzilla_service_spec.rb | 2 ++ spec/models/project_services/buildkite_service_spec.rb | 2 ++ spec/models/project_services/campfire_service_spec.rb | 2 ++ spec/models/project_services/chat_message/issue_message_spec.rb | 2 ++ spec/models/project_services/chat_message/merge_message_spec.rb | 2 ++ spec/models/project_services/chat_message/note_message_spec.rb | 2 ++ spec/models/project_services/chat_message/pipeline_message_spec.rb | 2 ++ spec/models/project_services/chat_message/push_message_spec.rb | 2 ++ spec/models/project_services/chat_message/wiki_page_message_spec.rb | 2 ++ spec/models/project_services/chat_notification_service_spec.rb | 2 ++ spec/models/project_services/custom_issue_tracker_service_spec.rb | 2 ++ spec/models/project_services/drone_ci_service_spec.rb | 2 ++ spec/models/project_services/emails_on_push_service_spec.rb | 2 ++ spec/models/project_services/external_wiki_service_spec.rb | 2 ++ spec/models/project_services/flowdock_service_spec.rb | 2 ++ spec/models/project_services/gitlab_issue_tracker_service_spec.rb | 2 ++ spec/models/project_services/irker_service_spec.rb | 2 ++ spec/models/project_services/issue_tracker_service_spec.rb | 2 ++ spec/models/project_services/jira_service_spec.rb | 2 ++ spec/models/project_services/kubernetes_service_spec.rb | 2 ++ spec/models/project_services/mattermost_service_spec.rb | 2 ++ spec/models/project_services/mattermost_slash_commands_service_spec.rb | 2 ++ spec/models/project_services/microsoft_teams_service_spec.rb | 2 ++ spec/models/project_services/packagist_service_spec.rb | 2 ++ spec/models/project_services/pipelines_email_service_spec.rb | 2 ++ spec/models/project_services/pivotaltracker_service_spec.rb | 2 ++ spec/models/project_services/pushover_service_spec.rb | 2 ++ spec/models/project_services/redmine_service_spec.rb | 2 ++ spec/models/project_services/slack_service_spec.rb | 2 ++ spec/models/project_services/slack_slash_commands_service_spec.rb | 2 ++ spec/models/project_services/teamcity_service_spec.rb | 2 ++ spec/models/project_services/youtrack_service_spec.rb | 2 ++ spec/models/project_snippet_spec.rb | 2 ++ spec/models/project_spec.rb | 2 ++ spec/models/project_statistics_spec.rb | 2 ++ spec/models/project_team_spec.rb | 2 ++ spec/models/project_wiki_spec.rb | 2 ++ spec/models/protectable_dropdown_spec.rb | 2 ++ spec/models/protected_branch/merge_access_level_spec.rb | 2 ++ spec/models/protected_branch/push_access_level_spec.rb | 2 ++ spec/models/protected_branch_spec.rb | 2 ++ spec/models/protected_tag_spec.rb | 2 ++ spec/models/push_event_payload_spec.rb | 2 ++ spec/models/push_event_spec.rb | 2 ++ spec/models/redirect_route_spec.rb | 2 ++ spec/models/release_spec.rb | 2 ++ spec/models/remote_mirror_spec.rb | 2 ++ spec/models/repository_language_spec.rb | 2 ++ spec/models/repository_spec.rb | 2 ++ spec/models/route_spec.rb | 2 ++ spec/models/sent_notification_spec.rb | 2 ++ spec/models/service_spec.rb | 2 ++ spec/models/snippet_blob_spec.rb | 2 ++ spec/models/snippet_spec.rb | 2 ++ spec/models/spam_log_spec.rb | 2 ++ spec/models/ssh_host_key_spec.rb | 2 ++ spec/models/subscription_spec.rb | 2 ++ spec/models/system_note_metadata_spec.rb | 2 ++ spec/models/term_agreement_spec.rb | 2 ++ spec/models/timelog_spec.rb | 2 ++ spec/models/todo_spec.rb | 2 ++ spec/models/tree_spec.rb | 2 ++ spec/models/trending_project_spec.rb | 2 ++ spec/models/upload_spec.rb | 2 ++ spec/models/user_agent_detail_spec.rb | 2 ++ spec/models/user_callout_spec.rb | 2 ++ spec/models/user_custom_attribute_spec.rb | 2 ++ spec/models/user_interacted_project_spec.rb | 2 ++ spec/models/user_spec.rb | 2 ++ spec/models/wiki_directory_spec.rb | 2 ++ spec/models/wiki_page_spec.rb | 2 ++ 276 files changed, 552 insertions(+) (limited to 'spec') diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index eee80e9bad7..d9d60e02a97 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ability do diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index f49a61062c1..a5f8e999d5d 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe AbuseReport do diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index e128fe8a4b7..b523f393ece 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 3e95aa2b5dd..209d138f956 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Appearance do diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb index aa49594f4d1..dd263335b81 100644 --- a/spec/models/application_setting/term_spec.rb +++ b/spec/models/application_setting/term_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ApplicationSetting::Term do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 314f0728b8e..c5579dafb4a 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ApplicationSetting do diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb index 3f52091698c..8452ac69734 100644 --- a/spec/models/award_emoji_spec.rb +++ b/spec/models/award_emoji_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AwardEmoji do diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb index 33dc19e3432..314d7d1e9f4 100644 --- a/spec/models/badge_spec.rb +++ b/spec/models/badge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Badge do diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb index ed7f83d0489..c297bc957ea 100644 --- a/spec/models/badges/group_badge_spec.rb +++ b/spec/models/badges/group_badge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupBadge do diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb index 0e1a8159cb6..e683d110252 100644 --- a/spec/models/badges/project_badge_spec.rb +++ b/spec/models/badges/project_badge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectBadge do diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 05cf242e84d..d0e1688cce3 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -1,4 +1,6 @@ # encoding: utf-8 +# frozen_string_literal: true + require 'rails_helper' describe Blob do diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb index 7ba28f72215..39c7a34f052 100644 --- a/spec/models/blob_viewer/base_spec.rb +++ b/spec/models/blob_viewer/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Base do diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb index db41eca0fc8..0fcc94182af 100644 --- a/spec/models/blob_viewer/changelog_spec.rb +++ b/spec/models/blob_viewer/changelog_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Changelog do diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb index 85b0d9668a0..eda34779679 100644 --- a/spec/models/blob_viewer/composer_json_spec.rb +++ b/spec/models/blob_viewer/composer_json_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::ComposerJson do diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb index d8c4490637f..b6cc82c03ba 100644 --- a/spec/models/blob_viewer/gemspec_spec.rb +++ b/spec/models/blob_viewer/gemspec_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Gemspec do diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb index 16bf947b493..db405ceb4f1 100644 --- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb +++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::GitlabCiYml do diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb index 222ed166ee0..e02bfae3829 100644 --- a/spec/models/blob_viewer/license_spec.rb +++ b/spec/models/blob_viewer/license_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::License do diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb index fbaa8d47a71..b317278f3c8 100644 --- a/spec/models/blob_viewer/package_json_spec.rb +++ b/spec/models/blob_viewer/package_json_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::PackageJson do diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb index 9a23877b23f..7f1fb8666fd 100644 --- a/spec/models/blob_viewer/podspec_json_spec.rb +++ b/spec/models/blob_viewer/podspec_json_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::PodspecJson do diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb index 02d06ea24d6..527ae79d766 100644 --- a/spec/models/blob_viewer/podspec_spec.rb +++ b/spec/models/blob_viewer/podspec_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Podspec do diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb index 8d11d58cfca..958927bddb4 100644 --- a/spec/models/blob_viewer/readme_spec.rb +++ b/spec/models/blob_viewer/readme_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Readme do diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb index c13662427b0..f7ce873c9d1 100644 --- a/spec/models/blob_viewer/route_map_spec.rb +++ b/spec/models/blob_viewer/route_map_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::RouteMap do diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb index 63790486200..f95305abe78 100644 --- a/spec/models/blob_viewer/server_side_spec.rb +++ b/spec/models/blob_viewer/server_side_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::ServerSide do diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb index 12d29540137..54452faa0e1 100644 --- a/spec/models/board_spec.rb +++ b/spec/models/board_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Board do diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 30ca07d5d2c..3ab013ddc0e 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BroadcastMessage do diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb index 504bc710b25..82991937644 100644 --- a/spec/models/chat_name_spec.rb +++ b/spec/models/chat_name_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatName do diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb index 70a9a206faa..76beb3d506b 100644 --- a/spec/models/chat_team_spec.rb +++ b/spec/models/chat_team_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatTeam do diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb index 0014bbcf9f5..f63816fd92a 100644 --- a/spec/models/ci/artifact_blob_spec.rb +++ b/spec/models/ci/artifact_blob_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::ArtifactBlob do diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index b5ec8991720..aacfbe3f180 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Bridge do diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 016a5899eef..917a65ddf21 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildMetadata do diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb index 35622366829..a52c10019e6 100644 --- a/spec/models/ci/build_runner_session_spec.rb +++ b/spec/models/ci/build_runner_session_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildRunnerSession, model: true do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 3ec07143e93..2c41dfa65cd 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Build do diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index 59db347582b..f7ee4614e02 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do diff --git a/spec/models/ci/build_trace_chunks/database_spec.rb b/spec/models/ci/build_trace_chunks/database_spec.rb index d8fc9d57e95..eb94d7dae38 100644 --- a/spec/models/ci/build_trace_chunks/database_spec.rb +++ b/spec/models/ci/build_trace_chunks/database_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunks::Database do diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb index 8f49190af13..b8d78bcd069 100644 --- a/spec/models/ci/build_trace_chunks/fog_spec.rb +++ b/spec/models/ci/build_trace_chunks/fog_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunks::Fog do diff --git a/spec/models/ci/build_trace_chunks/redis_spec.rb b/spec/models/ci/build_trace_chunks/redis_spec.rb index 9da1e6a95ee..6cff33d24fa 100644 --- a/spec/models/ci/build_trace_chunks/redis_spec.rb +++ b/spec/models/ci/build_trace_chunks/redis_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do diff --git a/spec/models/ci/build_trace_section_name_spec.rb b/spec/models/ci/build_trace_section_name_spec.rb index 386ee6880cb..11e2d27ff79 100644 --- a/spec/models/ci/build_trace_section_name_spec.rb +++ b/spec/models/ci/build_trace_section_name_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceSectionName, model: true do diff --git a/spec/models/ci/build_trace_section_spec.rb b/spec/models/ci/build_trace_section_spec.rb index 541a9a36fb8..5bd3a953ec0 100644 --- a/spec/models/ci/build_trace_section_spec.rb +++ b/spec/models/ci/build_trace_section_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceSection, model: true do diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb index 838fa63cb1f..36c65d92840 100644 --- a/spec/models/ci/group_spec.rb +++ b/spec/models/ci/group_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Group do diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index 21d96bf3454..b3999765e5f 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::GroupVariable do diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index c68ba02b8de..d7abd54eec1 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::JobArtifact do diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb index 0c33c1466b7..be0307518eb 100644 --- a/spec/models/ci/legacy_stage_spec.rb +++ b/spec/models/ci/legacy_stage_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::LegacyStage do diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 8ee15f0e734..81913f4a3b6 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PipelineSchedule do diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb index dc8427f28bc..3c9379ecb0d 100644 --- a/spec/models/ci/pipeline_schedule_variable_spec.rb +++ b/spec/models/ci/pipeline_schedule_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PipelineScheduleVariable do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e5f3a9ce67a..e76cf376ed1 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Pipeline, :mailer do diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb index 03d09cb31d6..2ecb688299a 100644 --- a/spec/models/ci/pipeline_variable_spec.rb +++ b/spec/models/ci/pipeline_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PipelineVariable do diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index eb2daed7f32..b3ab63925dd 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Runner do diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 3228c400155..661958390e2 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Stage, :models do diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb index 7dcf3528f73..d04349bec92 100644 --- a/spec/models/ci/trigger_request_spec.rb +++ b/spec/models/ci/trigger_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::TriggerRequest do diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index d4b72205203..fde8375f2a5 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Trigger do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 02c07a2bd83..d2df6b3344e 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Variable do diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index cf5cbf8ec5c..fefeb8fd75f 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::CertManager do diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index f16eff92167..f97d126d918 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Helm do diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index a40fa988287..09e60b9a206 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Ingress do diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 2967c4076c6..5970a1959b5 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Jupyter do diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 054ed0be240..25493689fbc 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Knative do diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 81708b0c2ed..82a502addd4 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Prometheus do diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 3ce8aa1c7bc..7e2f5835279 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Runner do diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index a79b436c22a..14bec17a2bd 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb index 82ef5a23c18..2f017e69251 100644 --- a/spec/models/clusters/project_spec.rb +++ b/spec/models/clusters/project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Project do diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb index 5012e6f15c6..785db4febe0 100644 --- a/spec/models/clusters/providers/gcp_spec.rb +++ b/spec/models/clusters/providers/gcp_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Providers::Gcp do diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb index 30c504ebea8..8a7fcb39fe2 100644 --- a/spec/models/commit_collection_spec.rb +++ b/spec/models/commit_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CommitCollection do diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index f2efcd9d0e9..b96ca89c893 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CommitRange do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 9d4e18534ae..14f4b4d692f 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Commit do diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index e2b7f5c6ee2..ca2f9e36c98 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CommitStatus do diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb index 0bc3ee014e6..43c3580bed2 100644 --- a/spec/models/compare_spec.rb +++ b/spec/models/compare_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Compare do diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb index 04d6cfa2c02..de2bc3a387b 100644 --- a/spec/models/concerns/access_requestable_spec.rb +++ b/spec/models/concerns/access_requestable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AccessRequestable do diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb index 1ea7f2b9985..c750be6b75c 100644 --- a/spec/models/concerns/avatarable_spec.rb +++ b/spec/models/concerns/avatarable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Avatarable do diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb index 5713106418d..9e7106281ee 100644 --- a/spec/models/concerns/awardable_spec.rb +++ b/spec/models/concerns/awardable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Awardable do diff --git a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb index e5392fe0462..1fe90d3cc9a 100644 --- a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb +++ b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BatchDestroyDependentAssociations do diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb index 5906b588d0e..e8657c45a93 100644 --- a/spec/models/concerns/blocks_json_serialization_spec.rb +++ b/spec/models/concerns/blocks_json_serialization_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe BlocksJsonSerialization do diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 7d555f15e39..78637ff10c6 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CacheMarkdownField do diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index 43a544cfe26..394fac52aa7 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CacheableAttributes do diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb index 1bf6c9b3404..d6d41a25eac 100644 --- a/spec/models/concerns/case_sensitivity_spec.rb +++ b/spec/models/concerns/case_sensitivity_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CaseSensitivity do diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index 51221e07ca3..e41d75568f7 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'ChronicDurationAttribute reader' do diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb index 6951be903fe..42bed9434f5 100644 --- a/spec/models/concerns/deployable_spec.rb +++ b/spec/models/concerns/deployable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Deployable do diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb index 19ab4382b53..0e34d8fccf3 100644 --- a/spec/models/concerns/deployment_platform_spec.rb +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe DeploymentPlatform do diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb index 64bf04071e8..baddca47dfa 100644 --- a/spec/models/concerns/discussion_on_diff_spec.rb +++ b/spec/models/concerns/discussion_on_diff_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiscussionOnDiff do diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb index 17224c09693..c4cf8e80f7a 100644 --- a/spec/models/concerns/each_batch_spec.rb +++ b/spec/models/concerns/each_batch_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EachBatch do diff --git a/spec/models/concerns/editable_spec.rb b/spec/models/concerns/editable_spec.rb index 49a9a8ebcbc..4a4a3ca5687 100644 --- a/spec/models/concerns/editable_spec.rb +++ b/spec/models/concerns/editable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Editable do diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb index f7b436f32e6..f4f5eab5b86 100644 --- a/spec/models/concerns/expirable_spec.rb +++ b/spec/models/concerns/expirable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Expirable do diff --git a/spec/models/concerns/faster_cache_keys_spec.rb b/spec/models/concerns/faster_cache_keys_spec.rb index 8d3f94267fa..7830acbae3d 100644 --- a/spec/models/concerns/faster_cache_keys_spec.rb +++ b/spec/models/concerns/faster_cache_keys_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe FasterCacheKeys do diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb index 3f601243245..276d3d9e1d5 100644 --- a/spec/models/concerns/feature_gate_spec.rb +++ b/spec/models/concerns/feature_gate_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe FeatureGate do diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb index 28352d8c961..194caac3fce 100644 --- a/spec/models/concerns/group_descendant_spec.rb +++ b/spec/models/concerns/group_descendant_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupDescendant, :nested_groups do diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index e8b1eba67cc..a217dc42537 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HasStatus do diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb index bff96e12ffa..2bb21d7934e 100644 --- a/spec/models/concerns/has_variable_spec.rb +++ b/spec/models/concerns/has_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HasVariable do diff --git a/spec/models/concerns/ignorable_column_spec.rb b/spec/models/concerns/ignorable_column_spec.rb index b70f2331a0e..6b82825d2cc 100644 --- a/spec/models/concerns/ignorable_column_spec.rb +++ b/spec/models/concerns/ignorable_column_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IgnorableColumn do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 72c6161424b..259ac6852a8 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issuable do diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb index 7a279547a3a..7c97b580779 100644 --- a/spec/models/concerns/loaded_in_group_list_spec.rb +++ b/spec/models/concerns/loaded_in_group_list_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LoadedInGroupList do diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb index ff4a04ea573..ee32e3b165b 100644 --- a/spec/models/concerns/manual_inverse_association_spec.rb +++ b/spec/models/concerns/manual_inverse_association_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ManualInverseAssociation do diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index a9b237fa9ea..f31e3e8821d 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Mentionable do diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 81ca5b638fe..22d4b2cc517 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestone, 'Milestoneish' do diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb index 485a6e165a1..ee613b199ad 100644 --- a/spec/models/concerns/noteable_spec.rb +++ b/spec/models/concerns/noteable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Noteable do diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb index 431f1482615..3d5937c4fc6 100644 --- a/spec/models/concerns/participable_spec.rb +++ b/spec/models/concerns/participable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Participable do diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb index 941647a79fb..9db868dd348 100644 --- a/spec/models/concerns/presentable_spec.rb +++ b/spec/models/concerns/presentable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Presentable do diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 9041690023f..5aa43b58217 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectFeaturesCompatibility do diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb index f4b9c57e71a..7148261b1e4 100644 --- a/spec/models/concerns/prometheus_adapter_spec.rb +++ b/spec/models/concerns/prometheus_adapter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PrometheusAdapter, :use_clean_rails_memory_store_caching do diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb index ce602337647..94798f0590d 100644 --- a/spec/models/concerns/protected_ref_access_spec.rb +++ b/spec/models/concerns/protected_ref_access_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedRefAccess do diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 03ae45e6b17..32e13d5abed 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ReactiveCaching, :use_clean_rails_memory_store_caching do diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb index 7feeaa54069..57c7d2cb767 100644 --- a/spec/models/concerns/redactable_spec.rb +++ b/spec/models/concerns/redactable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Redactable do diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 23c6c6233e9..a9dca27f258 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RedisCacheable do diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb index ac8da30b6c9..d0ae45f7871 100644 --- a/spec/models/concerns/relative_positioning_spec.rb +++ b/spec/models/concerns/relative_positioning_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RelativePositioning do diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb index 97b046b0f21..9ea01ca9002 100644 --- a/spec/models/concerns/resolvable_discussion_spec.rb +++ b/spec/models/concerns/resolvable_discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Discussion, ResolvableDiscussion do diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb index fcb5250278e..4f46252a044 100644 --- a/spec/models/concerns/resolvable_note_spec.rb +++ b/spec/models/concerns/resolvable_note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Note, ResolvableNote do diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 565266321d3..1fb0dd5030c 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Group, 'Routable' do diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb index 0d3beb6a6e3..a4a81ae126d 100644 --- a/spec/models/concerns/sha_attribute_spec.rb +++ b/spec/models/concerns/sha_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ShaAttribute do diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb index 0a9d2021a19..184f7986a6f 100644 --- a/spec/models/concerns/sortable_spec.rb +++ b/spec/models/concerns/sortable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Sortable do diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb index e698207166c..650d49e41a1 100644 --- a/spec/models/concerns/spammable_spec.rb +++ b/spec/models/concerns/spammable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Spammable do diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb index 8c945686b66..5c0d1042e06 100644 --- a/spec/models/concerns/strip_attribute_spec.rb +++ b/spec/models/concerns/strip_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StripAttribute do diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb index 45dfb136aea..2f88adf08dd 100644 --- a/spec/models/concerns/subscribable_spec.rb +++ b/spec/models/concerns/subscribable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Subscribable, 'Subscribable' do diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index 40cb4eef60a..51e28974ae0 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'TokenAuthenticatable' do diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb index 2a0182b4294..7332da309d5 100644 --- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb +++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TokenAuthenticatableStrategies::Base do diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb index ca38f86c5ab..70f41981b3b 100644 --- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb +++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TokenAuthenticatableStrategies::Encrypted do diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb index 265abd6bd72..f28e5f56411 100644 --- a/spec/models/concerns/triggerable_hooks_spec.rb +++ b/spec/models/concerns/triggerable_hooks_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe TriggerableHooks do diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb index 6cd2de6dcce..9ba35702ba6 100644 --- a/spec/models/concerns/uniquify_spec.rb +++ b/spec/models/concerns/uniquify_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Uniquify do diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb index e46945e301e..013112d1d51 100644 --- a/spec/models/container_repository_spec.rb +++ b/spec/models/container_repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ContainerRepository do diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb index b3193619503..60b1a860dfd 100644 --- a/spec/models/conversational_development_index/metric_spec.rb +++ b/spec/models/conversational_development_index/metric_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ConversationalDevelopmentIndex::Metric do diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 6a6b58fb52b..b22a0340015 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#code' do diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index 45f1b4fe8a3..07d60be091a 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#issue' do diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index d366e2b723a..3d22a284264 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#plan' do diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 156eb96cfce..383727cd8f7 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#production' do diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 0aedfb49cb5..1af5f9cc1f4 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#review' do diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index 0cbda50c688..8375944f03c 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#staging' do diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index e58b8fdff58..b78258df564 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#test' do diff --git a/spec/models/cycle_analytics_spec.rb b/spec/models/cycle_analytics_spec.rb index 0fe24870f02..5d8b5b573cf 100644 --- a/spec/models/cycle_analytics_spec.rb +++ b/spec/models/cycle_analytics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CycleAnalytics do diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 41440c6d288..ec6cfb6b826 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployKey, :mailer do diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index fca3090ff4a..c137444763b 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployKeysProject do diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 3435f93c999..05320703e25 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployToken do diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 5fce9504334..d9170d5fa07 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Deployment do diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb index 50b19000799..cfeb4382927 100644 --- a/spec/models/diff_discussion_spec.rb +++ b/spec/models/diff_discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffDiscussion do diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 67e5f4f7e41..fa19cb47a0d 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffNote do diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb index f4efe5a7b3a..b8bdeb781dc 100644 --- a/spec/models/diff_viewer/base_spec.rb +++ b/spec/models/diff_viewer/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffViewer::Base do diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb index 86b14b6ebf3..27de0584b8a 100644 --- a/spec/models/diff_viewer/server_side_spec.rb +++ b/spec/models/diff_viewer/server_side_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffViewer::ServerSide do diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index a46f7ed6507..0d02165787a 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Discussion do diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb index 47eb0717c0c..cae88f39660 100644 --- a/spec/models/email_spec.rb +++ b/spec/models/email_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Email do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ab1b306e597..448ed35cb1e 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Environment do diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index 2576a9aba06..c503c35305f 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EnvironmentStatus do diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb index 6078f429bdc..efe511042c3 100644 --- a/spec/models/event_collection_spec.rb +++ b/spec/models/event_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EventCollection do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index ce4f8ee4705..d192fe70506 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Event do diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index 83ba22caa03..9d064d458f0 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExternalIssue do diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb index 60d04562e6c..eab758248de 100644 --- a/spec/models/fork_network_member_spec.rb +++ b/spec/models/fork_network_member_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ForkNetworkMember do diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb index a43baf1820a..5ec0f8d6b02 100644 --- a/spec/models/fork_network_spec.rb +++ b/spec/models/fork_network_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ForkNetwork do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index a3e68d2e646..c851810ffb3 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GenericCommitStatus do diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index f93904065c7..9d901d01a52 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GlobalMilestone do diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb index 58a1d2e4ea2..479b39cd139 100644 --- a/spec/models/gpg_key_spec.rb +++ b/spec/models/gpg_key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe GpgKey do diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb index 3c86837f47f..51d2f9cb9ac 100644 --- a/spec/models/gpg_key_subkey_spec.rb +++ b/spec/models/gpg_key_subkey_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe GpgKeySubkey do diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb index e90319c39b1..47c343edf0e 100644 --- a/spec/models/gpg_signature_spec.rb +++ b/spec/models/gpg_signature_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe GpgSignature do diff --git a/spec/models/group_custom_attribute_spec.rb b/spec/models/group_custom_attribute_spec.rb index 7ecb2022567..7d60c74b62b 100644 --- a/spec/models/group_custom_attribute_spec.rb +++ b/spec/models/group_custom_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupCustomAttribute do diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index d0fc1eaa3ec..a3a5c631c3d 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupLabel do diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index fcc33cd95fe..01856870fe0 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupMilestone do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 16624ce47d0..2c6abddca17 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Group do diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb index fc30f3056e5..93862e98172 100644 --- a/spec/models/guest_spec.rb +++ b/spec/models/guest_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Guest do diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb index df7edda2213..1249c793f7f 100644 --- a/spec/models/hooks/active_hook_filter_spec.rb +++ b/spec/models/hooks/active_hook_filter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ActiveHookFilter do diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 5dd31b1b5de..a945f0d1516 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectHook do diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index e32eaafc13f..936c2fbad27 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ServiceHook do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index edd1cb455af..e0d4d2e4858 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe SystemHook do diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb index 744a6ccae8b..f812149c9be 100644 --- a/spec/models/hooks/web_hook_log_spec.rb +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe WebHookLog do diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index a308ac6e33a..fe08dc4f5e6 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WebHook do diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index e1a7a59dfd1..74ddc2d6284 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Identity do diff --git a/spec/models/import_export_upload_spec.rb b/spec/models/import_export_upload_spec.rb index 58af84b8a08..18a714f4d98 100644 --- a/spec/models/import_export_upload_spec.rb +++ b/spec/models/import_export_upload_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ImportExportUpload do diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb index cb3d6c7cda2..dd944c43552 100644 --- a/spec/models/instance_configuration_spec.rb +++ b/spec/models/instance_configuration_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe InstanceConfiguration do diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index d32f163f05b..ff2382838ae 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe InternalId do diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index b7291eebe64..07858fe8a70 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issue::Metrics do diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb index 580a98193af..7fc635f100f 100644 --- a/spec/models/issue_collection_spec.rb +++ b/spec/models/issue_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssueCollection do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 6101df2e099..892dd053e39 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issue do diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 06d26ef89f1..a0b6eff88d5 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Key, :mailer do diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index e2b49bc2de7..b160e72e759 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LabelLink do diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb index 9dcb0f06b20..1a93468290f 100644 --- a/spec/models/label_priority_spec.rb +++ b/spec/models/label_priority_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LabelPriority do diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 3fc6c06b7fa..5174c590a10 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Label do diff --git a/spec/models/legacy_diff_discussion_spec.rb b/spec/models/legacy_diff_discussion_spec.rb index dae97b69c84..49ea319fbd1 100644 --- a/spec/models/legacy_diff_discussion_spec.rb +++ b/spec/models/legacy_diff_discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LegacyDiffDiscussion do diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb index 88838b127d2..effd8b08124 100644 --- a/spec/models/lfs_download_object_spec.rb +++ b/spec/models/lfs_download_object_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe LfsDownloadObject do diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb index 41ca1578b94..aa64d66944b 100644 --- a/spec/models/lfs_file_lock_spec.rb +++ b/spec/models/lfs_file_lock_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe LfsFileLock do diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb index 3f929710862..3d4d4b7d795 100644 --- a/spec/models/lfs_object_spec.rb +++ b/spec/models/lfs_object_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LfsObject do diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb index 0a3180f43e8..3e86ee38566 100644 --- a/spec/models/lfs_objects_project_spec.rb +++ b/spec/models/lfs_objects_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LfsObjectsProject do diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb index dd912eefac1..a0224ec2f58 100644 --- a/spec/models/license_template_spec.rb +++ b/spec/models/license_template_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LicenseTemplate do diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index a51580f8292..18d4549977c 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe List do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 188beac1582..c68c3ce2abe 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Member do diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index bc937368cff..f227abd3dae 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupMember do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 36bfff2c339..497764b6825 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectMember do diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index 02ff7839739..49573af0fed 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequest::Metrics do diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb index 10487190a44..ab2aadf7d88 100644 --- a/spec/models/merge_request_diff_commit_spec.rb +++ b/spec/models/merge_request_diff_commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe MergeRequestDiffCommit do diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb index faa47660a74..66957c24fdc 100644 --- a/spec/models/merge_request_diff_file_spec.rb +++ b/spec/models/merge_request_diff_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe MergeRequestDiffFile do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 0f00ea7e85e..a53add67066 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequestDiff do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5adeb616e84..092504cde9e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequest do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 717d4ae4ec0..b82368318f2 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestone do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index aadc298ae0b..62e7dd3231b 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Namespace do diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index c364dd6643b..d1a2bedf542 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Network::Graph do diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb index 591c1a89748..99eeac8d778 100644 --- a/spec/models/note_diff_file_spec.rb +++ b/spec/models/note_diff_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe NoteDiffFile do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index eb6f6ff5faf..7a1ab20186a 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Note do diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb index 13fe47799ed..3710f2be287 100644 --- a/spec/models/notification_recipient_spec.rb +++ b/spec/models/notification_recipient_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NotificationRecipient do diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index c8ab564e3bc..85128456918 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe NotificationSetting do diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 4b85c5e8720..142ddebbbf8 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PagesDomain do diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb index c82ab9c9e62..e0e1101ffc6 100644 --- a/spec/models/personal_access_token_spec.rb +++ b/spec/models/personal_access_token_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PersonalAccessToken do diff --git a/spec/models/programming_language_spec.rb b/spec/models/programming_language_spec.rb index 99cd358f863..b327d360461 100644 --- a/spec/models/programming_language_spec.rb +++ b/spec/models/programming_language_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProgrammingLanguage do diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb index c289ee0859a..6f06fe4e55a 100644 --- a/spec/models/project_authorization_spec.rb +++ b/spec/models/project_authorization_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectAuthorization do diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 7ff64c76e37..8ad28ce68cc 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectAutoDevops do diff --git a/spec/models/project_custom_attribute_spec.rb b/spec/models/project_custom_attribute_spec.rb index 669de5506bc..80638676b49 100644 --- a/spec/models/project_custom_attribute_spec.rb +++ b/spec/models/project_custom_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectCustomAttribute do diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb index 9e2e40c2e8f..2a5fefc1ab0 100644 --- a/spec/models/project_deploy_token_spec.rb +++ b/spec/models/project_deploy_token_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ProjectDeployToken, type: :model do diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index fee7d65c217..50c9d5968ac 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectFeature do diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 5bea21427d4..dad5506900b 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectGroupLink do diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb index e3b2d971419..472bf8f9713 100644 --- a/spec/models/project_import_state_spec.rb +++ b/spec/models/project_import_state_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ProjectImportState, type: :model do diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 689d4e505e5..330aab9f856 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectLabel do diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index e66109fd98f..18e839bcc64 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AsanaService do diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index 5cb6d63659e..7742e33e901 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AssemblaService do diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index b880d90d28f..08c510f09df 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BambooService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb index 43f7bcb1a19..6818db48fee 100644 --- a/spec/models/project_services/bugzilla_service_spec.rb +++ b/spec/models/project_services/bugzilla_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BugzillaService do diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 1615a93a4ca..091d4d8f695 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildkiteService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index ed8347edffd..bf4c52fc7ab 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CampfireService do diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index f7a35fdc88a..d3adc62c38e 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::IssueMessage do diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index 7997b5bb6b9..b56eb19dd55 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::MergeMessage do diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index 5abbd7bec18..5e7987dc0f6 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::NoteMessage do diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb index 0ff20400999..8f9fa310ad4 100644 --- a/spec/models/project_services/chat_message/pipeline_message_spec.rb +++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::PipelineMessage do diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index 973d6bdb2a0..a89645a3ea8 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::PushMessage do diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 7efcba9bcfd..c3db516f253 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::WikiPageMessage do diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb index 46713df77da..6f4ddd223f6 100644 --- a/spec/models/project_services/chat_notification_service_spec.rb +++ b/spec/models/project_services/chat_notification_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatNotificationService do diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb index 7e1b1a4f2af..f0e7551693d 100644 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CustomIssueTrackerService do diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 26597d9b83c..22df19d943f 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DroneCiService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb index d9b7010e5e5..0a58eb367e3 100644 --- a/spec/models/project_services/emails_on_push_service_spec.rb +++ b/spec/models/project_services/emails_on_push_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EmailsOnPushService do diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index 62fd97b038b..bdd8605436f 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExternalWikiService do diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index fabcb142858..c1ebe69ee66 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe FlowdockService do diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 3237b660a16..11f96c03d46 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GitlabIssueTrackerService do diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index cb9ca76fc3f..2e1f6964692 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'socket' require 'json' diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb index e6a1752576b..2fc4d69c2db 100644 --- a/spec/models/project_services/issue_tracker_service_spec.rb +++ b/spec/models/project_services/issue_tracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssueTrackerService do diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 5428fcb1271..4a7eee1fbf3 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe JiraService do diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 56e587262ef..7bf093b71e7 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe KubernetesService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb index 10c62ca55a7..6261c70f266 100644 --- a/spec/models/project_services/mattermost_service_spec.rb +++ b/spec/models/project_services/mattermost_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MattermostService do diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 1983e0cc967..87e482059f2 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MattermostSlashCommandsService do diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb index 3351c6280b4..521d5265753 100644 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ b/spec/models/project_services/microsoft_teams_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MicrosoftTeamsService do diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb index 6acee311700..53f18a1bdd9 100644 --- a/spec/models/project_services/packagist_service_spec.rb +++ b/spec/models/project_services/packagist_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PackagistService do diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb index 75ae2207910..ca17e7453b8 100644 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ b/spec/models/project_services/pipelines_email_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelinesEmailService, :mailer do diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index f7d2372eca2..de2c8790405 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PivotaltrackerService do diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 54b8c658ff6..d2a45f48705 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PushoverService do diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index 2ac14eab5e1..ac570ac27e1 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RedmineService do diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 13cf4d1915e..01f580c5d01 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SlackService do diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb index 5c4bce90ace..8c57907d064 100644 --- a/spec/models/project_services/slack_slash_commands_service_spec.rb +++ b/spec/models/project_services/slack_slash_commands_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SlackSlashCommandsService do diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 64b4efca43a..96dccae733b 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TeamcityService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb index 9524b526a46..bf9d892f66c 100644 --- a/spec/models/project_services/youtrack_service_spec.rb +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe YoutrackService do diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index 1b439bcfad1..e87b4f41f4d 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectSnippet do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 90dcf861849..2158d3cf3e6 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Project do diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index 64c39f09e33..c670b6aac56 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ProjectStatistics do diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 3537dead5d1..a2d4fad9292 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe ProjectTeam do diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 7be8d67ba9e..7ea67f31534 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -1,4 +1,6 @@ # coding: utf-8 +# frozen_string_literal: true + require "spec_helper" describe ProjectWiki do diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb index d4433a88a15..aca3df9fdde 100644 --- a/spec/models/protectable_dropdown_spec.rb +++ b/spec/models/protectable_dropdown_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectableDropdown do diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb index 612e4a0e332..39dd586b157 100644 --- a/spec/models/protected_branch/merge_access_level_spec.rb +++ b/spec/models/protected_branch/merge_access_level_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranch::MergeAccessLevel do diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb index 9ccdc22fd41..628c8d29ecd 100644 --- a/spec/models/protected_branch/push_access_level_spec.rb +++ b/spec/models/protected_branch/push_access_level_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranch::PushAccessLevel do diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index dafe7646366..267434a4148 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranch do diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb index e5a0f6ec23f..79120d17d39 100644 --- a/spec/models/protected_tag_spec.rb +++ b/spec/models/protected_tag_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedTag do diff --git a/spec/models/push_event_payload_spec.rb b/spec/models/push_event_payload_spec.rb index 69a4922b6fd..6b59ee5ee57 100644 --- a/spec/models/push_event_payload_spec.rb +++ b/spec/models/push_event_payload_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PushEventPayload do diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb index bfe7a30b96a..f86500f91cd 100644 --- a/spec/models/push_event_spec.rb +++ b/spec/models/push_event_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PushEvent do diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb index 106ae59af29..6ecb5c31c7e 100644 --- a/spec/models/redirect_route_spec.rb +++ b/spec/models/redirect_route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RedirectRoute do diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 157c96c1f65..b4b32c95dee 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Release do diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index c06e9a08ab4..0478094034a 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RemoteMirror, :mailer do diff --git a/spec/models/repository_language_spec.rb b/spec/models/repository_language_spec.rb index e2e4beb512f..13a4cd1e7cf 100644 --- a/spec/models/repository_language_spec.rb +++ b/spec/models/repository_language_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryLanguage do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 6599b4e765a..2578208659a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Repository do diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 48799781b87..20289afbeb5 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Route do diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb index 6c35ed8f649..09be90b82ed 100644 --- a/spec/models/sent_notification_spec.rb +++ b/spec/models/sent_notification_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SentNotification do diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 25eecb3f909..2f025038bab 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Service do diff --git a/spec/models/snippet_blob_spec.rb b/spec/models/snippet_blob_spec.rb index 7c71c458fcc..88441e39d45 100644 --- a/spec/models/snippet_blob_spec.rb +++ b/spec/models/snippet_blob_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SnippetBlob do diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 664dc3fa145..3524cdae3b8 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Snippet do diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb index 90a2caaeb88..e9ea234f75d 100644 --- a/spec/models/spam_log_spec.rb +++ b/spec/models/spam_log_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SpamLog do diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb index 4c677569561..a17cd8ba345 100644 --- a/spec/models/ssh_host_key_spec.rb +++ b/spec/models/ssh_host_key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SshHostKey do diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb index 9e4c2620d82..41bd48810b2 100644 --- a/spec/models/subscription_spec.rb +++ b/spec/models/subscription_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Subscription do diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb index 1e3f587e460..bcd3c03f947 100644 --- a/spec/models/system_note_metadata_spec.rb +++ b/spec/models/system_note_metadata_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SystemNoteMetadata do diff --git a/spec/models/term_agreement_spec.rb b/spec/models/term_agreement_spec.rb index 950dfa09a6a..42a48048b67 100644 --- a/spec/models/term_agreement_spec.rb +++ b/spec/models/term_agreement_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TermAgreement do diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb index a0c93c531ea..9d69a0ab148 100644 --- a/spec/models/timelog_spec.rb +++ b/spec/models/timelog_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Timelog do diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 3682e21ca40..b5bf294790a 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Todo do diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb index 6bdb62a0864..c2d5dfdf9c4 100644 --- a/spec/models/tree_spec.rb +++ b/spec/models/tree_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Tree do diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb index 3b5e7ca0d39..619fc8e7d38 100644 --- a/spec/models/trending_project_spec.rb +++ b/spec/models/trending_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TrendingProject do diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index 5a0df9fbbb0..02702cb2497 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Upload do diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb index b4669f8c1c2..f191d245045 100644 --- a/spec/models/user_agent_detail_spec.rb +++ b/spec/models/user_agent_detail_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe UserAgentDetail do diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb index d54355afe12..b87f6f03d6f 100644 --- a/spec/models/user_callout_spec.rb +++ b/spec/models/user_callout_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe UserCallout do diff --git a/spec/models/user_custom_attribute_spec.rb b/spec/models/user_custom_attribute_spec.rb index 37fc3cb64f0..d0981b2d771 100644 --- a/spec/models/user_custom_attribute_spec.rb +++ b/spec/models/user_custom_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UserCustomAttribute do diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb index cb4bb3372d4..47d919c1d12 100644 --- a/spec/models/user_interacted_project_spec.rb +++ b/spec/models/user_interacted_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UserInteractedProject do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 1be29d039a7..b7e36748fa2 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe User do diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb index fb8575cfe2b..5fbcccf897e 100644 --- a/spec/models/wiki_directory_spec.rb +++ b/spec/models/wiki_directory_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.describe WikiDirectory do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index cba22b2cc4e..e68da67818a 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe WikiPage do -- cgit v1.2.1 From 3a1a12b3833e28323347b1021a845a4d85beb563 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 1 Apr 2019 11:55:08 +1300 Subject: Fix cert_manager_spec to not modify String Also break up long string to be readable --- .../clusters/applications/cert_manager_spec.rb | 27 +++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index fefeb8fd75f..af7eadfc74c 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -11,7 +11,27 @@ describe Clusters::Applications::CertManager do include_examples 'cluster application initial status specs' describe '#install_command' do - let(:cluster_issuer_file) { { "cluster_issuer.yaml": "---\napiVersion: certmanager.k8s.io/v1alpha1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-prod\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: admin@example.com\n privateKeySecretRef:\n name: letsencrypt-prod\n http01: {}\n" } } + let(:cert_email) { 'admin@example.com' } + + let(:cluster_issuer_file) do + file_contents = <<~EOF + --- + apiVersion: certmanager.k8s.io/v1alpha1 + kind: ClusterIssuer + metadata: + name: letsencrypt-prod + spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: #{cert_email} + privateKeySecretRef: + name: letsencrypt-prod + http01: {} + EOF + + { "cluster_issuer.yaml": file_contents } + end + subject { cert_manager.install_command } it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } @@ -26,9 +46,10 @@ describe Clusters::Applications::CertManager do end context 'for a specific user' do + let(:cert_email) { 'abc@xyz.com' } + before do - cert_manager.email = 'abc@xyz.com' - cluster_issuer_file[:'cluster_issuer.yaml'].gsub! 'admin@example.com', 'abc@xyz.com' + cert_manager.email = cert_email end it 'should use his/her email to register issuer with certificate provider' do -- cgit v1.2.1 From 5cd63ace04deee2e6d7c13694bfd8dc46d0edaf4 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 1 Apr 2019 12:07:50 +1300 Subject: Don't modify String in instance_configuration_spec --- spec/models/instance_configuration_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb index dd944c43552..e65f97df3c3 100644 --- a/spec/models/instance_configuration_spec.rb +++ b/spec/models/instance_configuration_spec.rb @@ -32,8 +32,8 @@ describe InstanceConfiguration do end def stub_pub_file(exist: true) - path = 'spec/fixtures/ssh_host_example_key.pub' - path << 'random' unless exist + path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random' + allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path)) end end -- cgit v1.2.1 From 32a1277d28a87f92a87218a60c32174f4c7279a9 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 1 Apr 2019 13:25:54 +1300 Subject: Fix license_template_spec to not modify String Revamp how spec verifies proc#call called only once --- spec/models/license_template_spec.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb index a0224ec2f58..7037277e580 100644 --- a/spec/models/license_template_spec.rb +++ b/spec/models/license_template_spec.rb @@ -5,14 +5,15 @@ require 'spec_helper' describe LicenseTemplate do describe '#content' do it 'calls a proc exactly once if provided' do - lazy = build_template(-> { 'bar' }) - content = lazy.content + content_proc = -> { 'bar' } + expect(content_proc).to receive(:call).once.and_call_original - expect(content).to eq('bar') - expect(content.object_id).to eq(lazy.content.object_id) + lazy = build_template(content_proc) - content.replace('foo') - expect(lazy.content).to eq('foo') + expect(lazy.content).to eq('bar') + + # Subsequent calls should not call proc again + expect(lazy.content).to eq('bar') end it 'returns a string if provided' do -- cgit v1.2.1 From ab2047d0e74bbb168a96a8d333fa562ec021cff9 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 1 Apr 2019 13:26:31 +1300 Subject: Don't use frozen_string_literal for now While we debug why this is failing --- spec/models/ci/build_trace_chunk_spec.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index f7ee4614e02..59db347582b 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - require 'spec_helper' describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do -- cgit v1.2.1 From 8686e01250824e475dc9412c399c44100878a71e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 31 Mar 2019 22:00:19 -0700 Subject: Avoid excessive recursive calls with Rugged TreeEntries The Rugged implementation was recursively scanning the repository to create `flat_path` because the post-process step was being called from with a loop. For large repositories, this was significantly slowing things down. Break the call to `rugged_populate_flat_path` out of this loop to make this work efficiently. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59759 --- spec/lib/gitlab/git/tree_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 60060c41616..7ad3cde97f8 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Git::Tree, :seed_helper do it 'returns a list of tree objects' do entries = described_class.where(repository, SeedRepo::Commit::ID, 'files', true) - expect(entries.count).to be > 10 + expect(entries.count).to be >= 5 expect(entries).to all(be_a(Gitlab::Git::Tree)) end -- cgit v1.2.1 From a7d4824ded0c5ae6a155d7bdc87a1ba71eb3df5c Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 29 Mar 2019 14:56:40 +0700 Subject: Check mergeability in merge to ref service and add spec Add changelog ok ok --- spec/models/merge_request_spec.rb | 32 ++++++++++++++++++++++ .../merge_requests/merge_to_ref_service_spec.rb | 20 ++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5adeb616e84..138036ab3c2 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3081,6 +3081,38 @@ describe MergeRequest do end end + describe '#mergeable_to_ref?' do + it 'returns true when merge request is mergeable' do + subject = create(:merge_request) + + expect(subject.mergeable_to_ref?).to be(true) + end + + it 'returns false when merge request is already merged' do + subject = create(:merge_request, :merged) + + expect(subject.mergeable_to_ref?).to be(false) + end + + it 'returns false when merge request is closed' do + subject = create(:merge_request, :closed) + + expect(subject.mergeable_to_ref?).to be(false) + end + + it 'returns false when merge request is work in progress' do + subject = create(:merge_request, title: 'WIP: The feature') + + expect(subject.mergeable_to_ref?).to be(false) + end + + it 'returns false when merge request has no commits' do + subject = create(:merge_request, source_branch: 'empty-branch', target_branch: 'master') + + expect(subject.mergeable_to_ref?).to be(false) + end + end + describe '#merge_participants' do it 'contains author' do expect(subject.merge_participants).to eq([subject.author]) diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index fabca8f6b4a..a3b48abae26 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -40,15 +40,6 @@ describe MergeRequests::MergeToRefService do end shared_examples_for 'successfully evaluates pre-condition checks' do - it 'returns error when feature is disabled' do - stub_feature_flags(merge_to_tmp_merge_ref_path: false) - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('Feature is not enabled') - end - it 'returns an error when the failing to process the merge' do allow(project.repository).to receive(:merge_to_ref).and_return(nil) @@ -180,6 +171,17 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end + context 'when merge request is WIP state' do + it 'fails to merge' do + merge_request = create(:merge_request, title: 'WIP: The feature') + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}") + end + end + it 'returns error when user has no authorization to admin the merge request' do unauthorized_user = create(:user) project.add_reporter(unauthorized_user) -- cgit v1.2.1 From e756dca0c5f32c2507fc2ef23c89c6e36e0e1b4c Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 1 Apr 2019 10:55:33 +0200 Subject: Extra permissions in Project & Issue GraphQL Allow extra permissions for the `Types::ProjectType` and `Types:IssueType` GraphQL types. As we'll be adding more permissions in CE. Now this spec only validates if all the expected permissions are present, but it will not fail if there are more. --- spec/graphql/types/permission_types/issue_spec.rb | 4 +++- spec/graphql/types/permission_types/project_spec.rb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb index c3f84629aa2..f0fbeda202f 100644 --- a/spec/graphql/types/permission_types/issue_spec.rb +++ b/spec/graphql/types/permission_types/issue_spec.rb @@ -7,6 +7,8 @@ describe Types::PermissionTypes::Issue do :create_note, :reopen_issue ] - expect(described_class).to have_graphql_fields(expected_permissions) + expected_permissions.each do |permission| + expect(described_class).to have_graphql_field(permission) + end end end diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb index 4288412eda3..4974995b587 100644 --- a/spec/graphql/types/permission_types/project_spec.rb +++ b/spec/graphql/types/permission_types/project_spec.rb @@ -13,6 +13,8 @@ describe Types::PermissionTypes::Project do :update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content ] - expect(described_class).to have_graphql_fields(expected_permissions) + expected_permissions.each do |permission| + expect(described_class).to have_graphql_field(permission) + end end end -- cgit v1.2.1 From 5e10b611299b4a968e51f5a706d60b7d1a9c29b5 Mon Sep 17 00:00:00 2001 From: Antoine Huret Date: Mon, 1 Apr 2019 09:09:49 +0000 Subject: add a uniq constraints on issues and mrs labels --- spec/services/issues/create_service_spec.rb | 14 ++++++++++++++ spec/services/issues/update_service_spec.rb | 10 ++++++++++ 2 files changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 715b1168bfb..d50412b6d2c 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -138,6 +138,20 @@ describe Issues::CreateService do end end + context 'when duplicate label titles are given' do + let(:label) { create(:label, project: project) } + + let(:opts) do + { title: 'Title', + description: 'Description', + labels: [label.title, label.title] } + end + + it 'assigns the label once' do + expect(issue.labels).to contain_exactly(label) + end + end + it 'executes issue hooks when issue is not confidential' do opts = { title: 'Title', description: 'Description', confidential: false } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index f1684209729..1c8a4b608d5 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -592,6 +592,16 @@ describe Issues::UpdateService, :mailer do expect(result.label_ids).not_to include(label.id) end end + + context 'when duplicate label titles are given' do + let(:params) do + { labels: [label3.title, label3.title] } + end + + it 'assigns the label once' do + expect(result.labels).to contain_exactly(label3) + end + end end context 'updating asssignee_id' do -- cgit v1.2.1 From 374d4f17442f9d1f159215e3e4887797620f77de Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 1 Apr 2019 12:49:18 +0200 Subject: Refactor pipeline_spec This makes sure we load the page as part of the spec, instead of a before block. So we could still perform setup inside shared examples, avoiding failures caught by data being wrongly cached when loading the page multiple times during a single spec --- spec/features/projects/pipelines/pipeline_spec.rb | 38 +++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 9fdf78baa1e..b197557039d 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -59,11 +59,11 @@ describe 'Pipeline', :js do let(:project) { create(:project, :repository) } let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) } - before do - visit project_pipeline_path(project, pipeline) - end + subject(:visit_pipeline) { visit project_pipeline_path(project, pipeline) } it 'shows the pipeline graph' do + visit_pipeline + expect(page).to have_selector('.pipeline-visualization') expect(page).to have_content('Build') expect(page).to have_content('Test') @@ -73,14 +73,20 @@ describe 'Pipeline', :js do end it 'shows Pipeline tab pane as active' do + visit_pipeline + expect(page).to have_css('#js-tab-pipeline.active') end it 'shows link to the pipeline ref' do + visit_pipeline + expect(page).to have_link(pipeline.ref) end it 'shows the pipeline information' do + visit_pipeline + within '.pipeline-info' do expect(page).to have_content("#{pipeline.statuses.count} jobs " \ "for #{pipeline.ref} ") @@ -96,6 +102,10 @@ describe 'Pipeline', :js do end describe 'pipeline graph' do + before do + visit_pipeline + end + context 'when pipeline has running builds' do it 'shows a running icon and a cancel action for the running build' do page.within('#ci-badge-deploy') do @@ -227,6 +237,10 @@ describe 'Pipeline', :js do end context 'page tabs' do + before do + visit_pipeline + end + it 'shows Pipeline, Jobs and Failed Jobs tabs with link' do expect(page).to have_link('Pipeline') expect(page).to have_link('Jobs') @@ -253,6 +267,10 @@ describe 'Pipeline', :js do end context 'retrying jobs' do + before do + visit_pipeline + end + it { expect(page).not_to have_content('retried') } context 'when retrying' do @@ -265,6 +283,10 @@ describe 'Pipeline', :js do end context 'canceling jobs' do + before do + visit_pipeline + end + it { expect(page).not_to have_selector('.ci-canceled') } context 'when canceling' do @@ -284,6 +306,10 @@ describe 'Pipeline', :js do user: user) end + before do + visit_pipeline + end + it 'does not render link to the pipeline ref' do expect(page).not_to have_link(pipeline.ref) expect(page).to have_content(pipeline.ref) @@ -305,6 +331,10 @@ describe 'Pipeline', :js do merge_request.all_pipelines.last end + before do + visit_pipeline + end + it 'shows the pipeline information' do within '.pipeline-info' do expect(page).to have_content("#{pipeline.statuses.count} jobs " \ @@ -356,6 +386,8 @@ describe 'Pipeline', :js do before do pipeline.update(user: user) + + visit_pipeline end it 'shows the pipeline information' do -- cgit v1.2.1 From 45293f66facc2792f15bd06bb5b05d55fc155cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Mon, 1 Apr 2019 15:13:22 +0000 Subject: Fix MergeRequest#has_commits? nil comparison --- spec/models/merge_request_spec.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 092504cde9e..4b3a77e6bc2 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2714,14 +2714,21 @@ describe MergeRequest do end describe '#has_commits?' do - before do + it 'returns true when merge request diff has commits' do allow(subject.merge_request_diff).to receive(:commits_count) .and_return(2) - end - it 'returns true when merge request diff has commits' do expect(subject.has_commits?).to be_truthy end + + context 'when commits_count is nil' do + it 'returns false' do + allow(subject.merge_request_diff).to receive(:commits_count) + .and_return(nil) + + expect(subject.has_commits?).to be_falsey + end + end end describe '#has_no_commits?' do -- cgit v1.2.1 From b5e09a26de6afb0c194613c6ee4d22a22a0fce14 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Mon, 1 Apr 2019 16:29:05 +0000 Subject: Include cluster domain into Project Cluster API Domain was introduced on 11.8 and was not included on the Project Cluster API. With this change user will be able to include domain when adding and updating a cluster. Domain will also be included on the GET calls. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59441 --- spec/requests/api/project_clusters_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 4e42e233b4c..81442125a1c 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -60,7 +60,7 @@ describe API::ProjectClusters do end let(:cluster) do - create(:cluster, :project, :provided_by_gcp, + create(:cluster, :project, :provided_by_gcp, :with_domain, platform_kubernetes: platform_kubernetes, user: current_user, projects: [project]) @@ -88,6 +88,7 @@ describe API::ProjectClusters do expect(json_response['platform_type']).to eq('kubernetes') expect(json_response['environment_scope']).to eq('*') expect(json_response['cluster_type']).to eq('project_type') + expect(json_response['domain']).to eq('example.com') end it 'returns project information' do @@ -187,6 +188,7 @@ describe API::ProjectClusters do let(:cluster_params) do { name: 'test-cluster', + domain: 'domain.example.com', platform_kubernetes_attributes: platform_kubernetes_attributes } end @@ -217,6 +219,7 @@ describe API::ProjectClusters do expect(cluster_result).to be_kubernetes expect(cluster_result.project).to eq(project) expect(cluster_result.name).to eq('test-cluster') + expect(cluster_result.domain).to eq('domain.example.com') expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.namespace).to eq(namespace) @@ -294,6 +297,7 @@ describe API::ProjectClusters do let(:update_params) do { + domain: 'new-domain.com', platform_kubernetes_attributes: platform_kubernetes_attributes } end @@ -330,6 +334,7 @@ describe API::ProjectClusters do end it 'should update cluster attributes' do + expect(cluster.domain).to eq('new-domain.com') expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') end end @@ -342,6 +347,7 @@ describe API::ProjectClusters do end it 'should not update cluster attributes' do + expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') expect(cluster.kubernetes_namespace.namespace).not_to eq('invalid_namespace') end -- cgit v1.2.1 From d4c6a3af78c0ef8257c453980832c4d419aabb51 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 31 Mar 2019 06:38:23 -0700 Subject: Force a full GC after importing a project During a project import, it's possible that new branches are created by the importer to handle pull requests that have been created from forked projects, which would increment the `pushes_since_gc` value via `HousekeepingService.increment!` before a full garbage collection gets to run. This causes HousekeepingService to skip the full `git gc` and move to the incremental repack mode. To ensure that a garbage collection is run to pack refs and objects, explicitly execute the task. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59477 --- spec/services/projects/after_import_service_spec.rb | 2 +- spec/services/projects/housekeeping_service_spec.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb index 4dd6c6dab86..765b4ffae8f 100644 --- a/spec/services/projects/after_import_service_spec.rb +++ b/spec/services/projects/after_import_service_spec.rb @@ -13,7 +13,7 @@ describe Projects::AfterImportService do describe '#execute' do before do allow(Projects::HousekeepingService) - .to receive(:new).with(project).and_return(housekeeping_service) + .to receive(:new).with(project, :gc).and_return(housekeeping_service) allow(housekeeping_service) .to receive(:execute).and_yield diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 18ecef1c0a1..12ae9105627 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -88,6 +88,19 @@ describe Projects::HousekeepingService do expect(project.pushes_since_gc).to eq(1) end end + + it 'runs the task specifically requested' do + housekeeping = described_class.new(project, :gc) + + allow(housekeeping).to receive(:try_obtain_lease).and_return(:gc_uuid) + allow(housekeeping).to receive(:lease_key).and_return(:gc_lease_key) + + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :gc_lease_key, :gc_uuid).twice + + 2.times do + housekeeping.execute + end + end end describe '#needed?' do -- cgit v1.2.1 From d6b952ad3e2ab3d4e7742f5f767dde30673a2661 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Sat, 30 Mar 2019 20:15:48 +1300 Subject: Add frozen_string_literal to spec/workers Adds `# frozen_string_literal: true` to spec/workers ruby files --- spec/workers/admin_email_worker_spec.rb | 2 ++ spec/workers/archive_trace_worker_spec.rb | 2 ++ spec/workers/authorized_projects_worker_spec.rb | 2 ++ spec/workers/background_migration_worker_spec.rb | 2 ++ spec/workers/build_coverage_worker_spec.rb | 2 ++ spec/workers/build_finished_worker_spec.rb | 2 ++ spec/workers/build_hooks_worker_spec.rb | 2 ++ spec/workers/build_success_worker_spec.rb | 2 ++ spec/workers/build_trace_sections_worker_spec.rb | 2 ++ spec/workers/ci/archive_traces_cron_worker_spec.rb | 2 ++ spec/workers/ci/build_schedule_worker_spec.rb | 2 ++ spec/workers/cluster_provision_worker_spec.rb | 2 ++ spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb | 2 ++ spec/workers/concerns/application_worker_spec.rb | 2 ++ spec/workers/concerns/cluster_queue_spec.rb | 2 ++ spec/workers/concerns/cronjob_queue_spec.rb | 2 ++ spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb | 2 ++ spec/workers/concerns/gitlab/github_import/object_importer_spec.rb | 2 ++ spec/workers/concerns/gitlab/github_import/queue_spec.rb | 2 ++ spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb | 2 ++ spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb | 2 ++ spec/workers/concerns/pipeline_background_queue_spec.rb | 2 ++ spec/workers/concerns/pipeline_queue_spec.rb | 2 ++ spec/workers/concerns/project_import_options_spec.rb | 2 ++ spec/workers/concerns/repository_check_queue_spec.rb | 2 ++ spec/workers/concerns/waitable_worker_spec.rb | 2 ++ spec/workers/create_gpg_signature_worker_spec.rb | 2 ++ spec/workers/create_note_diff_file_worker_spec.rb | 2 ++ spec/workers/create_pipeline_worker_spec.rb | 2 ++ spec/workers/delete_diff_files_worker_spec.rb | 2 ++ spec/workers/delete_merged_branches_worker_spec.rb | 2 ++ spec/workers/delete_user_worker_spec.rb | 2 ++ spec/workers/deployments/success_worker_spec.rb | 2 ++ spec/workers/detect_repository_languages_worker_spec.rb | 2 ++ spec/workers/email_receiver_worker_spec.rb | 2 ++ spec/workers/emails_on_push_worker_spec.rb | 2 ++ spec/workers/every_sidekiq_worker_spec.rb | 2 ++ spec/workers/expire_build_artifacts_worker_spec.rb | 2 ++ spec/workers/expire_build_instance_artifacts_worker_spec.rb | 2 ++ spec/workers/expire_job_cache_worker_spec.rb | 2 ++ spec/workers/expire_pipeline_cache_worker_spec.rb | 2 ++ spec/workers/git_garbage_collect_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/advance_stage_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/import_issue_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/import_note_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb | 2 ++ .../github_import/stage/import_issues_and_diff_notes_worker_spec.rb | 2 ++ .../gitlab/github_import/stage/import_lfs_objects_worker_spec.rb | 2 ++ spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb | 2 ++ .../gitlab/github_import/stage/import_pull_requests_worker_spec.rb | 2 ++ .../workers/gitlab/github_import/stage/import_repository_worker_spec.rb | 2 ++ spec/workers/gitlab_shell_worker_spec.rb | 2 ++ spec/workers/gitlab_usage_ping_worker_spec.rb | 2 ++ spec/workers/group_destroy_worker_spec.rb | 2 ++ spec/workers/hashed_storage/migrator_worker_spec.rb | 2 ++ spec/workers/hashed_storage/project_migrate_worker_spec.rb | 2 ++ spec/workers/invalid_gpg_signature_update_worker_spec.rb | 2 ++ spec/workers/issue_due_scheduler_worker_spec.rb | 2 ++ spec/workers/mail_scheduler/issue_due_worker_spec.rb | 2 ++ spec/workers/mail_scheduler/notification_service_worker_spec.rb | 2 ++ spec/workers/merge_worker_spec.rb | 2 ++ spec/workers/namespaceless_project_destroy_worker_spec.rb | 2 ++ spec/workers/new_issue_worker_spec.rb | 2 ++ spec/workers/new_merge_request_worker_spec.rb | 2 ++ spec/workers/new_note_worker_spec.rb | 2 ++ spec/workers/pages_domain_verification_cron_worker_spec.rb | 2 ++ spec/workers/pages_domain_verification_worker_spec.rb | 2 ++ spec/workers/pipeline_hooks_worker_spec.rb | 2 ++ spec/workers/pipeline_metrics_worker_spec.rb | 2 ++ spec/workers/pipeline_notification_worker_spec.rb | 2 ++ spec/workers/pipeline_process_worker_spec.rb | 2 ++ spec/workers/pipeline_schedule_worker_spec.rb | 2 ++ spec/workers/pipeline_success_worker_spec.rb | 2 ++ spec/workers/pipeline_update_worker_spec.rb | 2 ++ spec/workers/plugin_worker_spec.rb | 2 ++ spec/workers/post_receive_spec.rb | 2 ++ spec/workers/process_commit_worker_spec.rb | 2 ++ spec/workers/project_cache_worker_spec.rb | 2 ++ spec/workers/project_destroy_worker_spec.rb | 2 ++ spec/workers/project_export_worker_spec.rb | 2 ++ spec/workers/propagate_service_template_worker_spec.rb | 2 ++ spec/workers/prune_old_events_worker_spec.rb | 2 ++ spec/workers/prune_web_hook_logs_worker_spec.rb | 2 ++ spec/workers/reactive_caching_worker_spec.rb | 2 ++ spec/workers/rebase_worker_spec.rb | 2 ++ spec/workers/remote_mirror_notification_worker_spec.rb | 2 ++ spec/workers/remove_expired_group_links_worker_spec.rb | 2 ++ spec/workers/remove_expired_members_worker_spec.rb | 2 ++ spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb | 2 ++ spec/workers/repository_check/batch_worker_spec.rb | 2 ++ spec/workers/repository_check/clear_worker_spec.rb | 2 ++ spec/workers/repository_check/dispatch_worker_spec.rb | 2 ++ spec/workers/repository_check/single_repository_worker_spec.rb | 2 ++ spec/workers/repository_cleanup_worker_spec.rb | 2 ++ spec/workers/repository_fork_worker_spec.rb | 2 ++ spec/workers/repository_import_worker_spec.rb | 2 ++ spec/workers/repository_remove_remote_worker_spec.rb | 2 ++ spec/workers/repository_update_remote_mirror_worker_spec.rb | 2 ++ spec/workers/run_pipeline_schedule_worker_spec.rb | 2 ++ spec/workers/stage_update_worker_spec.rb | 2 ++ spec/workers/stuck_ci_jobs_worker_spec.rb | 2 ++ spec/workers/stuck_import_jobs_worker_spec.rb | 2 ++ spec/workers/stuck_merge_jobs_worker_spec.rb | 2 ++ spec/workers/system_hook_push_worker_spec.rb | 2 ++ spec/workers/todos_destroyer/confidential_issue_worker_spec.rb | 2 ++ spec/workers/todos_destroyer/entity_leave_worker_spec.rb | 2 ++ spec/workers/todos_destroyer/group_private_worker_spec.rb | 2 ++ spec/workers/todos_destroyer/private_features_worker_spec.rb | 2 ++ spec/workers/todos_destroyer/project_private_worker_spec.rb | 2 ++ spec/workers/trending_projects_worker_spec.rb | 2 ++ spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb | 2 ++ spec/workers/update_merge_requests_worker_spec.rb | 2 ++ spec/workers/upload_checksum_worker_spec.rb | 2 ++ spec/workers/wait_for_cluster_creation_worker_spec.rb | 2 ++ 118 files changed, 236 insertions(+) (limited to 'spec') diff --git a/spec/workers/admin_email_worker_spec.rb b/spec/workers/admin_email_worker_spec.rb index 27687f069ea..f72b932423f 100644 --- a/spec/workers/admin_email_worker_spec.rb +++ b/spec/workers/admin_email_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AdminEmailWorker do diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb index 7244ad4f199..368ed3f3db1 100644 --- a/spec/workers/archive_trace_worker_spec.rb +++ b/spec/workers/archive_trace_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ArchiveTraceWorker do diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb index d095138f6b7..4c02278de64 100644 --- a/spec/workers/authorized_projects_worker_spec.rb +++ b/spec/workers/authorized_projects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AuthorizedProjectsWorker do diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb index 3bd072e7125..746c858609f 100644 --- a/spec/workers/background_migration_worker_spec.rb +++ b/spec/workers/background_migration_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BackgroundMigrationWorker, :sidekiq, :clean_gitlab_redis_shared_state do diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb index ba20488f663..25686ae68ca 100644 --- a/spec/workers/build_coverage_worker_spec.rb +++ b/spec/workers/build_coverage_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildCoverageWorker do diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index ccb26849e67..33f327d4a0c 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildFinishedWorker do diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb index 97654a93f5c..59b252a8be3 100644 --- a/spec/workers/build_hooks_worker_spec.rb +++ b/spec/workers/build_hooks_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildHooksWorker do diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index 5eb9709ded9..065aeaf2b65 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildSuccessWorker do diff --git a/spec/workers/build_trace_sections_worker_spec.rb b/spec/workers/build_trace_sections_worker_spec.rb index 45243f45547..97fc0a2da0c 100644 --- a/spec/workers/build_trace_sections_worker_spec.rb +++ b/spec/workers/build_trace_sections_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildTraceSectionsWorker do diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb index 478fb7d2c0f..eca6cf5235f 100644 --- a/spec/workers/ci/archive_traces_cron_worker_spec.rb +++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::ArchiveTracesCronWorker do diff --git a/spec/workers/ci/build_schedule_worker_spec.rb b/spec/workers/ci/build_schedule_worker_spec.rb index 4a3fe84d7f7..647f9763fed 100644 --- a/spec/workers/ci/build_schedule_worker_spec.rb +++ b/spec/workers/ci/build_schedule_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildScheduleWorker do diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb index da32f29fec0..9cc2ad12bfc 100644 --- a/spec/workers/cluster_provision_worker_spec.rb +++ b/spec/workers/cluster_provision_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ClusterProvisionWorker do diff --git a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb index 2e2e9afd25a..a9ffdfb085e 100644 --- a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb +++ b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ClusterWaitForIngressIpAddressWorker do diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb index 901d77178bc..ae5244e2f62 100644 --- a/spec/workers/concerns/application_worker_spec.rb +++ b/spec/workers/concerns/application_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ApplicationWorker do diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb index 4118b9aa194..732d55dfbde 100644 --- a/spec/workers/concerns/cluster_queue_spec.rb +++ b/spec/workers/concerns/cluster_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ClusterQueue do diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb index c042a52f41f..cf4d47b7500 100644 --- a/spec/workers/concerns/cronjob_queue_spec.rb +++ b/spec/workers/concerns/cronjob_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CronjobQueue do diff --git a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb index 4b9aa9a7ef8..200cdffd560 100644 --- a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::NotifyUponDeath do diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb index 9c187bead0a..51b685b5792 100644 --- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ObjectImporter do diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb index a96f583aff7..d262bc2e05c 100644 --- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Queue do diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb index 8de4059c4ae..294eacf09ab 100644 --- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ReschedulingMethods do diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb index d85a87f2cb0..f9081a875b5 100644 --- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::StageMethods do diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb index 24c0a3c6a20..78ceafb359f 100644 --- a/spec/workers/concerns/pipeline_background_queue_spec.rb +++ b/spec/workers/concerns/pipeline_background_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineBackgroundQueue do diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb index a312b307fce..eedfceb8bf0 100644 --- a/spec/workers/concerns/pipeline_queue_spec.rb +++ b/spec/workers/concerns/pipeline_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineQueue do diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb index 3699fd83a9a..c5fbcfb5fb0 100644 --- a/spec/workers/concerns/project_import_options_spec.rb +++ b/spec/workers/concerns/project_import_options_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectImportOptions do diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb index d2eeecfc9a8..55ed71f124c 100644 --- a/spec/workers/concerns/repository_check_queue_spec.rb +++ b/spec/workers/concerns/repository_check_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheckQueue do diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb index ce38cde9208..37fadd6ac02 100644 --- a/spec/workers/concerns/waitable_worker_spec.rb +++ b/spec/workers/concerns/waitable_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WaitableWorker do diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb index f5479e57260..ae09b4b77f1 100644 --- a/spec/workers/create_gpg_signature_worker_spec.rb +++ b/spec/workers/create_gpg_signature_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreateGpgSignatureWorker do diff --git a/spec/workers/create_note_diff_file_worker_spec.rb b/spec/workers/create_note_diff_file_worker_spec.rb index 0ac946a1232..e35aaa7d593 100644 --- a/spec/workers/create_note_diff_file_worker_spec.rb +++ b/spec/workers/create_note_diff_file_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreateNoteDiffFileWorker do diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb index 02cb0f46cb4..62a17da80c0 100644 --- a/spec/workers/create_pipeline_worker_spec.rb +++ b/spec/workers/create_pipeline_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreatePipelineWorker do diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb index e0edd313922..9f8b20df48e 100644 --- a/spec/workers/delete_diff_files_worker_spec.rb +++ b/spec/workers/delete_diff_files_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteDiffFilesWorker do diff --git a/spec/workers/delete_merged_branches_worker_spec.rb b/spec/workers/delete_merged_branches_worker_spec.rb index 39009d9e4b2..a218ca921d9 100644 --- a/spec/workers/delete_merged_branches_worker_spec.rb +++ b/spec/workers/delete_merged_branches_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteMergedBranchesWorker do diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb index 06d9e125105..c963b886e62 100644 --- a/spec/workers/delete_user_worker_spec.rb +++ b/spec/workers/delete_user_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteUserWorker do diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb index ba7d45eca01..1c68922b03d 100644 --- a/spec/workers/deployments/success_worker_spec.rb +++ b/spec/workers/deployments/success_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Deployments::SuccessWorker do diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb index ff3878fbc8e..dbf32555985 100644 --- a/spec/workers/detect_repository_languages_worker_spec.rb +++ b/spec/workers/detect_repository_languages_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DetectRepositoryLanguagesWorker do diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index 045135255d6..f8a31fcdee6 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe EmailReceiverWorker, :mailer do diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index 05b4fb49ea3..0f87df89c29 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EmailsOnPushWorker, :mailer do diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index ebe02373275..8fddd8540ef 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Every Sidekiq worker' do diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb index 27995cf1611..74d6b5605d1 100644 --- a/spec/workers/expire_build_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_artifacts_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpireBuildArtifactsWorker do diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index e1a56c72162..bdb5a3801d9 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpireBuildInstanceArtifactsWorker do diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb index 1b614342a18..6ac285ca944 100644 --- a/spec/workers/expire_job_cache_worker_spec.rb +++ b/spec/workers/expire_job_cache_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpireJobCacheWorker do diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb index 54c9a69d329..c98b69ef76c 100644 --- a/spec/workers/expire_pipeline_cache_worker_spec.rb +++ b/spec/workers/expire_pipeline_cache_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpirePipelineCacheWorker do diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 4895a968d6e..2459638c3e6 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'spec_helper' diff --git a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb index fc7aafbc0c9..b1647d8c7df 100644 --- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb +++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb index 5b1c6b6010a..42d69ff6166 100644 --- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportDiffNoteWorker do diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb index ab070d6d081..06a573e16b7 100644 --- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportIssueWorker do diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb index 3a30f06bb2d..5110c3ff11b 100644 --- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportNoteWorker do diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb index 3cccd7cab21..d46e381fc51 100644 --- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportPullRequestWorker do diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb index 7ff133f1049..fa4ded8e42f 100644 --- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb +++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::RefreshImportJidWorker do diff --git a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb index 91e0cddb5d8..35a856802c2 100644 --- a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::FinishImportWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb index ad6154cc4a4..0c7fc2a164e 100644 --- a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb index ab347f5b75b..5d96f562c30 100644 --- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb index b19884d7991..e7c9dabb292 100644 --- a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb index 94cff9e4e80..90590a45900 100644 --- a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportNotesWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb index 1fbb073a34a..15d485f1018 100644 --- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb index adab535ac05..6d47d73b92e 100644 --- a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb index 6b222af454d..0758cfc4ee2 100644 --- a/spec/workers/gitlab_shell_worker_spec.rb +++ b/spec/workers/gitlab_shell_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GitlabShellWorker do diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb index 49b4e04dc7c..aff5d112cdd 100644 --- a/spec/workers/gitlab_usage_ping_worker_spec.rb +++ b/spec/workers/gitlab_usage_ping_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GitlabUsagePingWorker do diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb index a170c84ab12..90a4150a31a 100644 --- a/spec/workers/group_destroy_worker_spec.rb +++ b/spec/workers/group_destroy_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupDestroyWorker do diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb index a85f820a3eb..a318cdd003e 100644 --- a/spec/workers/hashed_storage/migrator_worker_spec.rb +++ b/spec/workers/hashed_storage/migrator_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HashedStorage::MigratorWorker do diff --git a/spec/workers/hashed_storage/project_migrate_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb index 340e722aa7e..f266c7dbe8c 100644 --- a/spec/workers/hashed_storage/project_migrate_worker_spec.rb +++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do diff --git a/spec/workers/invalid_gpg_signature_update_worker_spec.rb b/spec/workers/invalid_gpg_signature_update_worker_spec.rb index 5972696515b..4f727469ea8 100644 --- a/spec/workers/invalid_gpg_signature_update_worker_spec.rb +++ b/spec/workers/invalid_gpg_signature_update_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe InvalidGpgSignatureUpdateWorker do diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb index 2710267d384..61ad8330840 100644 --- a/spec/workers/issue_due_scheduler_worker_spec.rb +++ b/spec/workers/issue_due_scheduler_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssueDueSchedulerWorker do diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb index 1026ae5b4bf..fa17775e9f2 100644 --- a/spec/workers/mail_scheduler/issue_due_worker_spec.rb +++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MailScheduler::IssueDueWorker do diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb index 5cfba01850c..0729c5f9ffb 100644 --- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb +++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MailScheduler::NotificationServiceWorker do diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index b57c275c770..138a99abde6 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeWorker do diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index 2f21a1321e1..4fbda37e268 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NamespacelessProjectDestroyWorker do diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb index baa8ddb59e5..88a75ce5b70 100644 --- a/spec/workers/new_issue_worker_spec.rb +++ b/spec/workers/new_issue_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NewIssueWorker do diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb index c3f29a40d58..d078ddd07d9 100644 --- a/spec/workers/new_merge_request_worker_spec.rb +++ b/spec/workers/new_merge_request_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NewMergeRequestWorker do diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb index 575361c93d4..2966a201a62 100644 --- a/spec/workers/new_note_worker_spec.rb +++ b/spec/workers/new_note_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe NewNoteWorker do diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb index 8f780428c82..9b479da1cb6 100644 --- a/spec/workers/pages_domain_verification_cron_worker_spec.rb +++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PagesDomainVerificationCronWorker do diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb index 372fc95ab4a..2f23dcb487f 100644 --- a/spec/workers/pages_domain_verification_worker_spec.rb +++ b/spec/workers/pages_domain_verification_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PagesDomainVerificationWorker do diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb index 035e329839f..60df08f40da 100644 --- a/spec/workers/pipeline_hooks_worker_spec.rb +++ b/spec/workers/pipeline_hooks_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineHooksWorker do diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb index 896f9e6e7f2..6beecbcd114 100644 --- a/spec/workers/pipeline_metrics_worker_spec.rb +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineMetricsWorker do diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb index eb539ffd893..98b0f139fe2 100644 --- a/spec/workers/pipeline_notification_worker_spec.rb +++ b/spec/workers/pipeline_notification_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineNotificationWorker, :mailer do diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb index 86e9d7f6684..d33cf72e51e 100644 --- a/spec/workers/pipeline_process_worker_spec.rb +++ b/spec/workers/pipeline_process_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineProcessWorker do diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index ff408427926..f23910d23be 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineScheduleWorker do diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb index d1c84adda6f..4cbe384b47a 100644 --- a/spec/workers/pipeline_success_worker_spec.rb +++ b/spec/workers/pipeline_success_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineSuccessWorker do diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb index 0b456cfd0da..0225e4a9601 100644 --- a/spec/workers/pipeline_update_worker_spec.rb +++ b/spec/workers/pipeline_update_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineUpdateWorker do diff --git a/spec/workers/plugin_worker_spec.rb b/spec/workers/plugin_worker_spec.rb index 9238a8199bc..ca6c9986131 100644 --- a/spec/workers/plugin_worker_spec.rb +++ b/spec/workers/plugin_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PluginWorker do diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 9cddad71a51..66958a4c116 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PostReceive do diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb index 2d071c181c2..47bac63511e 100644 --- a/spec/workers/process_commit_worker_spec.rb +++ b/spec/workers/process_commit_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProcessCommitWorker do diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index b9b5445562f..a7353227043 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectCacheWorker do diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb index 6132f145f8d..ec40900a5b7 100644 --- a/spec/workers/project_destroy_worker_spec.rb +++ b/spec/workers/project_destroy_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectDestroyWorker do diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb index 8899969c178..8065087796c 100644 --- a/spec/workers/project_export_worker_spec.rb +++ b/spec/workers/project_export_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectExportWorker do diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb index af1fb80a51d..fb4ced77832 100644 --- a/spec/workers/propagate_service_template_worker_spec.rb +++ b/spec/workers/propagate_service_template_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PropagateServiceTemplateWorker do diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb index ea2b6ae229e..f1eef1923bf 100644 --- a/spec/workers/prune_old_events_worker_spec.rb +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PruneOldEventsWorker do diff --git a/spec/workers/prune_web_hook_logs_worker_spec.rb b/spec/workers/prune_web_hook_logs_worker_spec.rb index b3ec71d4a00..e57334967fd 100644 --- a/spec/workers/prune_web_hook_logs_worker_spec.rb +++ b/spec/workers/prune_web_hook_logs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PruneWebHookLogsWorker do diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb index 3da851de067..2395e6ec947 100644 --- a/spec/workers/reactive_caching_worker_spec.rb +++ b/spec/workers/reactive_caching_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ReactiveCachingWorker do diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb index 900332ed6b3..578b8cf7451 100644 --- a/spec/workers/rebase_worker_spec.rb +++ b/spec/workers/rebase_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RebaseWorker, '#perform' do diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb index e3db10ed645..5182f67b4af 100644 --- a/spec/workers/remote_mirror_notification_worker_spec.rb +++ b/spec/workers/remote_mirror_notification_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoteMirrorNotificationWorker, :mailer do diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb index 689bc3d27b4..10d9aa37dee 100644 --- a/spec/workers/remove_expired_group_links_worker_spec.rb +++ b/spec/workers/remove_expired_group_links_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoveExpiredGroupLinksWorker do diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb index 058fdf4c009..69a5725bb35 100644 --- a/spec/workers/remove_expired_members_worker_spec.rb +++ b/spec/workers/remove_expired_members_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoveExpiredMembersWorker do diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb index 57f83c1dbe9..0e21933a9a5 100644 --- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb +++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoveUnreferencedLfsObjectsWorker do diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb index 50b93fce2dc..051c6a5d141 100644 --- a/spec/workers/repository_check/batch_worker_spec.rb +++ b/spec/workers/repository_check/batch_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheck::BatchWorker do diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb index 1c49415d46c..7ad9e287204 100644 --- a/spec/workers/repository_check/clear_worker_spec.rb +++ b/spec/workers/repository_check/clear_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheck::ClearWorker do diff --git a/spec/workers/repository_check/dispatch_worker_spec.rb b/spec/workers/repository_check/dispatch_worker_spec.rb index 7877429aa8f..03efb6a0a80 100644 --- a/spec/workers/repository_check/dispatch_worker_spec.rb +++ b/spec/workers/repository_check/dispatch_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheck::DispatchWorker do diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index f11875cffd1..65e1c5e9d5d 100644 --- a/spec/workers/repository_check/single_repository_worker_spec.rb +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'fileutils' diff --git a/spec/workers/repository_cleanup_worker_spec.rb b/spec/workers/repository_cleanup_worker_spec.rb index 3adae0b6cfa..e58664cf22a 100644 --- a/spec/workers/repository_cleanup_worker_spec.rb +++ b/spec/workers/repository_cleanup_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCleanupWorker do diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 31bfe88d0bd..26fd67adfaa 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryForkWorker do diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 87ac4bc05c1..b8767af8eee 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryImportWorker do diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb index 6ddb653d142..6eba5c50960 100644 --- a/spec/workers/repository_remove_remote_worker_spec.rb +++ b/spec/workers/repository_remove_remote_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RepositoryRemoveRemoteWorker do diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb index b582a3650b6..4de51ecb3e9 100644 --- a/spec/workers/repository_update_remote_mirror_worker_spec.rb +++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RepositoryUpdateRemoteMirrorWorker do diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb index 481a84837f9..690af22f4dc 100644 --- a/spec/workers/run_pipeline_schedule_worker_spec.rb +++ b/spec/workers/run_pipeline_schedule_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RunPipelineScheduleWorker do diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb index 7bc76c79464..429d42bac29 100644 --- a/spec/workers/stage_update_worker_spec.rb +++ b/spec/workers/stage_update_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StageUpdateWorker do diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb index e09b8e5b964..72de62f1188 100644 --- a/spec/workers/stuck_ci_jobs_worker_spec.rb +++ b/spec/workers/stuck_ci_jobs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StuckCiJobsWorker do diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb index e94d2be9850..dcb8e59ed28 100644 --- a/spec/workers/stuck_import_jobs_worker_spec.rb +++ b/spec/workers/stuck_import_jobs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StuckImportJobsWorker do diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb index 5aaff27a6b2..09efed6d2cf 100644 --- a/spec/workers/stuck_merge_jobs_worker_spec.rb +++ b/spec/workers/stuck_merge_jobs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StuckMergeJobsWorker do diff --git a/spec/workers/system_hook_push_worker_spec.rb b/spec/workers/system_hook_push_worker_spec.rb index b1d446ed25f..890a622d11a 100644 --- a/spec/workers/system_hook_push_worker_spec.rb +++ b/spec/workers/system_hook_push_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SystemHookPushWorker do diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb index 9d7c0b8f560..18876b71615 100644 --- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb +++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::ConfidentialIssueWorker do diff --git a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb index 955447906aa..cb14fac0910 100644 --- a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb +++ b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::EntityLeaveWorker do diff --git a/spec/workers/todos_destroyer/group_private_worker_spec.rb b/spec/workers/todos_destroyer/group_private_worker_spec.rb index fcc38989ced..d9a240136d5 100644 --- a/spec/workers/todos_destroyer/group_private_worker_spec.rb +++ b/spec/workers/todos_destroyer/group_private_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::GroupPrivateWorker do diff --git a/spec/workers/todos_destroyer/private_features_worker_spec.rb b/spec/workers/todos_destroyer/private_features_worker_spec.rb index 9599f5ee071..abd04acc3bd 100644 --- a/spec/workers/todos_destroyer/private_features_worker_spec.rb +++ b/spec/workers/todos_destroyer/private_features_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::PrivateFeaturesWorker do diff --git a/spec/workers/todos_destroyer/project_private_worker_spec.rb b/spec/workers/todos_destroyer/project_private_worker_spec.rb index 15d926fa9d5..c1bb0438ec3 100644 --- a/spec/workers/todos_destroyer/project_private_worker_spec.rb +++ b/spec/workers/todos_destroyer/project_private_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::ProjectPrivateWorker do diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb index c3c6fdcf2d5..6e524085662 100644 --- a/spec/workers/trending_projects_worker_spec.rb +++ b/spec/workers/trending_projects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TrendingProjectsWorker do diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb index d20d926f5a0..c4af829a5e2 100644 --- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb +++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UpdateHeadPipelineForMergeRequestWorker do diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb index 0b553db0ca4..486dade454a 100644 --- a/spec/workers/update_merge_requests_worker_spec.rb +++ b/spec/workers/update_merge_requests_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UpdateMergeRequestsWorker do diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb index 9e50ce15871..7202c8001b4 100644 --- a/spec/workers/upload_checksum_worker_spec.rb +++ b/spec/workers/upload_checksum_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe UploadChecksumWorker do diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb index 0e92b298178..850eba263a7 100644 --- a/spec/workers/wait_for_cluster_creation_worker_spec.rb +++ b/spec/workers/wait_for_cluster_creation_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WaitForClusterCreationWorker do -- cgit v1.2.1 From 8c5b3d030210cc2659107ec2633a496165470125 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Thu, 28 Mar 2019 20:05:27 +0100 Subject: Allow streaming io objects into Gitaly This allows us to set the encoding of an IO passed without reading it into memory. This is useful if we want to stream files into Gitaly. Like we do when uploading a new file to the repository. --- spec/lib/gitlab/encoding_helper_spec.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 429816efec3..88ea98eb1e1 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -189,14 +189,23 @@ describe Gitlab::EncodingHelper do end end - describe '#binary_stringio' do + describe '#binary_io' do it 'does not mutate the original string encoding' do test = 'my-test' - io_stream = ext_class.binary_stringio(test) + io_stream = ext_class.binary_io(test) expect(io_stream.external_encoding.name).to eq('ASCII-8BIT') expect(test.encoding.name).to eq('UTF-8') end + + it 'returns a copy of the IO with the correct encoding' do + test = fixture_file_upload('spec/fixtures/doc_sample.txt').to_io + + io_stream = ext_class.binary_io(test) + + expect(io_stream.external_encoding.name).to eq('ASCII-8BIT') + expect(test).not_to eq(io_stream) + end end end -- cgit v1.2.1 From e3465a5dde695f316fec13dfde477660b402fdb8 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 1 Apr 2019 18:55:57 +0000 Subject: Add jQuery matchers to Jest --- .../reports/components/report_section_spec.js | 200 +++++++++++++++++++++ spec/frontend/test_setup.js | 8 + .../reports/components/report_section_spec.js | 200 --------------------- 3 files changed, 208 insertions(+), 200 deletions(-) create mode 100644 spec/frontend/reports/components/report_section_spec.js delete mode 100644 spec/javascripts/reports/components/report_section_spec.js (limited to 'spec') diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js new file mode 100644 index 00000000000..3b609484b9e --- /dev/null +++ b/spec/frontend/reports/components/report_section_spec.js @@ -0,0 +1,200 @@ +import Vue from 'vue'; +import reportSection from '~/reports/components/report_section.vue'; +import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper'; + +describe('Report section', () => { + let vm; + const ReportSection = Vue.extend(reportSection); + + const resolvedIssues = [ + { + name: 'Insecure Dependency', + fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5', + path: 'Gemfile.lock', + line: 12, + urlPath: 'foo/Gemfile.lock', + }, + ]; + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + beforeEach(() => { + vm = mountComponent(ReportSection, { + component: '', + status: 'SUCCESS', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + resolvedIssues, + hasIssues: false, + alwaysOpen: false, + }); + }); + + describe('isCollapsible', () => { + const testMatrix = [ + { hasIssues: false, alwaysOpen: false, isCollapsible: false }, + { hasIssues: false, alwaysOpen: true, isCollapsible: false }, + { hasIssues: true, alwaysOpen: false, isCollapsible: true }, + { hasIssues: true, alwaysOpen: true, isCollapsible: false }, + ]; + + testMatrix.forEach(({ hasIssues, alwaysOpen, isCollapsible }) => { + const issues = hasIssues ? 'has issues' : 'has no issues'; + const open = alwaysOpen ? 'is always open' : 'is not always open'; + + it(`is ${isCollapsible}, if the report ${issues} and ${open}`, done => { + vm.hasIssues = hasIssues; + vm.alwaysOpen = alwaysOpen; + + Vue.nextTick() + .then(() => { + expect(vm.isCollapsible).toBe(isCollapsible); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('isExpanded', () => { + const testMatrix = [ + { isCollapsed: false, alwaysOpen: false, isExpanded: true }, + { isCollapsed: false, alwaysOpen: true, isExpanded: true }, + { isCollapsed: true, alwaysOpen: false, isExpanded: false }, + { isCollapsed: true, alwaysOpen: true, isExpanded: true }, + ]; + + testMatrix.forEach(({ isCollapsed, alwaysOpen, isExpanded }) => { + const issues = isCollapsed ? 'is collapsed' : 'is not collapsed'; + const open = alwaysOpen ? 'is always open' : 'is not always open'; + + it(`is ${isExpanded}, if the report ${issues} and ${open}`, done => { + vm.isCollapsed = isCollapsed; + vm.alwaysOpen = alwaysOpen; + + Vue.nextTick() + .then(() => { + expect(vm.isExpanded).toBe(isExpanded); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + }); + + describe('when it is loading', () => { + it('should render loading indicator', () => { + vm = mountComponent(ReportSection, { + component: '', + status: 'LOADING', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + hasIssues: false, + }); + + expect(vm.$el.textContent.trim()).toEqual('Loading codeclimate report'); + }); + }); + + describe('with success status', () => { + beforeEach(() => { + vm = mountComponent(ReportSection, { + component: '', + status: 'SUCCESS', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + resolvedIssues, + hasIssues: true, + }); + }); + + it('should render provided data', () => { + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Code quality improved on 1 point and degraded on 1 point', + ); + + expect(vm.$el.querySelectorAll('.report-block-container li').length).toEqual( + resolvedIssues.length, + ); + }); + + describe('toggleCollapsed', () => { + const hiddenCss = { display: 'none' }; + + it('toggles issues', done => { + vm.$el.querySelector('button').click(); + + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse'); + + vm.$el.querySelector('button').click(); + }) + .then(Vue.nextTick) + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand'); + }) + .then(done) + .catch(done.fail); + }); + + it('is always expanded, if always-open is set to true', done => { + vm.alwaysOpen = true; + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('with failed request', () => { + it('should render error indicator', () => { + vm = mountComponent(ReportSection, { + component: '', + status: 'ERROR', + loadingText: 'Loading codeclimate report', + errorText: 'Failed to load codeclimate report', + successText: 'Code quality improved on 1 point and degraded on 1 point', + hasIssues: false, + }); + + expect(vm.$el.textContent.trim()).toEqual('Failed to load codeclimate report'); + }); + }); + + describe('with action buttons passed to the slot', () => { + beforeEach(() => { + vm = mountComponentWithSlots(ReportSection, { + props: { + status: 'SUCCESS', + successText: 'success', + hasIssues: true, + }, + slots: { + actionButtons: ['Action!'], + }, + }); + }); + + it('should render the passed button', () => { + expect(vm.$el.textContent.trim()).toContain('Action!'); + }); + + it('should still render the expand/collapse button', () => { + expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); + }); + }); +}); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 1e3c28e25eb..f09bcb5407e 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import * as jqueryMatchers from 'custom-jquery-matchers'; import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; import { initializeTestTimeout } from './helpers/timeout'; @@ -44,3 +45,10 @@ Object.assign(global, { preloadFixtures() {}, setFixtures: setHTMLFixture, }); + +// custom-jquery-matchers was written for an old Jest version, we need to make it compatible +Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => { + expect.extend({ + [matcherName]: matcherFactory().compare, + }); +}); diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js deleted file mode 100644 index b02af8baaec..00000000000 --- a/spec/javascripts/reports/components/report_section_spec.js +++ /dev/null @@ -1,200 +0,0 @@ -import Vue from 'vue'; -import reportSection from '~/reports/components/report_section.vue'; -import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper'; - -describe('Report section', () => { - let vm; - const ReportSection = Vue.extend(reportSection); - - const resolvedIssues = [ - { - name: 'Insecure Dependency', - fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5', - path: 'Gemfile.lock', - line: 12, - urlPath: 'foo/Gemfile.lock', - }, - ]; - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - beforeEach(() => { - vm = mountComponent(ReportSection, { - component: '', - status: 'SUCCESS', - loadingText: 'Loading codeclimate report', - errorText: 'foo', - successText: 'Code quality improved on 1 point and degraded on 1 point', - resolvedIssues, - hasIssues: false, - alwaysOpen: false, - }); - }); - - describe('isCollapsible', () => { - const testMatrix = [ - { hasIssues: false, alwaysOpen: false, isCollapsible: false }, - { hasIssues: false, alwaysOpen: true, isCollapsible: false }, - { hasIssues: true, alwaysOpen: false, isCollapsible: true }, - { hasIssues: true, alwaysOpen: true, isCollapsible: false }, - ]; - - testMatrix.forEach(({ hasIssues, alwaysOpen, isCollapsible }) => { - const issues = hasIssues ? 'has issues' : 'has no issues'; - const open = alwaysOpen ? 'is always open' : 'is not always open'; - - it(`is ${isCollapsible}, if the report ${issues} and ${open}`, done => { - vm.hasIssues = hasIssues; - vm.alwaysOpen = alwaysOpen; - - Vue.nextTick() - .then(() => { - expect(vm.isCollapsible).toBe(isCollapsible); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('isExpanded', () => { - const testMatrix = [ - { isCollapsed: false, alwaysOpen: false, isExpanded: true }, - { isCollapsed: false, alwaysOpen: true, isExpanded: true }, - { isCollapsed: true, alwaysOpen: false, isExpanded: false }, - { isCollapsed: true, alwaysOpen: true, isExpanded: true }, - ]; - - testMatrix.forEach(({ isCollapsed, alwaysOpen, isExpanded }) => { - const issues = isCollapsed ? 'is collapsed' : 'is not collapsed'; - const open = alwaysOpen ? 'is always open' : 'is not always open'; - - it(`is ${isExpanded}, if the report ${issues} and ${open}`, done => { - vm.isCollapsed = isCollapsed; - vm.alwaysOpen = alwaysOpen; - - Vue.nextTick() - .then(() => { - expect(vm.isExpanded).toBe(isExpanded); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - }); - - describe('when it is loading', () => { - it('should render loading indicator', () => { - vm = mountComponent(ReportSection, { - component: '', - status: 'LOADING', - loadingText: 'Loading codeclimate report', - errorText: 'foo', - successText: 'Code quality improved on 1 point and degraded on 1 point', - hasIssues: false, - }); - - expect(vm.$el.textContent.trim()).toEqual('Loading codeclimate report'); - }); - }); - - describe('with success status', () => { - beforeEach(() => { - vm = mountComponent(ReportSection, { - component: '', - status: 'SUCCESS', - loadingText: 'Loading codeclimate report', - errorText: 'foo', - successText: 'Code quality improved on 1 point and degraded on 1 point', - resolvedIssues, - hasIssues: true, - }); - }); - - it('should render provided data', () => { - expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( - 'Code quality improved on 1 point and degraded on 1 point', - ); - - expect(vm.$el.querySelectorAll('.report-block-container li').length).toEqual( - resolvedIssues.length, - ); - }); - - describe('toggleCollapsed', () => { - const hiddenCss = { display: 'none' }; - - it('toggles issues', done => { - vm.$el.querySelector('button').click(); - - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse'); - - vm.$el.querySelector('button').click(); - }) - .then(Vue.nextTick) - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand'); - }) - .then(done) - .catch(done.fail); - }); - - it('is always expanded, if always-open is set to true', done => { - vm.alwaysOpen = true; - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('with failed request', () => { - it('should render error indicator', () => { - vm = mountComponent(ReportSection, { - component: '', - status: 'ERROR', - loadingText: 'Loading codeclimate report', - errorText: 'Failed to load codeclimate report', - successText: 'Code quality improved on 1 point and degraded on 1 point', - hasIssues: false, - }); - - expect(vm.$el.textContent.trim()).toEqual('Failed to load codeclimate report'); - }); - }); - - describe('with action buttons passed to the slot', () => { - beforeEach(() => { - vm = mountComponentWithSlots(ReportSection, { - props: { - status: 'SUCCESS', - successText: 'success', - hasIssues: true, - }, - slots: { - actionButtons: ['Action!'], - }, - }); - }); - - it('should render the passed button', () => { - expect(vm.$el.textContent.trim()).toContain('Action!'); - }); - - it('should still render the expand/collapse button', () => { - expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); - }); - }); -}); -- cgit v1.2.1 From de03a8bcb45f11a50bee071e977274c113e7b39a Mon Sep 17 00:00:00 2001 From: Tiger Date: Tue, 26 Mar 2019 17:09:07 +1100 Subject: Detailed status for builds that fail prerequisites Create FailedUnmetPrerequisites status to allow custom messaging for builds that were unable to be queued due to failing to meet prerequisites (eg. failing to create Kubernetes namespace for deployment). --- spec/factories/ci/builds.rb | 5 +++ spec/lib/gitlab/ci/status/build/factory_spec.rb | 29 +++++++++++++++++ .../build/failed_unmet_prerequisites_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb (limited to 'spec') diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 067391c1179..f8c494c159e 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -336,6 +336,11 @@ FactoryBot.define do failure_reason 2 end + trait :prerequisite_failure do + failed + failure_reason 10 + end + trait :with_runner_session do after(:build) do |build| build.build_runner_session(url: 'https://localhost') diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index b379b08ad62..b6231510b91 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -123,6 +123,35 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.action_path).to include 'retry' end end + + context 'when build has unmet prerequisites' do + let(:build) { create(:ci_build, :prerequisite_failure) } + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable, + Gitlab::Ci::Status::Build::FailedUnmetPrerequisites] + end + + it 'fabricates a failed with unmet prerequisites build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::FailedUnmetPrerequisites + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'status_failed' + expect(status.favicon).to eq 'favicon_status_failed' + expect(status.label).to eq 'failed' + expect(status).to have_details + expect(status).to have_action + expect(status.action_title).to include 'Retry' + expect(status.action_path).to include 'retry' + end + end end context 'when build is a canceled' do diff --git a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb new file mode 100644 index 00000000000..a4854bdc6b9 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Status::Build::FailedUnmetPrerequisites do + describe '#illustration' do + subject { described_class.new(double).illustration } + + it { is_expected.to include(:image, :size, :title, :content) } + end + + describe '.matches?' do + let(:build) { create(:ci_build, :created) } + + subject { described_class.matches?(build, double) } + + context 'when build has not failed' do + it { is_expected.to be_falsey } + end + + context 'when build has failed' do + before do + build.drop!(failure_reason) + end + + context 'with unmet prerequisites' do + let(:failure_reason) { :unmet_prerequisites } + + it { is_expected.to be_truthy } + end + + context 'with a different error' do + let(:failure_reason) { :runner_system_failure } + + it { is_expected.to be_falsey } + end + end + end +end -- cgit v1.2.1 From 02b9b5facf0496f7c64a7913cc4f4c8437527f72 Mon Sep 17 00:00:00 2001 From: Tiger Date: Wed, 27 Mar 2019 15:01:35 +1100 Subject: Expose build failure reason We can use this to show more informative error messages with links to documentation etc. --- spec/serializers/build_details_entity_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index f6bd6e9ede4..1edf69dc290 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -112,5 +112,15 @@ describe BuildDetailsEntity do expect(subject['merge_request_path']).to be_nil end end + + context 'when the build has failed' do + let(:build) { create(:ci_build, :created) } + + before do + build.drop!(:unmet_prerequisites) + end + + it { is_expected.to include(failure_reason: 'unmet_prerequisites') } + end end end -- cgit v1.2.1 From 11f2ddf021ef86d481f68ee6a1e09b57fd18dcae Mon Sep 17 00:00:00 2001 From: jerasmus Date: Thu, 21 Mar 2019 08:09:15 +0200 Subject: Display error for unmet prerequisites Added the ability to display an error for unmet prerequisites --- spec/javascripts/jobs/components/job_app_spec.js | 36 +++++++++++++++++++++ .../components/unmet_prerequisites_block_spec.js | 37 ++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js (limited to 'spec') diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index ba5d672f189..6117eb8cff5 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -17,6 +17,7 @@ describe('Job App ', () => { const props = { endpoint: `${gl.TEST_HOST}jobs/123.json`, runnerHelpUrl: 'help/runner', + deploymentHelpUrl: 'help/deployment', runnerSettingsUrl: 'settings/ci-cd/runners', terminalPath: 'jobs/123/terminal', pagePath: `${gl.TEST_HOST}jobs/123`, @@ -253,6 +254,41 @@ describe('Job App ', () => { }); }); + describe('unmet prerequisites block', () => { + it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => { + mock.onGet(props.endpoint).replyOnce( + 200, + Object.assign({}, job, { + status: { + group: 'failed', + icon: 'status_failed', + label: 'failed', + text: 'failed', + details_path: 'path', + illustration: { + content: 'Retry this job in order to create the necessary resources.', + image: 'path', + size: 'svg-430', + title: 'Failed to create resources', + }, + }, + has_trace: false, + unmet_prerequisites: true, + runners: { + available: true, + }, + tags: [], + }), + ); + vm = mountComponentWithStore(Component, { props, store }); + + setTimeout(() => { + expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull(); + done(); + }, 0); + }); + }); + describe('environments block', () => { it('renders environment block when job has environment', done => { mock.onGet(props.endpoint).replyOnce( diff --git a/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js new file mode 100644 index 00000000000..68fcb321214 --- /dev/null +++ b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import component from '~/jobs/components/unmet_prerequisites_block.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Unmet Prerequisites Block Job component', () => { + const Component = Vue.extend(component); + let vm; + const helpPath = '/user/project/clusters/index.html#troubleshooting-failed-deployment-jobs'; + + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: true, + helpPath, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders an alert with the correct message', () => { + const container = vm.$el.querySelector('.js-failed-unmet-prerequisites'); + const alertMessage = + 'This job failed because the necessary resources were not successfully created.'; + + expect(container).not.toBeNull(); + expect(container.innerHTML).toContain(alertMessage); + }); + + it('renders link to help page', () => { + const helpLink = vm.$el.querySelector('.js-help-path'); + + expect(helpLink).not.toBeNull(); + expect(helpLink.innerHTML).toContain('More information'); + expect(helpLink.getAttribute('href')).toEqual(helpPath); + }); +}); -- cgit v1.2.1 From 506a0ee646d6e600a83cc589abc5543166347b06 Mon Sep 17 00:00:00 2001 From: jerasmus Date: Mon, 1 Apr 2019 12:22:10 +0200 Subject: Fix frontend unit tests Fixed the broken frontend unit tests --- spec/javascripts/jobs/components/job_app_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index 6117eb8cff5..cef40117304 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -272,8 +272,8 @@ describe('Job App ', () => { title: 'Failed to create resources', }, }, + failure_reason: 'unmet_prerequisites', has_trace: false, - unmet_prerequisites: true, runners: { available: true, }, -- cgit v1.2.1 From 3be46bdf086c4b11ec8c5eccae8d1c345127f2fc Mon Sep 17 00:00:00 2001 From: Tiger Date: Fri, 29 Mar 2019 10:42:19 +1100 Subject: Split Security Auto DevOps jobs into templates Container Scanning, Dependency Scanning, License Management and SAST parts of Auto DevOps now use the preexisting templates. Auto DevOps and the DAST job template will now use a shared job template instead of maintaining two copies of the job. This also allows Auto DevOps to use custom authentication with DAST. --- spec/lib/gitlab/ci/templates/templates_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index fbbd58280a9..e20a621168c 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe "CI YML Templates" do ABSTRACT_TEMPLATES = %w[Serverless].freeze + PROJECT_DEPENDENT_TEMPLATES = %w[Auto-DevOps].freeze def self.concrete_templates Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template| @@ -20,7 +21,10 @@ describe "CI YML Templates" do describe 'concrete templates with CI/CD jobs' do concrete_templates.each do |template| it "#{template.name} template should be valid" do - expect { Gitlab::Ci::YamlProcessor.new(template.content) } + # Trigger processing of included files + project = create(:project, :test_repo) if PROJECT_DEPENDENT_TEMPLATES.include?(template.name) + + expect { Gitlab::Ci::YamlProcessor.new(template.content, project: project) } .not_to raise_error end end -- cgit v1.2.1 From 22fe2fb4c11c16739a3a1bb97569884db70ac546 Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Tue, 2 Apr 2019 06:49:11 +0000 Subject: Fix group transfer selection possibilities --- spec/helpers/groups_helper_spec.rb | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'spec') diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 91541a16c13..1763c46389a 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -229,4 +229,37 @@ describe GroupsHelper do expect(helper.group_sidebar_links).not_to include(*cross_project_features) end end + + describe 'parent_group_options', :nested_groups do + let(:current_user) { create(:user) } + let(:group) { create(:group, name: 'group') } + let(:group2) { create(:group, name: 'group2') } + + before do + group.add_owner(current_user) + group2.add_owner(current_user) + end + + it 'includes explicitly owned groups except self' do + expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json) + end + + it 'excludes parent group' do + subgroup = create(:group, parent: group2) + + expect(parent_group_options(subgroup)).to eq([{ id: group.id, text: group.human_name }].to_json) + end + + it 'includes subgroups with inherited ownership' do + subgroup = create(:group, parent: group) + + expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }, { id: subgroup.id, text: subgroup.human_name }].to_json) + end + + it 'excludes own subgroups' do + create(:group, parent: group2) + + expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json) + end + end end -- cgit v1.2.1 From 6473f6976124f047a56f9d004e3d0746ab493b29 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Tue, 2 Apr 2019 06:54:54 +0000 Subject: Add new field to Clusters table Adds boolean field 'managed' to clusters table. This new column will be used to distinguish those clusters that are automatically managed by gitlab. Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/56557 --- spec/factories/clusters/clusters.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'spec') diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb index 1cc3c0e03d8..97405ec7c58 100644 --- a/spec/factories/clusters/clusters.rb +++ b/spec/factories/clusters/clusters.rb @@ -3,6 +3,7 @@ FactoryBot.define do user name 'test-cluster' cluster_type :project_type + managed true trait :instance do cluster_type { Clusters::Cluster.cluster_types[:instance_type] } @@ -63,5 +64,9 @@ FactoryBot.define do trait :with_domain do domain 'example.com' end + + trait :user_managed do + managed false + end end end -- cgit v1.2.1 From a466d97e62a89b320713da44d67d452284ad8282 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 2 Apr 2019 07:48:35 +0000 Subject: Rake task for removing exif from uploads Adds a rake task which can be used for removing EXIF data from existing uploads. --- spec/lib/gitlab/sanitizers/exif_spec.rb | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 spec/lib/gitlab/sanitizers/exif_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb new file mode 100644 index 00000000000..bd5f330c7a1 --- /dev/null +++ b/spec/lib/gitlab/sanitizers/exif_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +describe Gitlab::Sanitizers::Exif do + let(:sanitizer) { described_class.new } + + describe '#batch_clean' do + context 'with image uploads' do + let!(:uploads) { create_list(:upload, 3, :with_file, :issuable_upload) } + + it 'processes all uploads if range ID is not set' do + expect(sanitizer).to receive(:clean).exactly(3).times + + sanitizer.batch_clean + end + + it 'processes only uploads in the selected range' do + expect(sanitizer).to receive(:clean).once + + sanitizer.batch_clean(start_id: uploads[1].id, stop_id: uploads[1].id) + end + + it 'pauses if sleep_time is set' do + expect(sanitizer).to receive(:sleep).exactly(3).times.with(1.second) + expect(sanitizer).to receive(:clean).exactly(3).times + + sanitizer.batch_clean(sleep_time: 1) + end + end + + it 'filters only jpg/tiff images' do + create(:upload, path: 'filename.jpg') + create(:upload, path: 'filename.jpeg') + create(:upload, path: 'filename.JPG') + create(:upload, path: 'filename.tiff') + create(:upload, path: 'filename.TIFF') + create(:upload, path: 'filename.png') + create(:upload, path: 'filename.txt') + + expect(sanitizer).to receive(:clean).exactly(5).times + sanitizer.batch_clean + end + end + + describe '#clean' do + let(:uploader) { create(:upload, :with_file, :issuable_upload).build_uploader } + + context "no dry run" do + it "removes exif from the image" do + uploader.store!(fixture_file_upload('spec/fixtures/rails_sample.jpg')) + + original_upload = uploader.upload + expected_args = ["exiftool", "-all=", "-tagsFromFile", "@", *Gitlab::Sanitizers::Exif::EXCLUDE_PARAMS, "--IPTC:all", "--XMP-iptcExt:all", kind_of(String)] + + expect(sanitizer).to receive(:extra_tags).and_return(["", 0]) + expect(sanitizer).to receive(:exec_remove_exif!).once.and_call_original + expect(uploader).to receive(:store!).and_call_original + expect(Gitlab::Popen).to receive(:popen).with(expected_args) do |args| + File.write("#{args.last}_original", "foo") if args.last.start_with?(Dir.tmpdir) + + [expected_args, 0] + end + + sanitizer.clean(uploader, dry_run: false) + + expect(uploader.upload.id).not_to eq(original_upload.id) + expect(uploader.upload.path).to eq(original_upload.path) + end + + it "ignores image without exif" do + expected_args = ["exiftool", "-all", "-j", "-sort", "--IPTC:all", "--XMP-iptcExt:all", kind_of(String)] + + expect(Gitlab::Popen).to receive(:popen).with(expected_args).and_return(["[{}]", 0]) + expect(sanitizer).not_to receive(:exec_remove_exif!) + expect(uploader).not_to receive(:store!) + + sanitizer.clean(uploader, dry_run: false) + end + + it "raises an error if the exiftool fails with an error" do + expect(Gitlab::Popen).to receive(:popen).and_return(["error", 1]) + + expect { sanitizer.clean(uploader, dry_run: false) }.to raise_exception(RuntimeError, "failed to get exif tags: error") + end + end + + context "dry run" do + it "doesn't change the image" do + expect(sanitizer).to receive(:extra_tags).and_return({ 'foo' => 'bar' }) + expect(sanitizer).not_to receive(:exec_remove_exif!) + expect(uploader).not_to receive(:store!) + + sanitizer.clean(uploader, dry_run: true) + end + end + end + + describe "#extra_tags" do + it "returns a list of keys for exif file" do + tags = '[{ + "DigitalSourceType": "some source", + "ImageHeight": 654 + }]' + + expect(Gitlab::Popen).to receive(:popen).and_return([tags, 0]) + + expect(sanitizer.extra_tags('filename')).not_to be_empty + end + + it "returns an empty list for file with only whitelisted and ignored tags" do + tags = '[{ + "ImageHeight": 654, + "Megapixels": 0.641 + }]' + + expect(Gitlab::Popen).to receive(:popen).and_return([tags, 0]) + + expect(sanitizer.extra_tags('some file')).to be_empty + end + end +end -- cgit v1.2.1 From 7c26c47cb55613babcbbbd329ffec1ab4b79378f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 1 Apr 2019 18:49:03 +0200 Subject: [CE] Reduce diff with EE in spec/models/concerns/prometheus_adapter_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/models/concerns/prometheus_adapter_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb index 7148261b1e4..db20a8b4701 100644 --- a/spec/models/concerns/prometheus_adapter_spec.rb +++ b/spec/models/concerns/prometheus_adapter_spec.rb @@ -6,14 +6,15 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do include PrometheusHelpers include ReactiveCachingHelpers - class TestClass - include PrometheusAdapter - end - let(:project) { create(:prometheus_project) } let(:service) { project.prometheus_service } - let(:described_class) { TestClass } + let(:described_class) do + Class.new do + include PrometheusAdapter + end + end + let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery } describe '#query' do -- cgit v1.2.1 From 590129a89238c3683aa8ffa41b72624fd786cbf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 1 Apr 2019 19:41:36 +0200 Subject: [CE] Reduce diff with EE in spec/models/environment_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/models/environment_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 448ed35cb1e..ca5eed60b56 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -592,7 +592,9 @@ describe Environment do shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do it 'returns the terminals from the deployment service' do - expect(project.deployment_platform) + deployment_platform_target = Gitlab.ee? ? environment : project + + expect(deployment_platform_target.deployment_platform) .to receive(:terminals).with(environment) .and_return(:fake_terminals) -- cgit v1.2.1 From 0f369d145b568fc1e1b6034b42a4b3a6b3652d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 1 Apr 2019 19:16:48 +0200 Subject: [CE] Reduce diff with EE in spec/models/project_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/models/project_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2158d3cf3e6..33e514cd7b9 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2143,6 +2143,15 @@ describe Project do expect(project.add_import_job).to eq(import_jid) end + + context 'without repository' do + it 'schedules RepositoryImportWorker' do + project = create(:project, import_url: generate(:url)) + + expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid) + expect(project.add_import_job).to eq(import_jid) + end + end end context 'not forked' do -- cgit v1.2.1 From f3ad4ecb73f66487191160505578d236d9361d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 1 Apr 2019 19:20:31 +0200 Subject: [CE] Reduce diff with EE in spec/models/project_team_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/models/project_team_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index a2d4fad9292..77c88a04cde 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -195,6 +195,30 @@ describe ProjectTeam do end end + describe '#add_users' do + let(:user1) { create(:user) } + let(:user2) { create(:user) } + let(:project) { create(:project) } + + it 'add the given users to the team' do + project.team.add_users([user1, user2], :reporter) + + expect(project.team.reporter?(user1)).to be(true) + expect(project.team.reporter?(user2)).to be(true) + end + end + + describe '#add_user' do + let(:user) { create(:user) } + let(:project) { create(:project) } + + it 'add the given user to the team' do + project.team.add_user(user, :reporter) + + expect(project.team.reporter?(user)).to be(true) + end + end + describe "#human_max_access" do it 'returns Maintainer role' do user = create(:user) -- cgit v1.2.1 From 8708c720c5d9a08160658c2ee9fea9796568a1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 1 Apr 2019 19:24:21 +0200 Subject: [CE] Reduce diff with EE in spec/models/project_wiki_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/models/project_wiki_spec.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'spec') diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 7ea67f31534..2525a6aebe0 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -1,4 +1,3 @@ -# coding: utf-8 # frozen_string_literal: true require "spec_helper" -- cgit v1.2.1 From a04e1d5e32f7ae1740ba8b9daaf3f71a5048126c Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 2 Apr 2019 14:07:11 +0700 Subject: Backport EE change This is for merge request pipelines --- spec/models/ci/pipeline_spec.rb | 42 ---------------------- .../merge_request_widget_entity_spec.rb | 4 +++ spec/serializers/pipeline_entity_spec.rb | 10 ++++++ 3 files changed, 14 insertions(+), 42 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e76cf376ed1..83b0f172f03 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -322,48 +322,6 @@ describe Ci::Pipeline, :mailer do end end - describe '.mergeable_merge_request_pipelines' do - subject { described_class.mergeable_merge_request_pipelines(merge_request) } - - let!(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) - end - - let(:merge_request) { create(:merge_request) } - let(:target_sha) { merge_request.target_branch_sha } - - it 'returns mergeable merge pipelines' do - is_expected.to eq([pipeline]) - end - - context 'when target sha does not point the head of the target branch' do - let(:target_sha) { merge_request.diff_head_sha } - - it 'returns empty array' do - is_expected.to be_empty - end - end - end - - describe '#mergeable_merge_request_pipeline?' do - subject { pipeline.mergeable_merge_request_pipeline? } - - let!(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha) - end - - let(:merge_request) { create(:merge_request) } - let(:target_sha) { merge_request.target_branch_sha } - - it { is_expected.to be_truthy } - - context 'when target sha does not point the head of the target branch' do - let(:target_sha) { merge_request.diff_head_sha } - - it { is_expected.to be_falsy } - end - end - describe '#merge_request_ref?' do subject { pipeline.merge_request_ref? } diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 727fd8951f2..0e99ef38d2f 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -13,6 +13,10 @@ describe MergeRequestWidgetEntity do described_class.new(resource, request: request).as_json end + it 'has the latest sha of the target branch' do + is_expected.to include(:target_branch_sha) + end + describe 'source_project_full_path' do it 'includes the full path of the source project' do expect(subject[:source_project_full_path]).to be_present diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 1d992e8a483..dba7fd91747 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -143,6 +143,11 @@ describe PipelineEntity do expect(subject[:flags][:detached_merge_request_pipeline]).to be_truthy end + it 'does not expose source sha and target sha' do + expect(subject[:source_sha]).to be_nil + expect(subject[:target_sha]).to be_nil + end + context 'when user is a developer' do before do project.add_developer(user) @@ -189,6 +194,11 @@ describe PipelineEntity do it 'makes atached flag true' do expect(subject[:flags][:merge_request_pipeline]).to be_truthy end + + it 'exposes source sha and target sha' do + expect(subject[:source_sha]).to be_present + expect(subject[:target_sha]).to be_present + end end end end -- cgit v1.2.1 From e540ae798b812bf4b6068fbb0e7f22ade7d5f309 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 2 Apr 2019 10:50:58 +0100 Subject: Remove groups destroy race condition spec This was removed in the below MR in EE only, as it was causing failures related to Geo. https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3999 --- spec/services/groups/destroy_service_spec.rb | 38 ---------------------------- 1 file changed, 38 deletions(-) (limited to 'spec') diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index d80d0f5a8a8..98114ea4919 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -82,44 +82,6 @@ describe Groups::DestroyService do expect(Group.unscoped.count).to eq(2) end end - - context 'potential race conditions' do - context "when the `GroupDestroyWorker` task runs immediately" do - it "deletes the group" do - # Commit the contents of this spec's transaction so far - # so subsequent db connections can see it. - # - # DO NOT REMOVE THIS LINE, even if you see a WARNING with "No - # transaction is currently in progress". Without this, this - # spec will always be green, since the group created in setup - # cannot be seen by any other connections / threads in this spec. - Group.connection.commit_db_transaction - - group_record = run_with_new_database_connection do |conn| - conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first - end - - expect(group_record).not_to be_nil - - # Execute the contents of `GroupDestroyWorker` in a separate thread, to - # simulate data manipulation by the Sidekiq worker (different database - # connection / transaction). - expect(GroupDestroyWorker).to receive(:perform_async).and_wrap_original do |m, group_id, user_id| - Thread.new { m[group_id, user_id] }.join(5) - end - - # Kick off the initial group destroy in a new thread, so that - # it doesn't share this spec's database transaction. - Thread.new { described_class.new(group, user).async_execute }.join(5) - - group_record = run_with_new_database_connection do |conn| - conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first - end - - expect(group_record).to be_nil - end - end - end end describe 'synchronous delete' do -- cgit v1.2.1 From 0e66357a3e411b44647cc4d4ca10c2a19b9a3b55 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 2 Apr 2019 10:26:53 +0000 Subject: Monitor GraphQL with Prometheus Extends graphql's platform tracing class to observe duration of graphql methods. In graphql 1.8.11 is added prometheus class but it's not very useful for us because it uses prometheus_exporter to export results. --- spec/lib/gitlab/graphql/tracing_spec.rb | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 spec/lib/gitlab/graphql/tracing_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/tracing_spec.rb b/spec/lib/gitlab/graphql/tracing_spec.rb new file mode 100644 index 00000000000..6bae737d0f6 --- /dev/null +++ b/spec/lib/gitlab/graphql/tracing_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Tracing do + let!(:graphql_duration_seconds) { double('Gitlab::Metrics::NullMetric') } + + before do + allow(Gitlab::Metrics) + .to receive(:histogram) + .with(:graphql_duration_seconds, 'GraphQL execution time') + .and_return(graphql_duration_seconds) + end + + it 'updates graphql histogram with expected labels' do + query = 'query { users { id } }' + + expect_metric('graphql.lex', 'lex') + expect_metric('graphql.parse', 'parse') + expect_metric('graphql.validate', 'validate') + expect_metric('graphql.analyze', 'analyze_multiplex') + expect_metric('graphql.execute', 'execute_query_lazy') + expect_metric('graphql.execute', 'execute_multiplex') + + GitlabSchema.execute(query) + end + + private + + def expect_metric(platform_key, key) + expect(graphql_duration_seconds) + .to receive(:observe) + .with({ platform_key: platform_key, key: key }, be > 0.0) + end +end -- cgit v1.2.1 From 351b39f172bf77f4eb0e84fb2e5a71af9fe72348 Mon Sep 17 00:00:00 2001 From: Igor Date: Tue, 2 Apr 2019 11:01:07 +0000 Subject: Backport splitting approval changes from CE Author and committers approvals are split in EE This commit provides backports for those changes This reverts commit 886f00bcba23d67e2c86591c2eb5359ef457a2f9. --- spec/models/commit_collection_spec.rb | 14 +++++++------- spec/models/merge_request_spec.rb | 27 ++++++--------------------- 2 files changed, 13 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb index 8a7fcb39fe2..0bdf83fa90f 100644 --- a/spec/models/commit_collection_spec.rb +++ b/spec/models/commit_collection_spec.rb @@ -14,26 +14,26 @@ describe CommitCollection do end end - describe '.authors' do + describe '.committers' do it 'returns a relation of users when users are found' do - user = create(:user, email: commit.author_email.upcase) + user = create(:user, email: commit.committer_email.upcase) collection = described_class.new(project, [commit]) - expect(collection.authors).to contain_exactly(user) + expect(collection.committers).to contain_exactly(user) end - it 'returns empty array when authors cannot be found' do + it 'returns empty array when committers cannot be found' do collection = described_class.new(project, [commit]) - expect(collection.authors).to be_empty + expect(collection.committers).to be_empty end it 'excludes authors of merge commits' do commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98") - create(:user, email: commit.author_email.upcase) + create(:user, email: commit.committer_email.upcase) collection = described_class.new(project, [commit]) - expect(collection.authors).to be_empty + expect(collection.committers).to be_empty end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index fb32f72e2de..892fdc4e4e9 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -476,7 +476,6 @@ describe MergeRequest do it 'does not cache issues from external trackers' do issue = ExternalIssue.new('JIRA-123', subject.project) commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") - allow(subject).to receive(:commits).and_return([commit]) expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to raise_error @@ -1065,31 +1064,17 @@ describe MergeRequest do end end - describe '#commit_authors' do - it 'returns all the authors of every commit in the merge request' do - users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| - create(:user, email: email) - end - - expect(subject.commit_authors).to match_array(users) - end - - it 'returns an empty array if no author is associated with a user' do - expect(subject.commit_authors).to be_empty - end - end - - describe '#authors' do - it 'returns a list with all the commit authors in the merge request and author' do - users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| + describe '#committers' do + it 'returns all the committers of every commit in the merge request' do + users = subject.commits.without_merge_commits.map(&:committer_email).uniq.map do |email| create(:user, email: email) end - expect(subject.authors).to match_array([subject.author, *users]) + expect(subject.committers).to match_array(users) end - it 'returns only the author if no committer is associated with a user' do - expect(subject.authors).to contain_exactly(subject.author) + it 'returns an empty array if no committer is associated with a user' do + expect(subject.committers).to be_empty end end -- cgit v1.2.1 From e37383d45395c0c7cefc1dda3be12bd4c2d6cc4f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Mar 2019 09:23:51 -0700 Subject: Cache FindCommit results in pipelines view For each pipeline, the controller will call `Pipeline#latest?` to determine if the pipeline's ref is the latest for that branch. Since it's likely that the same branches are being used in each pipeline, we can reduce Gitaly overhead by caching the results of the FindCommit call. --- spec/controllers/projects/pipelines_controller_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index ece8532cb84..b64ae552efc 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -28,6 +28,8 @@ describe Projects::PipelinesController do end it 'returns serialized pipelines', :request_store do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + queries = ActiveRecord::QueryRecorder.new do get_pipelines_index_json end -- cgit v1.2.1 From eebf24ead51a28863126f0b144e4fcdc5a6b2916 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 2 Apr 2019 13:24:36 +0100 Subject: Fixes duplicate diff too large error messages Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59802 --- spec/javascripts/diffs/components/diff_file_spec.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index d9b298e84da..ef4589ada48 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -141,18 +141,16 @@ describe('DiffFile', () => { it('should have too large warning and blob link', done => { const BLOB_LINK = '/file/view/path'; vm.file.viewer.error = diffViewerErrors.too_large; + vm.file.viewer.error_message = + 'This source diff could not be displayed because it is too large'; vm.file.view_path = BLOB_LINK; + vm.file.renderIt = true; vm.$nextTick(() => { expect(vm.$el.innerText).toContain( 'This source diff could not be displayed because it is too large', ); - expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined(); - expect( - vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK), - ).toBeGreaterThan(-1); - done(); }); }); -- cgit v1.2.1 From 35b9274f12f29524855237bfdcd864497d62de95 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 2 Apr 2019 13:20:26 +0000 Subject: Stop calling UnlinkRepositoryFromObjectPool RPC Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/59777. In earlier iterations of our implementation of Git object deduplication we thought we would be making extensive use of Git remotes in pool repositories in the future, and that we should manage these remotes carefully from the start. We now expect we only care about one remote, namely the source project. The other remotes are there only for forensic purposes. Before this MR we tried to also remove pool remotes when member projects got deleted, with the UnlinkRepositoryFromObjectPool RPC. This is fragile when there are race conditions (see https://gitlab.com/gitlab-org/gitaly/issues/1568#note_153955926). We have spent some time making this RPC less fragile in https://gitlab.com/gitlab-org/gitaly/merge_requests/1151 but looking at this problem again, I think we should just stop calling it. --- spec/models/pool_repository_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb index 112d4ab56fc..e5a3a3ad66e 100644 --- a/spec/models/pool_repository_spec.rb +++ b/spec/models/pool_repository_spec.rb @@ -24,14 +24,14 @@ describe PoolRepository do end end - describe '#unlink_repository' do + describe '#mark_obsolete_if_last' do let(:pool) { create(:pool_repository, :ready) } context 'when the last member leaves' do it 'schedules pool removal' do expect(::ObjectPool::DestroyWorker).to receive(:perform_async).with(pool.id).and_call_original - pool.unlink_repository(pool.source_project.repository) + pool.mark_obsolete_if_last(pool.source_project.repository) end end @@ -40,7 +40,7 @@ describe PoolRepository do create(:project, :repository, pool_repository: pool) expect(::ObjectPool::DestroyWorker).not_to receive(:perform_async).with(pool.id) - pool.unlink_repository(pool.source_project.repository) + pool.mark_obsolete_if_last(pool.source_project.repository) end end end -- cgit v1.2.1 From 6766a0a144bd07441b4593d25235924a14df0a91 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Mon, 25 Mar 2019 16:20:49 +0800 Subject: Download a folder from repository Add `GetArchiveRequest` to git-archive params. Modifies `Git::Repository#archive_metadata` to append `path` to `ArchivePrefix` so it'll not hit the cache of repository archive when it already exists. --- spec/lib/gitlab/git/repository_spec.rb | 11 ++++++++++- spec/lib/gitlab/workhorse_spec.rb | 31 +++++++++++++++++++------------ 2 files changed, 29 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 8ba6862392c..fc8f590068a 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -152,13 +152,14 @@ describe Gitlab::Git::Repository, :seed_helper do let(:append_sha) { true } let(:ref) { 'master' } let(:format) { nil } + let(:path) { nil } let(:expected_extension) { 'tar.gz' } let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" } let(:expected_path) { File.join(storage_path, cache_key, expected_filename) } let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" } - subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha) } + subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha, path: path) } it 'sets CommitId to the commit SHA' do expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID) @@ -176,6 +177,14 @@ describe Gitlab::Git::Repository, :seed_helper do expect(metadata['ArchivePath']).to eq(expected_path) end + context 'path is set' do + let(:path) { 'foo/bar' } + + it 'appends the path to the prefix' do + expect(metadata['ArchivePrefix']).to eq("#{expected_prefix}-foo-bar") + end + end + context 'append_sha varies archive path and filename' do where(:append_sha, :ref, :expected_prefix) do sha = SeedRepo::LastCommit::ID diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index d88086b01b1..fed7834e2a9 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -16,20 +16,12 @@ describe Gitlab::Workhorse do let(:ref) { 'master' } let(:format) { 'zip' } let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path } - let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) } - let(:gitaly_params) do - base_params.merge( - 'GitalyServer' => { - 'address' => Gitlab::GitalyClient.address(project.repository_storage), - 'token' => Gitlab::GitalyClient.token(project.repository_storage) - }, - 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys - ) - end + let(:path) { 'some/path' } + let(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: nil, path: path) } let(:cache_disabled) { false } subject do - described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil) + described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil, path: path) end before do @@ -41,7 +33,22 @@ describe Gitlab::Workhorse do expect(key).to eq('Gitlab-Workhorse-Send-Data') expect(command).to eq('git-archive') - expect(params).to include(gitaly_params) + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'ArchivePath' => metadata['ArchivePath'], + 'GetArchiveRequest' => Base64.urlsafe_encode64( + Gitaly::GetArchiveRequest.new( + repository: repository.gitaly_repository, + commit_id: metadata['CommitId'], + prefix: metadata['ArchivePrefix'], + format: Gitaly::GetArchiveRequest::Format::ZIP, + path: path + ).to_proto + ) + }.deep_stringify_keys) end context 'when archive caching is disabled' do -- cgit v1.2.1 From c0eb5cd7a24544b9dea8ace77e15d79995475b85 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Mon, 1 Apr 2019 09:38:31 -0800 Subject: Make the dropdown match the design Regenerate locale.pot as well --- spec/features/projects/branches/download_buttons_spec.rb | 2 +- spec/features/projects/files/download_buttons_spec.rb | 2 +- spec/features/projects/show/download_buttons_spec.rb | 3 +-- spec/features/projects/tags/download_buttons_spec.rb | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index c8dc72a34ec..3e75890725e 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -35,7 +35,7 @@ describe 'Download buttons in branches page' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build') - expect(page).to have_link "Download '#{build.name}'", href: href + expect(page).to have_link build.name, href: href end end end diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb index 03cb3530e2b..111972a6b00 100644 --- a/spec/features/projects/files/download_buttons_spec.rb +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -30,7 +30,7 @@ describe 'Projects > Files > Download buttons in files tree' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build') - expect(page).to have_link "Download '#{build.name}'", href: href + expect(page).to have_link build.name, href: href end end end diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb index 3a2dcc5aa55..fee5f8001b0 100644 --- a/spec/features/projects/show/download_buttons_spec.rb +++ b/spec/features/projects/show/download_buttons_spec.rb @@ -35,11 +35,10 @@ describe 'Projects > Show > Download buttons' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build') - expect(page).to have_link "Download '#{build.name}'", href: href + expect(page).to have_link build.name, href: href end it 'download links have download attribute' do - expect(page).to have_selector('a', text: 'Download') page.all('a', text: 'Download').each do |link| expect(link[:download]).to eq '' end diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index fbfd8cee7aa..4c8ec53836a 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -36,7 +36,7 @@ describe 'Download buttons in tags page' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build') - expect(page).to have_link "Download '#{build.name}'", href: href + expect(page).to have_link build.name, href: href end end end -- cgit v1.2.1 From 6b24eb9ce6ae42c161ded782971cd72b8836b56f Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Tue, 2 Apr 2019 17:22:29 +0300 Subject: Remove already defined shared example Removing the time traking shared example as it was actually moved under time_tracking_quick_action_shared_examples.rb filename to a different path as part of spec refactorign for quick action https://gitlab.com/gitlab-org/gitlab-ce/issues/50199 https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26095 --- .../time_tracking_shared_examples.rb | 85 ---------------------- 1 file changed, 85 deletions(-) delete mode 100644 spec/support/shared_examples/time_tracking_shared_examples.rb (limited to 'spec') diff --git a/spec/support/shared_examples/time_tracking_shared_examples.rb b/spec/support/shared_examples/time_tracking_shared_examples.rb deleted file mode 100644 index 909d4e2ee8d..00000000000 --- a/spec/support/shared_examples/time_tracking_shared_examples.rb +++ /dev/null @@ -1,85 +0,0 @@ -shared_examples 'issuable time tracker' do - it 'renders the sidebar component empty state' do - page.within '.time-tracking-no-tracking-pane' do - expect(page).to have_content 'No estimate or time spent' - end - end - - it 'updates the sidebar component when estimate is added' do - submit_time('/estimate 3w 1d 1h') - - wait_for_requests - page.within '.time-tracking-estimate-only-pane' do - expect(page).to have_content '3w 1d 1h' - end - end - - it 'updates the sidebar component when spent is added' do - submit_time('/spend 3w 1d 1h') - - wait_for_requests - page.within '.time-tracking-spend-only-pane' do - expect(page).to have_content '3w 1d 1h' - end - end - - it 'shows the comparison when estimate and spent are added' do - submit_time('/estimate 3w 1d 1h') - submit_time('/spend 3w 1d 1h') - - wait_for_requests - page.within '.time-tracking-comparison-pane' do - expect(page).to have_content '3w 1d 1h' - end - end - - it 'updates the sidebar component when estimate is removed' do - submit_time('/estimate 3w 1d 1h') - submit_time('/remove_estimate') - - page.within '.time-tracking-component-wrap' do - expect(page).to have_content 'No estimate or time spent' - end - end - - it 'updates the sidebar component when spent is removed' do - submit_time('/spend 3w 1d 1h') - submit_time('/remove_time_spent') - - page.within '.time-tracking-component-wrap' do - expect(page).to have_content 'No estimate or time spent' - end - end - - it 'shows the help state when icon is clicked' do - page.within '.time-tracking-component-wrap' do - find('.help-button').click - expect(page).to have_content 'Track time with quick actions' - expect(page).to have_content 'Learn more' - end - end - - it 'hides the help state when close icon is clicked' do - page.within '.time-tracking-component-wrap' do - find('.help-button').click - find('.close-help-button').click - - expect(page).not_to have_content 'Track time with quick actions' - expect(page).not_to have_content 'Learn more' - end - end - - it 'displays the correct help url' do - page.within '.time-tracking-component-wrap' do - find('.help-button').click - - expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md') - end - end -end - -def submit_time(quick_action) - fill_in 'note[note]', with: quick_action - find('.js-comment-submit-button').click - wait_for_requests -end -- cgit v1.2.1 From 0e5dbaf87f4f6f511eee469139e57d896164846c Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Thu, 21 Mar 2019 12:08:15 +0800 Subject: Do not show system notes on commits in the MR page --- spec/models/merge_request_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 892fdc4e4e9..6f34ef9c1bc 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -805,6 +805,14 @@ describe MergeRequest do expect(merge_request.commits).not_to be_empty expect(merge_request.related_notes.count).to eq(3) end + + it "excludes system notes for commits" do + system_note = create(:note_on_commit, :system, commit_id: merge_request.commits.first.id, + project: merge_request.project) + + expect(merge_request.related_notes.count).to eq(2) + expect(merge_request.related_notes).not_to include(system_note) + end end describe '#for_fork?' do -- cgit v1.2.1 From 3bc30185180cbb5d7c8e026f6e7f1826617358a3 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Tue, 2 Apr 2019 17:58:52 +0200 Subject: Fix quick actions add label name middle word overlaps Fixes quick actions add label when adding a label which name middle word overlaps with another label name: for example adding "A B C" when also label "B" exists. With the fix only the label "A B C" is correctly added, previously also the label "B" was added due to the middle word overlaps. --- .../quick_actions/interpret_service_spec.rb | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'spec') diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 8b0f9c8ade2..c7e5cca324f 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -10,6 +10,7 @@ describe QuickActions::InterpretService do let(:milestone) { create(:milestone, project: project, title: '9.10') } let(:commit) { create(:commit, project: project) } let(:inprogress) { create(:label, project: project, title: 'In Progress') } + let(:helmchart) { create(:label, project: project, title: 'Helm Chart Registry') } let(:bug) { create(:label, project: project, title: 'Bug') } let(:note) { build(:note, commit_id: merge_request.diff_head_sha) } let(:service) { described_class.new(project, developer) } @@ -94,6 +95,26 @@ describe QuickActions::InterpretService do end end + shared_examples 'multiword label name starting without ~' do + it 'fetches label ids and populates add_label_ids if content contains /label' do + helmchart # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [helmchart.id]) + end + end + + shared_examples 'label name is included in the middle of another label name' do + it 'ignores the sublabel when the content contains the includer label name' do + helmchart # populate the label + create(:label, project: project, title: 'Chart') + + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [helmchart.id]) + end + end + shared_examples 'unlabel command' do it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do issuable.update!(label_ids: [inprogress.id]) # populate the label @@ -624,6 +645,26 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end + it_behaves_like 'multiword label name starting without ~' do + let(:content) { %(/label "#{helmchart.title}") } + let(:issuable) { issue } + end + + it_behaves_like 'multiword label name starting without ~' do + let(:content) { %(/label "#{helmchart.title}") } + let(:issuable) { merge_request } + end + + it_behaves_like 'label name is included in the middle of another label name' do + let(:content) { %(/label ~"#{helmchart.title}") } + let(:issuable) { issue } + end + + it_behaves_like 'label name is included in the middle of another label name' do + let(:content) { %(/label ~"#{helmchart.title}") } + let(:issuable) { merge_request } + end + it_behaves_like 'unlabel command' do let(:content) { %(/unlabel ~"#{inprogress.title}") } let(:issuable) { issue } -- cgit v1.2.1 From 0c45e268a1523797712380eca479e18e5b59cc2e Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 2 Apr 2019 20:05:56 +0000 Subject: Revert "Merge branch 'jprovazn-graphql-prometheus' into 'master'" This reverts merge request !26569 --- spec/lib/gitlab/graphql/tracing_spec.rb | 35 --------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 spec/lib/gitlab/graphql/tracing_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/tracing_spec.rb b/spec/lib/gitlab/graphql/tracing_spec.rb deleted file mode 100644 index 6bae737d0f6..00000000000 --- a/spec/lib/gitlab/graphql/tracing_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Graphql::Tracing do - let!(:graphql_duration_seconds) { double('Gitlab::Metrics::NullMetric') } - - before do - allow(Gitlab::Metrics) - .to receive(:histogram) - .with(:graphql_duration_seconds, 'GraphQL execution time') - .and_return(graphql_duration_seconds) - end - - it 'updates graphql histogram with expected labels' do - query = 'query { users { id } }' - - expect_metric('graphql.lex', 'lex') - expect_metric('graphql.parse', 'parse') - expect_metric('graphql.validate', 'validate') - expect_metric('graphql.analyze', 'analyze_multiplex') - expect_metric('graphql.execute', 'execute_query_lazy') - expect_metric('graphql.execute', 'execute_multiplex') - - GitlabSchema.execute(query) - end - - private - - def expect_metric(platform_key, key) - expect(graphql_duration_seconds) - .to receive(:observe) - .with({ platform_key: platform_key, key: key }, be > 0.0) - end -end -- cgit v1.2.1 From 2378dde2e2fa0de41f4f578e56209dc96b5a82c1 Mon Sep 17 00:00:00 2001 From: Donald Cook Date: Tue, 2 Apr 2019 20:23:23 +0000 Subject: Remove non-functional add issue button on welcome list --- spec/javascripts/boards/components/board_spec.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index 6e6b3e6950b..d08ee41802b 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -103,4 +103,18 @@ describe('Board component', () => { }) .catch(done.fail); }); + + it('does render add issue button', () => { + expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull(); + }); + + it('does not render add issue button when list type is blank', done => { + vm.list.type = 'blank'; + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.issue-count-badge-add-button')).toBeNull(); + + done(); + }); + }); }); -- cgit v1.2.1 From 4e5fc1949fb86eed92f7af6a2d98b55286d9b0b4 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Fri, 29 Mar 2019 16:02:55 +0100 Subject: Show Download diff links in closed MRs Download MR diffs links were only available for open MRs, however it may still be useful for closed MRs. --- .../components/mr_widget_header_spec.js | 32 +++++++++------------- 1 file changed, 13 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js index 02c476f2871..cd77b0ab815 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js @@ -15,6 +15,16 @@ describe('MRWidgetHeader', () => { gon.relative_url_root = ''; }); + const expectDownloadDropdownItems = () => { + const downloadEmailPatchesEl = vm.$el.querySelector('.js-download-email-patches'); + const downloadPlainDiffEl = vm.$el.querySelector('.js-download-plain-diff'); + + expect(downloadEmailPatchesEl.textContent.trim()).toEqual('Email patches'); + expect(downloadEmailPatchesEl.getAttribute('href')).toEqual('/mr/email-patches'); + expect(downloadPlainDiffEl.textContent.trim()).toEqual('Plain diff'); + expect(downloadPlainDiffEl.getAttribute('href')).toEqual('/mr/plainDiffPath'); + }; + describe('computed', () => { describe('shouldShowCommitsBehindText', () => { it('return true when there are divergedCommitsCount', () => { @@ -207,21 +217,7 @@ describe('MRWidgetHeader', () => { }); it('renders download dropdown with links', () => { - expect(vm.$el.querySelector('.js-download-email-patches').textContent.trim()).toEqual( - 'Email patches', - ); - - expect(vm.$el.querySelector('.js-download-email-patches').getAttribute('href')).toEqual( - '/mr/email-patches', - ); - - expect(vm.$el.querySelector('.js-download-plain-diff').textContent.trim()).toEqual( - 'Plain diff', - ); - - expect(vm.$el.querySelector('.js-download-plain-diff').getAttribute('href')).toEqual( - '/mr/plainDiffPath', - ); + expectDownloadDropdownItems(); }); }); @@ -250,10 +246,8 @@ describe('MRWidgetHeader', () => { expect(button).toEqual(null); }); - it('does not render download dropdown with links', () => { - expect(vm.$el.querySelector('.js-download-email-patches')).toEqual(null); - - expect(vm.$el.querySelector('.js-download-plain-diff')).toEqual(null); + it('renders download dropdown with links', () => { + expectDownloadDropdownItems(); }); }); -- cgit v1.2.1 From d17cce6aedf67139275362481c5b7fdeebf1b6a3 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 3 Apr 2019 01:48:17 +0100 Subject: Only execute system hooks once when pushing tags --- spec/services/git/tag_push_service_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb index e151db5827f..2d960fc9f08 100644 --- a/spec/services/git/tag_push_service_spec.rb +++ b/spec/services/git/tag_push_service_spec.rb @@ -31,6 +31,20 @@ describe Git::TagPushService do end end + describe 'System Hooks' do + let!(:push_data) { service.tap(&:execute).push_data } + + it "executes system hooks after pushing a tag" do + expect_next_instance_of(SystemHooksService) do |system_hooks_service| + expect(system_hooks_service) + .to receive(:execute_hooks) + .with(push_data, :tag_push_hooks) + end + + service.execute + end + end + describe "Pipelines" do subject { service.execute } -- cgit v1.2.1 From 8207f7877fea6987cbd8ef26e6f01feca6608bd2 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Mon, 4 Mar 2019 15:30:32 +1300 Subject: GraphQL Type authorization Enables authorizations to be defined on GraphQL Types. module Types class ProjectType < BaseObject authorize :read_project end end If a field has authorizations defined on it, and the return type of the field also has authorizations defined on it. then all of the combined permissions in the authorizations will be checked and must pass. Connection fields are checked by "digging" to find the type class of the "node" field in the expected location of edges->node. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/54417 --- spec/graphql/features/authorization_spec.rb | 221 ++++++++++++++++----- spec/graphql/types/issue_type_spec.rb | 2 + spec/graphql/types/merge_request_type_spec.rb | 11 +- spec/graphql/types/milestone_type_spec.rb | 9 + spec/graphql/types/project_type_spec.rb | 12 +- spec/graphql/types/query_type_spec.rb | 4 - spec/graphql/types/user_type_spec.rb | 9 + .../authorize/authorize_field_service_spec.rb | 73 +++++++ .../graphql/authorize/instrumentation_spec.rb | 67 ------- 9 files changed, 273 insertions(+), 135 deletions(-) create mode 100644 spec/graphql/types/milestone_type_spec.rb create mode 100644 spec/graphql/types/user_type_spec.rb create mode 100644 spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb delete mode 100644 spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb (limited to 'spec') diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb index a229d29afa6..f863c4444b8 100644 --- a/spec/graphql/features/authorization_spec.rb +++ b/spec/graphql/features/authorization_spec.rb @@ -5,61 +5,192 @@ require 'spec_helper' describe 'Gitlab::Graphql::Authorization' do set(:user) { create(:user) } + let(:permission_single) { :foo } + let(:permission_collection) { [:foo, :bar] } let(:test_object) { double(name: 'My name') } - let(:object_type) { object_type_class } - let(:query_type) { query_type_class(object_type, test_object) } - let(:schema) { schema_class(query_type) } + let(:query_string) { '{ object() { name } }' } + let(:result) { execute_query(query_type)['data'] } - let(:execute) do - schema.execute( - query_string, - context: { current_user: user }, - variables: {} - ) + subject { result['object'] } + + shared_examples 'authorization with a single permission' do + it 'returns the protected field when user has permission' do + permit(permission_single) + + expect(subject).to eq('name' => test_object.name) + end + + it 'returns nil when user is not authorized' do + expect(subject).to be_nil + end end - let(:result) { execute['data'] } + shared_examples 'authorization with a collection of permissions' do + it 'returns the protected field when user has all permissions' do + permit(*permission_collection) + + expect(subject).to eq('name' => test_object.name) + end + + it 'returns nil when user only has one of the permissions' do + permit(permission_collection.first) + + expect(subject).to be_nil + end + + it 'returns nil when user only has none of the permissions' do + expect(subject).to be_nil + end + end before do # By default, disallow all permissions. allow(Ability).to receive(:allowed?).and_return(false) end - describe 'authorizing with a single permission' do - let(:query_string) { '{ singlePermission() { name } }' } + describe 'Field authorizations' do + let(:type) { type_factory } - subject { result['singlePermission'] } + describe 'with a single permission' do + let(:query_type) do + query_factory do |query| + query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single + end + end + + include_examples 'authorization with a single permission' + end + + describe 'with a collection of permissions' do + let(:query_type) do + permissions = permission_collection + query_factory do |qt| + qt.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } do + authorize permissions + end + end + end - it 'should return the protected field when user has permission' do - permit(:foo) + include_examples 'authorization with a collection of permissions' + end + end - expect(subject['name']).to eq(test_object.name) + describe 'Type authorizations' do + let(:query_type) do + query_factory do |query| + query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } + end end - it 'should return nil when user is not authorized' do - expect(subject).to be_nil + describe 'with a single permission' do + let(:type) do + type_factory do |type| + type.authorize permission_single + end + end + + include_examples 'authorization with a single permission' + end + + describe 'with a collection of permissions' do + let(:type) do + type_factory do |type| + type.authorize permission_collection + end + end + + include_examples 'authorization with a collection of permissions' end end - describe 'authorizing with an Array of permissions' do - let(:query_string) { '{ permissionCollection() { name } }' } + describe 'type and field authorizations together' do + let(:permission_1) { permission_collection.first } + let(:permission_2) { permission_collection.last } - subject { result['permissionCollection'] } + let(:type) do + type_factory do |type| + type.authorize permission_1 + end + end - it 'should return the protected field when user has all permissions' do - permit(:foo, :bar) + let(:query_type) do + query_factory do |query| + query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2 + end + end - expect(subject['name']).to eq(test_object.name) + include_examples 'authorization with a collection of permissions' + end + + describe 'type authorizations when applied to a relay connection' do + let(:query_string) { '{ object() { edges { node { name } } } }' } + + let(:type) do + type_factory do |type| + type.authorize permission_single + end + end + + let(:query_type) do + query_factory do |query| + query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object] } + end end - it 'should return nil when user only has one of the permissions' do - permit(:foo) + subject { result.dig('object', 'edges') } - expect(subject).to be_nil + it 'returns the protected field when user has permission' do + permit(permission_single) + + expect(subject).not_to be_empty + expect(subject.first['node']).to eq('name' => test_object.name) end - it 'should return nil when user only has none of the permissions' do - expect(subject).to be_nil + it 'returns nil when user is not authorized' do + expect(subject).to be_empty + end + end + + describe 'type authorizations when applied to a basic connection' do + let(:type) do + type_factory do |type| + type.authorize permission_single + end + end + + let(:query_type) do + query_factory do |query| + query.field :object, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] } + end + end + + subject { result['object'].first } + + include_examples 'authorization with a single permission' + end + + describe 'when connections do not follow the correct specification' do + let(:query_string) { '{ object() { edges { node { name }} } }' } + + let(:type) do + bad_node = type_factory do |type| + type.graphql_name 'BadNode' + type.field :bad_node, GraphQL::STRING_TYPE, null: true + end + + type_factory do |type| + type.field :edges, [bad_node], null: true + end + end + + let(:query_type) do + query_factory do |query| + query.field :object, type, null: true + end + end + + it 'throws an error' do + expect { result }.to raise_error(Gitlab::Graphql::Errors::ConnectionDefinitionError) end end @@ -71,36 +202,34 @@ describe 'Gitlab::Graphql::Authorization' do end end - def object_type_class + def type_factory Class.new(Types::BaseObject) do - graphql_name 'TestObject' + graphql_name 'TestType' field :name, GraphQL::STRING_TYPE, null: true + + yield(self) if block_given? end end - def query_type_class(type, object) + def query_factory Class.new(Types::BaseObject) do graphql_name 'TestQuery' - field :single_permission, type, - null: true, - authorize: :foo, - resolve: ->(obj, args, ctx) { object } - - field :permission_collection, type, - null: true, - resolve: ->(obj, args, ctx) { object } do - authorize [:foo, :bar] - end + yield(self) if block_given? end end - def schema_class(query) - Class.new(GraphQL::Schema) do + def execute_query(query_type) + schema = Class.new(GraphQL::Schema) do use Gitlab::Graphql::Authorize - - query(query) + query(query_type) end + + schema.execute( + query_string, + context: { current_user: user }, + variables: {} + ) end end diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index 63a07647a60..dc37b15001f 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -4,4 +4,6 @@ describe GitlabSchema.types['Issue'] do it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) } it { expect(described_class.graphql_name).to eq('Issue') } + + it { expect(described_class).to require_graphql_authorizations(:read_issue) } end diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index c369953e3ea..89c12879074 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -3,14 +3,9 @@ require 'spec_helper' describe GitlabSchema.types['MergeRequest'] do it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) } - describe 'head pipeline' do - it 'has a head pipeline field' do - expect(described_class).to have_graphql_field(:head_pipeline) - end + it { expect(described_class).to require_graphql_authorizations(:read_merge_request) } - it 'authorizes the field' do - expect(described_class.fields['headPipeline']) - .to require_graphql_authorizations(:read_pipeline) - end + describe 'nested head pipeline' do + it { expect(described_class).to have_graphql_field(:head_pipeline) } end end diff --git a/spec/graphql/types/milestone_type_spec.rb b/spec/graphql/types/milestone_type_spec.rb new file mode 100644 index 00000000000..f7ee79eae9f --- /dev/null +++ b/spec/graphql/types/milestone_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['Milestone'] do + it { expect(described_class.graphql_name).to eq('Milestone') } + + it { expect(described_class).to require_graphql_authorizations(:read_milestone) } +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index e8f1c84f8d6..e0ad09bdf22 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -5,19 +5,11 @@ describe GitlabSchema.types['Project'] do it { expect(described_class.graphql_name).to eq('Project') } + it { expect(described_class).to require_graphql_authorizations(:read_project) } + describe 'nested merge request' do it { expect(described_class).to have_graphql_field(:merge_requests) } it { expect(described_class).to have_graphql_field(:merge_request) } - - it 'authorizes the merge request' do - expect(described_class.fields['mergeRequest']) - .to require_graphql_authorizations(:read_merge_request) - end - - it 'authorizes the merge requests' do - expect(described_class.fields['mergeRequests']) - .to require_graphql_authorizations(:read_merge_request) - end end describe 'nested issues' do diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index 07c61ea7647..69e3ea8a4a9 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -15,10 +15,6 @@ describe GitlabSchema.types['Query'] do is_expected.to have_graphql_type(Types::ProjectType) is_expected.to have_graphql_resolver(Resolvers::ProjectResolver) end - - it 'authorizes with read_project' do - is_expected.to require_graphql_authorizations(:read_project) - end end describe 'metadata field' do diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb new file mode 100644 index 00000000000..8134cc13eb4 --- /dev/null +++ b/spec/graphql/types/user_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['User'] do + it { expect(described_class.graphql_name).to eq('User') } + + it { expect(described_class).to require_graphql_authorizations(:read_user) } +end diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb new file mode 100644 index 00000000000..ce320a2bdb0 --- /dev/null +++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Also see spec/graphql/features/authorization_spec.rb for +# integration tests of AuthorizeFieldService +describe Gitlab::Graphql::Authorize::AuthorizeFieldService do + describe '#build_checker' do + let(:current_user) { double(:current_user) } + let(:abilities) { [double(:first_ability), double(:last_ability)] } + + let(:checker) do + service = described_class.new(double(resolve_proc: proc {})) + allow(service).to receive(:authorizations).and_return(abilities) + service.__send__(:build_checker, current_user) + end + + it 'returns a checker which checks for a single object' do + object = double(:object) + + abilities.each do |ability| + spy_ability_check_for(ability, object, passed: true) + end + + expect(checker.call(object)).to eq(object) + end + + it 'returns a checker which checks for all objects' do + objects = [double(:first), double(:last)] + + abilities.each do |ability| + objects.each do |object| + spy_ability_check_for(ability, object, passed: true) + end + end + + expect(checker.call(objects)).to eq(objects) + end + + context 'when some objects would not pass the check' do + it 'returns nil when it is single object' do + disallowed = double(:object) + + spy_ability_check_for(abilities.first, disallowed, passed: false) + + expect(checker.call(disallowed)).to be_nil + end + + it 'returns only objects which passed when there are more than one' do + allowed = double(:allowed) + disallowed = double(:disallowed) + + spy_ability_check_for(abilities.first, disallowed, passed: false) + + abilities.each do |ability| + spy_ability_check_for(ability, allowed, passed: true) + end + + expect(checker.call([disallowed, allowed])) + .to contain_exactly(allowed) + end + end + end + + private + + def spy_ability_check_for(ability, object, passed: true) + expect(Ability) + .to receive(:allowed?) + .with(current_user, ability, object) + .and_return(passed) + end +end diff --git a/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb b/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb deleted file mode 100644 index cf3a8bcc8b4..00000000000 --- a/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Graphql::Authorize::Instrumentation do - describe '#build_checker' do - let(:current_user) { double(:current_user) } - let(:abilities) { [double(:first_ability), double(:last_ability)] } - - let(:checker) do - described_class.new.__send__(:build_checker, current_user, abilities) - end - - it 'returns a checker which checks for a single object' do - object = double(:object) - - abilities.each do |ability| - spy_ability_check_for(ability, object, passed: true) - end - - expect(checker.call(object)).to eq(object) - end - - it 'returns a checker which checks for all objects' do - objects = [double(:first), double(:last)] - - abilities.each do |ability| - objects.each do |object| - spy_ability_check_for(ability, object, passed: true) - end - end - - expect(checker.call(objects)).to eq(objects) - end - - context 'when some objects would not pass the check' do - it 'returns nil when it is single object' do - disallowed = double(:object) - - spy_ability_check_for(abilities.first, disallowed, passed: false) - - expect(checker.call(disallowed)).to be_nil - end - - it 'returns only objects which passed when there are more than one' do - allowed = double(:allowed) - disallowed = double(:disallowed) - - spy_ability_check_for(abilities.first, disallowed, passed: false) - - abilities.each do |ability| - spy_ability_check_for(ability, allowed, passed: true) - end - - expect(checker.call([disallowed, allowed])) - .to contain_exactly(allowed) - end - end - - def spy_ability_check_for(ability, object, passed: true) - expect(Ability) - .to receive(:allowed?) - .with(current_user, ability, object) - .and_return(passed) - end - end -end -- cgit v1.2.1 From c17b7afa0298beb3290d46ad504f0f4dc4508d7a Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Wed, 3 Apr 2019 10:00:18 +0800 Subject: Remove reply_to_individual_notes feature flag Also removes specs related to the flag --- .../merge_request/user_posts_notes_spec.rb | 13 +--- .../notes/components/note_actions_spec.js | 82 ++++------------------ spec/services/notes/build_service_spec.rb | 36 +++------- spec/services/notes/create_service_spec.rb | 41 +++-------- 4 files changed, 34 insertions(+), 138 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index dc0862be6fc..e5770905dbd 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -67,18 +67,7 @@ describe 'Merge request > User posts notes', :js do end end - describe 'when reply_to_individual_notes feature flag is disabled' do - before do - stub_feature_flags(reply_to_individual_notes: false) - visit project_merge_request_path(project, merge_request) - end - - it 'does not show a reply button' do - expect(page).to have_no_selector('.js-reply-button') - end - end - - describe 'when reply_to_individual_notes feature flag is not set' do + describe 'reply button' do before do visit project_merge_request_path(project, merge_request) end diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index d604e90b529..0cfcc994234 100644 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -128,87 +128,33 @@ describe('noteActions', () => { }); }); - describe('with feature flag replyToIndividualNotes enabled', () => { + describe('for showReply = true', () => { beforeEach(() => { - gon.features = { - replyToIndividualNotes: true, - }; - }); - - afterEach(() => { - gon.features = {}; - }); - - describe('for showReply = true', () => { - beforeEach(() => { - wrapper = shallowMountNoteActions({ - ...props, - showReply: true, - }); - }); - - it('shows a reply button', () => { - const replyButton = wrapper.find({ ref: 'replyButton' }); - - expect(replyButton.exists()).toBe(true); + wrapper = shallowMountNoteActions({ + ...props, + showReply: true, }); }); - describe('for showReply = false', () => { - beforeEach(() => { - wrapper = shallowMountNoteActions({ - ...props, - showReply: false, - }); - }); - - it('does not show a reply button', () => { - const replyButton = wrapper.find({ ref: 'replyButton' }); + it('shows a reply button', () => { + const replyButton = wrapper.find({ ref: 'replyButton' }); - expect(replyButton.exists()).toBe(false); - }); + expect(replyButton.exists()).toBe(true); }); }); - describe('with feature flag replyToIndividualNotes disabled', () => { + describe('for showReply = false', () => { beforeEach(() => { - gon.features = { - replyToIndividualNotes: false, - }; - }); - - afterEach(() => { - gon.features = {}; - }); - - describe('for showReply = true', () => { - beforeEach(() => { - wrapper = shallowMountNoteActions({ - ...props, - showReply: true, - }); - }); - - it('does not show a reply button', () => { - const replyButton = wrapper.find({ ref: 'replyButton' }); - - expect(replyButton.exists()).toBe(false); + wrapper = shallowMountNoteActions({ + ...props, + showReply: false, }); }); - describe('for showReply = false', () => { - beforeEach(() => { - wrapper = shallowMountNoteActions({ - ...props, - showReply: false, - }); - }); - - it('does not show a reply button', () => { - const replyButton = wrapper.find({ ref: 'replyButton' }); + it('does not show a reply button', () => { + const replyButton = wrapper.find({ ref: 'replyButton' }); - expect(replyButton.exists()).toBe(false); - }); + expect(replyButton.exists()).toBe(false); }); }); }); diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb index af4daff336b..96fff20f7fb 100644 --- a/spec/services/notes/build_service_spec.rb +++ b/spec/services/notes/build_service_spec.rb @@ -128,37 +128,19 @@ describe Notes::BuildService do subject { described_class.new(project, author, note: 'Test', in_reply_to_discussion_id: note.discussion_id).execute } - shared_examples 'an individual note reply' do - it 'builds another individual note' do - expect(subject).to be_valid - expect(subject).to be_a(Note) - expect(subject.discussion_id).not_to eq(note.discussion_id) - end + it 'sets the note up to be in reply to that note' do + expect(subject).to be_valid + expect(subject).to be_a(DiscussionNote) + expect(subject.discussion_id).to eq(note.discussion_id) end - context 'when reply_to_individual_notes is disabled' do - before do - stub_feature_flags(reply_to_individual_notes: false) - end - - it_behaves_like 'an individual note reply' - end + context 'when noteable does not support replies' do + let(:note) { create(:note_on_commit) } - context 'when reply_to_individual_notes is enabled' do - before do - stub_feature_flags(reply_to_individual_notes: true) - end - - it 'sets the note up to be in reply to that note' do + it 'builds another individual note' do expect(subject).to be_valid - expect(subject).to be_a(DiscussionNote) - expect(subject.discussion_id).to eq(note.discussion_id) - end - - context 'when noteable does not support replies' do - let(:note) { create(:note_on_commit) } - - it_behaves_like 'an individual note reply' + expect(subject).to be_a(Note) + expect(subject.discussion_id).not_to eq(note.discussion_id) end end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 8d8e81173ff..bcbb8950910 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -298,41 +298,20 @@ describe Notes::CreateService do subject { described_class.new(project, user, reply_opts).execute } - context 'when reply_to_individual_notes is disabled' do - before do - stub_feature_flags(reply_to_individual_notes: false) - end - - it 'creates an individual note' do - expect(subject.type).to eq(nil) - expect(subject.discussion_id).not_to eq(existing_note.discussion_id) - end - - it 'does not convert existing note' do - expect { subject }.not_to change { existing_note.reload.type } - end + it 'creates a DiscussionNote in reply to existing note' do + expect(subject).to be_a(DiscussionNote) + expect(subject.discussion_id).to eq(existing_note.discussion_id) end - context 'when reply_to_individual_notes is enabled' do - before do - stub_feature_flags(reply_to_individual_notes: true) - end - - it 'creates a DiscussionNote in reply to existing note' do - expect(subject).to be_a(DiscussionNote) - expect(subject.discussion_id).to eq(existing_note.discussion_id) - end - - it 'converts existing note to DiscussionNote' do - expect do - existing_note + it 'converts existing note to DiscussionNote' do + expect do + existing_note - Timecop.freeze(Time.now + 1.minute) { subject } + Timecop.freeze(Time.now + 1.minute) { subject } - existing_note.reload - end.to change { existing_note.type }.from(nil).to('DiscussionNote') - .and change { existing_note.updated_at } - end + existing_note.reload + end.to change { existing_note.type }.from(nil).to('DiscussionNote') + .and change { existing_note.updated_at } end end end -- cgit v1.2.1 From 06b88af04657be961a4da97a586706fb99eb6a27 Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Tue, 12 Mar 2019 13:35:55 -0300 Subject: Add reusable project_selector component This commit adds a resuable UI component that allows a user to search for a project name, shows the search results, and allows the user to select one or more projects. This component communicates with its parent using props and events. This component was originally created for use in the EE-specific "Operations Dashboard" page, but it is applicable for CE use cases as well, and so was added as a CE shared component. In addition, some logic was extracted from the frequent_items_list_item component into shared filters to avoid logic duplication. --- spec/frontend/lib/utils/text_utility_spec.js | 27 ++++ .../components/frequent_items_list_item_spec.js | 49 +++++-- spec/javascripts/lib/utils/higlight_spec.js | 43 ++++++ .../project_selector/project_list_item_spec.js | 104 ++++++++++++++ .../project_selector/project_selector_spec.js | 152 +++++++++++++++++++++ 5 files changed, 365 insertions(+), 10 deletions(-) create mode 100644 spec/javascripts/lib/utils/higlight_spec.js create mode 100644 spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js create mode 100644 spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js (limited to 'spec') diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 0a266b19ea5..3f331055a32 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -151,4 +151,31 @@ describe('text_utility', () => { ); }); }); + + describe('truncateNamespace', () => { + it(`should return the root namespace if the namespace only includes one level`, () => { + expect(textUtils.truncateNamespace('a / b')).toBe('a'); + }); + + it(`should return the first 2 namespaces if the namespace inlcudes exactly 2 levels`, () => { + expect(textUtils.truncateNamespace('a / b / c')).toBe('a / b'); + }); + + it(`should return the first and last namespaces, separated by "...", if the namespace inlcudes more than 2 levels`, () => { + expect(textUtils.truncateNamespace('a / b / c / d')).toBe('a / ... / c'); + expect(textUtils.truncateNamespace('a / b / c / d / e / f / g / h / i')).toBe('a / ... / h'); + }); + + it(`should return an empty string for invalid inputs`, () => { + [undefined, null, 4, {}, true, new Date()].forEach(input => { + expect(textUtils.truncateNamespace(input)).toBe(''); + }); + }); + + it(`should not alter strings that aren't formatted as namespaces`, () => { + ['', ' ', '\t', 'a', 'a \\ b'].forEach(input => { + expect(textUtils.truncateNamespace(input)).toBe(input); + }); + }); + }); }); diff --git a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js index 7deed985219..92554bd9a69 100644 --- a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js +++ b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { trimText } from 'spec/helpers/vue_component_helper'; import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here const createComponent = () => { @@ -40,30 +41,58 @@ describe('FrequentItemsListItemComponent', () => { }); describe('highlightedItemName', () => { - it('should enclose part of project name in & which matches with `matcher` prop', () => { + it('should enclose part of project name in & which matches with `matcher` prop', done => { vm.matcher = 'lab'; - expect(vm.highlightedItemName).toContain('Lab'); + vm.$nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-frequent-items-item-title').innerHTML).toContain( + 'Lab', + ); + }) + .then(done) + .catch(done.fail); }); - it('should return project name as it is if `matcher` is not available', () => { + it('should return project name as it is if `matcher` is not available', done => { vm.matcher = null; - expect(vm.highlightedItemName).toBe(mockProject.name); + vm.$nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-frequent-items-item-title').innerHTML).toBe( + mockProject.name, + ); + }) + .then(done) + .catch(done.fail); }); }); describe('truncatedNamespace', () => { - it('should truncate project name from namespace string', () => { + it('should truncate project name from namespace string', done => { vm.namespace = 'platform / nokia-3310'; - expect(vm.truncatedNamespace).toBe('platform'); + vm.$nextTick() + .then(() => { + expect( + trimText(vm.$el.querySelector('.js-frequent-items-item-namespace').innerHTML), + ).toBe('platform'); + }) + .then(done) + .catch(done.fail); }); - it('should truncate namespace string from the middle if it includes more than two groups in path', () => { + it('should truncate namespace string from the middle if it includes more than two groups in path', done => { vm.namespace = 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310'; - expect(vm.truncatedNamespace).toBe('platform / ... / Mobile Chipset'); + vm.$nextTick() + .then(() => { + expect( + trimText(vm.$el.querySelector('.js-frequent-items-item-namespace').innerHTML), + ).toBe('platform / ... / Mobile Chipset'); + }) + .then(done) + .catch(done.fail); }); }); }); @@ -74,8 +103,8 @@ describe('FrequentItemsListItemComponent', () => { expect(vm.$el.querySelectorAll('a').length).toBe(1); expect(vm.$el.querySelectorAll('.frequent-items-item-avatar-container').length).toBe(1); expect(vm.$el.querySelectorAll('.frequent-items-item-metadata-container').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-title').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-namespace').length).toBe(1); + expect(vm.$el.querySelectorAll('.js-frequent-items-item-title').length).toBe(1); + expect(vm.$el.querySelectorAll('.js-frequent-items-item-namespace').length).toBe(1); }); }); }); diff --git a/spec/javascripts/lib/utils/higlight_spec.js b/spec/javascripts/lib/utils/higlight_spec.js new file mode 100644 index 00000000000..638bbf65ae9 --- /dev/null +++ b/spec/javascripts/lib/utils/higlight_spec.js @@ -0,0 +1,43 @@ +import highlight from '~/lib/utils/highlight'; + +describe('highlight', () => { + it(`should appropriately surround substring matches`, () => { + const expected = 'gitlab'; + + expect(highlight('gitlab', 'it')).toBe(expected); + }); + + it(`should return an empty string in the case of invalid inputs`, () => { + [null, undefined].forEach(input => { + expect(highlight(input, 'match')).toBe(''); + }); + }); + + it(`should return the original value if match is null, undefined, or ''`, () => { + [null, undefined].forEach(match => { + expect(highlight('gitlab', match)).toBe('gitlab'); + }); + }); + + it(`should highlight matches in non-string inputs`, () => { + const expected = '123456'; + + expect(highlight(123456, 45)).toBe(expected); + }); + + it(`should sanitize the input string before highlighting matches`, () => { + const expected = 'hello world'; + + expect(highlight('hello world', 'w')).toBe(expected); + }); + + it(`should not highlight anything if no matches are found`, () => { + expect(highlight('gitlab', 'hello')).toBe('gitlab'); + }); + + it(`should allow wrapping elements to be customized`, () => { + const expected = '123'; + + expect(highlight('123', '2', '', '')).toBe(expected); + }); +}); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js new file mode 100644 index 00000000000..8dbdfe97f8f --- /dev/null +++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js @@ -0,0 +1,104 @@ +import _ from 'underscore'; +import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { trimText } from 'spec/helpers/vue_component_helper'; + +const localVue = createLocalVue(); + +describe('ProjectListItem component', () => { + let wrapper; + let vm; + loadJSONFixtures('projects.json'); + const project = getJSONFixture('projects.json')[0]; + + beforeEach(() => { + wrapper = shallowMount(localVue.extend(ProjectListItem), { + propsData: { + project, + selected: false, + }, + sync: false, + localVue, + }); + + ({ vm } = wrapper); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('does not render a check mark icon if selected === false', () => { + expect(vm.$el.querySelector('.js-selected-icon.js-unselected')).toBeTruthy(); + }); + + it('renders a check mark icon if selected === true', done => { + wrapper.setProps({ selected: true }); + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.js-selected-icon.js-selected')).toBeTruthy(); + done(); + }); + }); + + it(`emits a "clicked" event when clicked`, () => { + spyOn(vm, '$emit'); + vm.onClick(); + + expect(vm.$emit).toHaveBeenCalledWith('click'); + }); + + it(`renders the project avatar`, () => { + expect(vm.$el.querySelector('.js-project-avatar')).toBeTruthy(); + }); + + it(`renders a simple namespace name with a trailing slash`, done => { + project.name_with_namespace = 'a / b'; + wrapper.setProps({ project: _.clone(project) }); + + vm.$nextTick(() => { + const renderedNamespace = trimText(vm.$el.querySelector('.js-project-namespace').textContent); + + expect(renderedNamespace).toBe('a /'); + done(); + }); + }); + + it(`renders a properly truncated namespace with a trailing slash`, done => { + project.name_with_namespace = 'a / b / c / d / e / f'; + wrapper.setProps({ project: _.clone(project) }); + + vm.$nextTick(() => { + const renderedNamespace = trimText(vm.$el.querySelector('.js-project-namespace').textContent); + + expect(renderedNamespace).toBe('a / ... / e /'); + done(); + }); + }); + + it(`renders the project name`, done => { + project.name = 'my-test-project'; + wrapper.setProps({ project: _.clone(project) }); + + vm.$nextTick(() => { + const renderedName = trimText(vm.$el.querySelector('.js-project-name').innerHTML); + + expect(renderedName).toBe('my-test-project'); + done(); + }); + }); + + it(`renders the project name with highlighting in the case of a search query match`, done => { + project.name = 'my-test-project'; + wrapper.setProps({ project: _.clone(project), matcher: 'pro' }); + + vm.$nextTick(() => { + const renderedName = trimText(vm.$el.querySelector('.js-project-name').innerHTML); + + const expected = 'my-test-project'; + + expect(renderedName).toBe(expected); + done(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js new file mode 100644 index 00000000000..88c1dff76a1 --- /dev/null +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -0,0 +1,152 @@ +import Vue from 'vue'; +import _ from 'underscore'; +import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue'; +import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; +import { shallowMount } from '@vue/test-utils'; +import { trimText } from 'spec/helpers/vue_component_helper'; + +describe('ProjectSelector component', () => { + let wrapper; + let vm; + loadJSONFixtures('projects.json'); + const allProjects = getJSONFixture('projects.json'); + const searchResults = allProjects.slice(0, 5); + let selected = []; + selected = selected.concat(allProjects.slice(0, 3)).concat(allProjects.slice(5, 8)); + + beforeEach(() => { + jasmine.clock().install(); + + wrapper = shallowMount(Vue.extend(ProjectSelector), { + propsData: { + projectSearchResults: searchResults, + selectedProjects: selected, + showNoResultsMessage: false, + showMinimumSearchQueryMessage: false, + showLoadingIndicator: false, + showSearchErrorMessage: false, + }, + attachToDocument: true, + }); + + ({ vm } = wrapper); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + vm.$destroy(); + }); + + it('renders the search results', () => { + expect(vm.$el.querySelectorAll('.js-project-list-item').length).toBe(5); + }); + + it(`triggers a (debounced) search when the search input value changes`, done => { + spyOn(vm, '$emit'); + const query = 'my test query!'; + const searchInput = vm.$el.querySelector('.js-project-selector-input'); + searchInput.value = query; + searchInput.dispatchEvent(new Event('input')); + + vm.$nextTick(() => { + expect(vm.$emit).not.toHaveBeenCalledWith(); + jasmine.clock().tick(501); + + expect(vm.$emit).toHaveBeenCalledWith('searched', query); + done(); + }); + }); + + it(`debounces the search input`, done => { + spyOn(vm, '$emit'); + const searchInput = vm.$el.querySelector('.js-project-selector-input'); + + const updateSearchQuery = (count = 0) => { + if (count === 10) { + jasmine.clock().tick(101); + + expect(vm.$emit).toHaveBeenCalledTimes(1); + expect(vm.$emit).toHaveBeenCalledWith('searched', `search query #9`); + done(); + } else { + searchInput.value = `search query #${count}`; + searchInput.dispatchEvent(new Event('input')); + + vm.$nextTick(() => { + jasmine.clock().tick(400); + updateSearchQuery(count + 1); + }); + } + }; + + updateSearchQuery(); + }); + + it(`includes a placeholder in the search box`, () => { + expect(vm.$el.querySelector('.js-project-selector-input').placeholder).toBe( + 'Search your projects', + ); + }); + + it(`triggers a "projectClicked" event when a project is clicked`, () => { + spyOn(vm, '$emit'); + wrapper.find(ProjectListItem).vm.$emit('click', _.first(searchResults)); + + expect(vm.$emit).toHaveBeenCalledWith('projectClicked', _.first(searchResults)); + }); + + it(`shows a "no results" message if showNoResultsMessage === true`, done => { + wrapper.setProps({ showNoResultsMessage: true }); + + vm.$nextTick(() => { + const noResultsEl = vm.$el.querySelector('.js-no-results-message'); + + expect(noResultsEl).toBeTruthy(); + + expect(trimText(noResultsEl.textContent)).toEqual('Sorry, no projects matched your search'); + + done(); + }); + }); + + it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, done => { + wrapper.setProps({ showMinimumSearchQueryMessage: true }); + + vm.$nextTick(() => { + const minimumSearchEl = vm.$el.querySelector('.js-minimum-search-query-message'); + + expect(minimumSearchEl).toBeTruthy(); + + expect(trimText(minimumSearchEl.textContent)).toEqual( + 'Enter at least three characters to search', + ); + + done(); + }); + }); + + it(`shows a error message if showSearchErrorMessage === true`, done => { + wrapper.setProps({ showSearchErrorMessage: true }); + + vm.$nextTick(() => { + const errorMessageEl = vm.$el.querySelector('.js-search-error-message'); + + expect(errorMessageEl).toBeTruthy(); + + expect(trimText(errorMessageEl.textContent)).toEqual( + 'Something went wrong, unable to search projects', + ); + + done(); + }); + }); + + it(`focuses the input element when the focusSearchInput() method is called`, () => { + const input = vm.$el.querySelector('.js-project-selector-input'); + + expect(document.activeElement).not.toBe(input); + vm.focusSearchInput(); + + expect(document.activeElement).toBe(input); + }); +}); -- cgit v1.2.1 From eb95100c066d2d70a2128ea9ac6776f720b0777a Mon Sep 17 00:00:00 2001 From: mfluharty Date: Thu, 28 Mar 2019 14:00:44 -0600 Subject: Make corrections to address review feedback Refactor tests to follow conventions Add XSS test Eliminate a few unnecessary lines, comments, and parameters Use Vue.set for nested state changes --- .../components/frequent_items_list_item_spec.js | 105 +++++++++------------ .../components/frequent_items_search_input_spec.js | 25 ++--- .../project_selector/project_list_item_spec.js | 104 ++++++++++---------- .../project_selector/project_selector_spec.js | 84 +++++++---------- 4 files changed, 146 insertions(+), 172 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js index 92554bd9a69..f00bc2eeb6d 100644 --- a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js +++ b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js @@ -1,26 +1,31 @@ import Vue from 'vue'; import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; import { trimText } from 'spec/helpers/vue_component_helper'; import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here const createComponent = () => { const Component = Vue.extend(frequentItemsListItemComponent); - return mountComponent(Component, { - itemId: mockProject.id, - itemName: mockProject.name, - namespace: mockProject.namespace, - webUrl: mockProject.webUrl, - avatarUrl: mockProject.avatarUrl, + return shallowMount(Component, { + propsData: { + itemId: mockProject.id, + itemName: mockProject.name, + namespace: mockProject.namespace, + webUrl: mockProject.webUrl, + avatarUrl: mockProject.avatarUrl, + }, }); }; describe('FrequentItemsListItemComponent', () => { + let wrapper; let vm; beforeEach(() => { - vm = createComponent(); + wrapper = createComponent(); + + ({ vm } = wrapper); }); afterEach(() => { @@ -30,81 +35,61 @@ describe('FrequentItemsListItemComponent', () => { describe('computed', () => { describe('hasAvatar', () => { it('should return `true` or `false` if whether avatar is present or not', () => { - vm.avatarUrl = 'path/to/avatar.png'; + wrapper.setProps({ avatarUrl: 'path/to/avatar.png' }); expect(vm.hasAvatar).toBe(true); - vm.avatarUrl = null; + wrapper.setProps({ avatarUrl: null }); expect(vm.hasAvatar).toBe(false); }); }); describe('highlightedItemName', () => { - it('should enclose part of project name in & which matches with `matcher` prop', done => { - vm.matcher = 'lab'; - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-frequent-items-item-title').innerHTML).toContain( - 'Lab', - ); - }) - .then(done) - .catch(done.fail); + it('should enclose part of project name in & which matches with `matcher` prop', () => { + wrapper.setProps({ matcher: 'lab' }); + + expect(wrapper.find('.js-frequent-items-item-title').html()).toContain( + 'Lab', + ); }); - it('should return project name as it is if `matcher` is not available', done => { - vm.matcher = null; - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-frequent-items-item-title').innerHTML).toBe( - mockProject.name, - ); - }) - .then(done) - .catch(done.fail); + it('should return project name as it is if `matcher` is not available', () => { + wrapper.setProps({ matcher: null }); + + expect(trimText(wrapper.find('.js-frequent-items-item-title').text())).toBe( + mockProject.name, + ); }); }); describe('truncatedNamespace', () => { - it('should truncate project name from namespace string', done => { - vm.namespace = 'platform / nokia-3310'; - - vm.$nextTick() - .then(() => { - expect( - trimText(vm.$el.querySelector('.js-frequent-items-item-namespace').innerHTML), - ).toBe('platform'); - }) - .then(done) - .catch(done.fail); + it('should truncate project name from namespace string', () => { + wrapper.setProps({ namespace: 'platform / nokia-3310' }); + + expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe('platform'); }); - it('should truncate namespace string from the middle if it includes more than two groups in path', done => { - vm.namespace = 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310'; - - vm.$nextTick() - .then(() => { - expect( - trimText(vm.$el.querySelector('.js-frequent-items-item-namespace').innerHTML), - ).toBe('platform / ... / Mobile Chipset'); - }) - .then(done) - .catch(done.fail); + it('should truncate namespace string from the middle if it includes more than two groups in path', () => { + wrapper.setProps({ + namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310', + }); + + expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe( + 'platform / ... / Mobile Chipset', + ); }); }); }); describe('template', () => { it('should render component element', () => { - expect(vm.$el.classList.contains('frequent-items-list-item-container')).toBeTruthy(); - expect(vm.$el.querySelectorAll('a').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-avatar-container').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-metadata-container').length).toBe(1); - expect(vm.$el.querySelectorAll('.js-frequent-items-item-title').length).toBe(1); - expect(vm.$el.querySelectorAll('.js-frequent-items-item-namespace').length).toBe(1); + expect(wrapper.classes()).toContain('frequent-items-list-item-container'); + expect(wrapper.findAll('a').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-avatar-container').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-metadata-container').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-title').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-namespace').length).toBe(1); }); }); }); diff --git a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js index d564292f1ba..ddbbc5c2d29 100644 --- a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js +++ b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js @@ -1,19 +1,22 @@ import Vue from 'vue'; import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue'; import eventHub from '~/frequent_items/event_hub'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; const createComponent = (namespace = 'projects') => { const Component = Vue.extend(searchComponent); - return mountComponent(Component, { namespace }); + return shallowMount(Component, { propsData: { namespace } }); }; describe('FrequentItemsSearchInputComponent', () => { + let wrapper; let vm; beforeEach(() => { - vm = createComponent(); + wrapper = createComponent(); + + ({ vm } = wrapper); }); afterEach(() => { @@ -35,7 +38,7 @@ describe('FrequentItemsSearchInputComponent', () => { describe('mounted', () => { it('should listen `dropdownOpen` event', done => { spyOn(eventHub, '$on'); - const vmX = createComponent(); + const vmX = createComponent().vm; Vue.nextTick(() => { expect(eventHub.$on).toHaveBeenCalledWith( @@ -49,7 +52,7 @@ describe('FrequentItemsSearchInputComponent', () => { describe('beforeDestroy', () => { it('should unbind event listeners on eventHub', done => { - const vmX = createComponent(); + const vmX = createComponent().vm; spyOn(eventHub, '$off'); vmX.$mount(); @@ -67,12 +70,12 @@ describe('FrequentItemsSearchInputComponent', () => { describe('template', () => { it('should render component element', () => { - const inputEl = vm.$el.querySelector('input.form-control'); - - expect(vm.$el.classList.contains('search-input-container')).toBeTruthy(); - expect(inputEl).not.toBe(null); - expect(inputEl.getAttribute('placeholder')).toBe('Search your projects'); - expect(vm.$el.querySelector('.search-icon')).toBeDefined(); + expect(wrapper.classes()).toContain('search-input-container'); + expect(wrapper.contains('input.form-control')).toBe(true); + expect(wrapper.contains('.search-icon')).toBe(true); + expect(wrapper.find('input.form-control').attributes('placeholder')).toBe( + 'Search your projects', + ); }); }); }); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js index 8dbdfe97f8f..b95183747bb 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js @@ -1,4 +1,3 @@ -import _ from 'underscore'; import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import { trimText } from 'spec/helpers/vue_component_helper'; @@ -6,99 +5,106 @@ import { trimText } from 'spec/helpers/vue_component_helper'; const localVue = createLocalVue(); describe('ProjectListItem component', () => { + const Component = localVue.extend(ProjectListItem); let wrapper; let vm; + let options; loadJSONFixtures('projects.json'); const project = getJSONFixture('projects.json')[0]; beforeEach(() => { - wrapper = shallowMount(localVue.extend(ProjectListItem), { + options = { propsData: { project, selected: false, }, sync: false, localVue, - }); - - ({ vm } = wrapper); + }; }); afterEach(() => { - vm.$destroy(); + wrapper.vm.$destroy(); }); it('does not render a check mark icon if selected === false', () => { - expect(vm.$el.querySelector('.js-selected-icon.js-unselected')).toBeTruthy(); + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-selected-icon.js-unselected')).toBe(true); }); - it('renders a check mark icon if selected === true', done => { - wrapper.setProps({ selected: true }); + it('renders a check mark icon if selected === true', () => { + options.propsData.selected = true; - vm.$nextTick(() => { - expect(vm.$el.querySelector('.js-selected-icon.js-selected')).toBeTruthy(); - done(); - }); + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-selected-icon.js-selected')).toBe(true); }); it(`emits a "clicked" event when clicked`, () => { + wrapper = shallowMount(Component, options); + ({ vm } = wrapper); + spyOn(vm, '$emit'); - vm.onClick(); + wrapper.vm.onClick(); - expect(vm.$emit).toHaveBeenCalledWith('click'); + expect(wrapper.vm.$emit).toHaveBeenCalledWith('click'); }); it(`renders the project avatar`, () => { - expect(vm.$el.querySelector('.js-project-avatar')).toBeTruthy(); + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-project-avatar')).toBe(true); }); - it(`renders a simple namespace name with a trailing slash`, done => { - project.name_with_namespace = 'a / b'; - wrapper.setProps({ project: _.clone(project) }); + it(`renders a simple namespace name with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b'; - vm.$nextTick(() => { - const renderedNamespace = trimText(vm.$el.querySelector('.js-project-namespace').textContent); + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); - expect(renderedNamespace).toBe('a /'); - done(); - }); + expect(renderedNamespace).toBe('a /'); }); - it(`renders a properly truncated namespace with a trailing slash`, done => { - project.name_with_namespace = 'a / b / c / d / e / f'; - wrapper.setProps({ project: _.clone(project) }); + it(`renders a properly truncated namespace with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b / c / d / e / f'; - vm.$nextTick(() => { - const renderedNamespace = trimText(vm.$el.querySelector('.js-project-namespace').textContent); + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); - expect(renderedNamespace).toBe('a / ... / e /'); - done(); - }); + expect(renderedNamespace).toBe('a / ... / e /'); }); - it(`renders the project name`, done => { - project.name = 'my-test-project'; - wrapper.setProps({ project: _.clone(project) }); + it(`renders the project name`, () => { + options.propsData.project.name = 'my-test-project'; - vm.$nextTick(() => { - const renderedName = trimText(vm.$el.querySelector('.js-project-name').innerHTML); + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').text()); - expect(renderedName).toBe('my-test-project'); - done(); - }); + expect(renderedName).toBe('my-test-project'); }); - it(`renders the project name with highlighting in the case of a search query match`, done => { - project.name = 'my-test-project'; - wrapper.setProps({ project: _.clone(project), matcher: 'pro' }); + it(`renders the project name with highlighting in the case of a search query match`, () => { + options.propsData.project.name = 'my-test-project'; + options.propsData.matcher = 'pro'; + + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').html()); + const expected = 'my-test-project'; + + expect(renderedName).toContain(expected); + }); - vm.$nextTick(() => { - const renderedName = trimText(vm.$el.querySelector('.js-project-name').innerHTML); + it('prevents search query and project name XSS', () => { + const alertSpy = spyOn(window, 'alert'); + options.propsData.project.name = "my-xss-project"; + options.propsData.matcher = "pro"; - const expected = 'my-test-project'; + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').html()); + const expected = 'my-xss-project'; - expect(renderedName).toBe(expected); - done(); - }); + expect(renderedName).toContain(expected); + expect(alertSpy).not.toHaveBeenCalled(); }); }); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js index 88c1dff76a1..ba9ec8f2f19 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -38,28 +38,25 @@ describe('ProjectSelector component', () => { }); it('renders the search results', () => { - expect(vm.$el.querySelectorAll('.js-project-list-item').length).toBe(5); + expect(wrapper.findAll('.js-project-list-item').length).toBe(5); }); - it(`triggers a (debounced) search when the search input value changes`, done => { + it(`triggers a (debounced) search when the search input value changes`, () => { spyOn(vm, '$emit'); const query = 'my test query!'; - const searchInput = vm.$el.querySelector('.js-project-selector-input'); - searchInput.value = query; - searchInput.dispatchEvent(new Event('input')); + const searchInput = wrapper.find('.js-project-selector-input'); + searchInput.setValue(query); + searchInput.trigger('input'); - vm.$nextTick(() => { - expect(vm.$emit).not.toHaveBeenCalledWith(); - jasmine.clock().tick(501); + expect(vm.$emit).not.toHaveBeenCalledWith(); + jasmine.clock().tick(501); - expect(vm.$emit).toHaveBeenCalledWith('searched', query); - done(); - }); + expect(vm.$emit).toHaveBeenCalledWith('searched', query); }); - it(`debounces the search input`, done => { + it(`debounces the search input`, () => { spyOn(vm, '$emit'); - const searchInput = vm.$el.querySelector('.js-project-selector-input'); + const searchInput = wrapper.find('.js-project-selector-input'); const updateSearchQuery = (count = 0) => { if (count === 10) { @@ -67,15 +64,12 @@ describe('ProjectSelector component', () => { expect(vm.$emit).toHaveBeenCalledTimes(1); expect(vm.$emit).toHaveBeenCalledWith('searched', `search query #9`); - done(); } else { - searchInput.value = `search query #${count}`; - searchInput.dispatchEvent(new Event('input')); + searchInput.setValue(`search query #${count}`); + searchInput.trigger('input'); - vm.$nextTick(() => { - jasmine.clock().tick(400); - updateSearchQuery(count + 1); - }); + jasmine.clock().tick(400); + updateSearchQuery(count + 1); } }; @@ -83,7 +77,7 @@ describe('ProjectSelector component', () => { }); it(`includes a placeholder in the search box`, () => { - expect(vm.$el.querySelector('.js-project-selector-input').placeholder).toBe( + expect(wrapper.find('.js-project-selector-input').attributes('placeholder')).toBe( 'Search your projects', ); }); @@ -95,58 +89,44 @@ describe('ProjectSelector component', () => { expect(vm.$emit).toHaveBeenCalledWith('projectClicked', _.first(searchResults)); }); - it(`shows a "no results" message if showNoResultsMessage === true`, done => { + it(`shows a "no results" message if showNoResultsMessage === true`, () => { wrapper.setProps({ showNoResultsMessage: true }); - vm.$nextTick(() => { - const noResultsEl = vm.$el.querySelector('.js-no-results-message'); - - expect(noResultsEl).toBeTruthy(); + expect(wrapper.contains('.js-no-results-message')).toBe(true); - expect(trimText(noResultsEl.textContent)).toEqual('Sorry, no projects matched your search'); + const noResultsEl = wrapper.find('.js-no-results-message'); - done(); - }); + expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search'); }); - it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, done => { + it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, () => { wrapper.setProps({ showMinimumSearchQueryMessage: true }); - vm.$nextTick(() => { - const minimumSearchEl = vm.$el.querySelector('.js-minimum-search-query-message'); - - expect(minimumSearchEl).toBeTruthy(); + expect(wrapper.contains('.js-minimum-search-query-message')).toBe(true); - expect(trimText(minimumSearchEl.textContent)).toEqual( - 'Enter at least three characters to search', - ); + const minimumSearchEl = wrapper.find('.js-minimum-search-query-message'); - done(); - }); + expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search'); }); - it(`shows a error message if showSearchErrorMessage === true`, done => { + it(`shows a error message if showSearchErrorMessage === true`, () => { wrapper.setProps({ showSearchErrorMessage: true }); - vm.$nextTick(() => { - const errorMessageEl = vm.$el.querySelector('.js-search-error-message'); - - expect(errorMessageEl).toBeTruthy(); + expect(wrapper.contains('.js-search-error-message')).toBe(true); - expect(trimText(errorMessageEl.textContent)).toEqual( - 'Something went wrong, unable to search projects', - ); + const errorMessageEl = wrapper.find('.js-search-error-message'); - done(); - }); + expect(trimText(errorMessageEl.text())).toEqual( + 'Something went wrong, unable to search projects', + ); }); it(`focuses the input element when the focusSearchInput() method is called`, () => { - const input = vm.$el.querySelector('.js-project-selector-input'); + const input = wrapper.find('.js-project-selector-input'); - expect(document.activeElement).not.toBe(input); + expect(document.activeElement).not.toBe(input.element); vm.focusSearchInput(); - expect(document.activeElement).toBe(input); + expect(document.activeElement).toBe(input.element); }); }); -- cgit v1.2.1 From e3b38ae09d8e84023a133d48b038994aa122f4b9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 3 Apr 2019 12:23:11 +0700 Subject: Fix Release permission spec We recently changed the permission model to disallow guests to read Releases. The spec should be updated as well according to the expectation. --- spec/support/shared_context/policies/project_policy_shared_context.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb index 8bcd26ec0cd..3ad6e067674 100644 --- a/spec/support/shared_context/policies/project_policy_shared_context.rb +++ b/spec/support/shared_context/policies/project_policy_shared_context.rb @@ -15,7 +15,7 @@ RSpec.shared_context 'ProjectPolicy context' do read_project_for_iids read_issue_iid read_label read_milestone read_project_snippet read_project_member read_note create_project create_issue create_note upload_file create_merge_request_in - award_emoji read_release + award_emoji ] end @@ -24,7 +24,7 @@ RSpec.shared_context 'ProjectPolicy context' do download_code fork_project create_project_snippet update_issue admin_issue admin_label admin_list read_commit_status read_build read_container_image read_pipeline read_environment read_deployment - read_merge_request download_wiki_code read_sentry_issue + read_merge_request download_wiki_code read_sentry_issue read_release ] end -- cgit v1.2.1 From 70f55be08114e31b1e2eae38bb3ab6d781dce68a Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Tue, 2 Apr 2019 18:47:35 +0300 Subject: Extract EE specific files/lines for quick actions tests --- spec/services/notes/quick_actions_service_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 7d2b6d5b8a7..9efdf96bc64 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -185,6 +185,7 @@ describe Notes::QuickActionsService do end before do + stub_licensed_features(multiple_issue_assignees: false) project.add_maintainer(maintainer) project.add_maintainer(assignee) end -- cgit v1.2.1 From 46fdec84dd787c5311cf45fcf1967a8907e62458 Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Wed, 3 Apr 2019 08:27:16 +0000 Subject: Extend timezone dropdown Adds optional paramters to the constructor to allow reuse of the timezone dropdown on other pages --- .../shared/components/timezone_dropdown_spec.js | 167 +++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js (limited to 'spec') diff --git a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js new file mode 100644 index 00000000000..a89952ee435 --- /dev/null +++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js @@ -0,0 +1,167 @@ +import $ from 'jquery'; +import GLDropdown from '~/gl_dropdown'; // eslint-disable-line no-unused-vars +import TimezoneDropdown, { + formatUtcOffset, + formatTimezone, +} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown'; + +describe('Timezone Dropdown', function() { + preloadFixtures('pipeline_schedules/edit.html'); + + let $inputEl = null; + let $dropdownEl = null; + let $wrapper = null; + const tzListSel = '.dropdown-content ul li a.is-active'; + + describe('Initialize', () => { + describe('with dropdown already loaded', () => { + beforeEach(() => { + loadFixtures('pipeline_schedules/edit.html'); + $wrapper = $('.dropdown'); + $inputEl = $('#schedule_cron_timezone'); + $dropdownEl = $('.js-timezone-dropdown'); + + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + }); + }); + + it('can take an $inputEl in the constructor', () => { + const tzStr = '[UTC + 5.5] Sri Jayawardenepura'; + const tzValue = 'Asia/Colombo'; + + expect($inputEl.val()).toBe('UTC'); + + $(`${tzListSel}:contains('${tzStr}')`, $wrapper).trigger('click'); + + const val = $inputEl.val(); + + expect(val).toBe(tzValue); + expect(val).not.toBe('UTC'); + }); + + it('will format data array of timezones into a list of offsets', () => { + const data = $dropdownEl.data('data'); + const formatted = $wrapper.find(tzListSel).text(); + + data.forEach(item => { + expect(formatted).toContain(formatTimezone(item)); + }); + }); + + it('will default the timezone to UTC', () => { + const tz = $inputEl.val(); + + expect(tz).toBe('UTC'); + }); + }); + + describe('without dropdown loaded', () => { + beforeEach(() => { + loadFixtures('pipeline_schedules/edit.html'); + $wrapper = $('.dropdown'); + $inputEl = $('#schedule_cron_timezone'); + $dropdownEl = $('.js-timezone-dropdown'); + }); + + it('will populate the list of UTC offsets after the dropdown is loaded', () => { + expect($wrapper.find(tzListSel).length).toEqual(0); + + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + }); + + expect($wrapper.find(tzListSel).length).toEqual($($dropdownEl).data('data').length); + }); + + it('will call a provided handler when a new timezone is selected', () => { + const onSelectTimezone = jasmine.createSpy('onSelectTimezoneMock'); + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + onSelectTimezone, + }); + + $wrapper + .find(tzListSel) + .first() + .trigger('click'); + + expect(onSelectTimezone).toHaveBeenCalled(); + }); + }); + }); + + describe('formatUtcOffset', () => { + it('will convert negative utc offsets in seconds to hours and minutes', () => { + expect(formatUtcOffset(-21600)).toEqual('- 6'); + }); + + it('will convert positive utc offsets in seconds to hours and minutes', () => { + expect(formatUtcOffset(25200)).toEqual('+ 7'); + expect(formatUtcOffset(49500)).toEqual('+ 13.75'); + }); + + it('will return 0 when given a string', () => { + expect(formatUtcOffset('BLAH')).toEqual('0'); + expect(formatUtcOffset('$%$%')).toEqual('0'); + }); + + it('will return 0 when given an array', () => { + expect(formatUtcOffset(['an', 'array'])).toEqual('0'); + }); + + it('will return 0 when given an object', () => { + expect(formatUtcOffset({ some: '', object: '' })).toEqual('0'); + }); + + it('will return 0 when given null', () => { + expect(formatUtcOffset(null)).toEqual('0'); + }); + + it('will return 0 when given undefined', () => { + expect(formatUtcOffset(undefined)).toEqual('0'); + }); + + it('will return 0 when given empty input', () => { + expect(formatUtcOffset('')).toEqual('0'); + }); + }); + + describe('formatTimezone', () => { + it('given name: "Chatham Is.", offset: "49500", will format for display as "[UTC + 13.75] Chatham Is."', () => { + expect( + formatTimezone({ + name: 'Chatham Is.', + offset: 49500, + identifier: 'Pacific/Chatham', + }), + ).toEqual('[UTC + 13.75] Chatham Is.'); + }); + + it('given name: "Saskatchewan", offset: "-21600", will format for display as "[UTC - 6] Saskatchewan"', () => { + expect( + formatTimezone({ + name: 'Saskatchewan', + offset: -21600, + identifier: 'America/Regina', + }), + ).toEqual('[UTC - 6] Saskatchewan'); + }); + + it('given name: "Accra", offset: "0", will format for display as "[UTC 0] Accra"', () => { + expect( + formatTimezone({ + name: 'Accra', + offset: 0, + identifier: 'Africa/Accra', + }), + ).toEqual('[UTC 0] Accra'); + }); + }); +}); -- cgit v1.2.1 From ffbc66cc0bb62b962779aea4d4b0c06dcf12078e Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Tue, 2 Apr 2019 04:14:19 +0300 Subject: Redirect to edit page on group transfer failure --- spec/controllers/groups_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index b2e6df6060a..38d7240ea81 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -616,7 +616,7 @@ describe GroupsController do end it 'should redirect to the current path' do - expect(response).to render_template(:edit) + expect(response).to redirect_to(edit_group_path(group)) end end -- cgit v1.2.1 From ab1e1b55a84ffc6b09233a6831be9bdc77c05115 Mon Sep 17 00:00:00 2001 From: syasonik Date: Fri, 15 Mar 2019 19:20:59 +0800 Subject: Specify time window for additional metrics api Adds support for start and end parameters in the #additional_metrics endpoint of the EnvironmentsController. start and end are meant to be unix timestamps, per the Prometheus API (as the consumer of this endpoint will eventually be transitioned to a prometheus endpoint). This functionality is behind the :metrics_time_window feature flag for development. --- .../projects/environments_controller_spec.rb | 34 ++++++++++++++++-- .../additional_metrics_environment_query_spec.rb | 30 ++++++++++++++-- spec/models/concerns/prometheus_adapter_spec.rb | 42 ++++++++++++++++++++++ spec/models/environment_spec.rb | 23 +++++++++--- 4 files changed, 121 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 36ce1119100..2dca2c3976f 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -392,7 +392,7 @@ describe Projects::EnvironmentsController do context 'when requesting metrics as JSON' do it 'returns a metrics JSON document' do - get :additional_metrics, params: environment_params(format: :json) + additional_metrics expect(response).to have_gitlab_http_status(204) expect(json_response).to eq({}) @@ -412,7 +412,7 @@ describe Projects::EnvironmentsController do end it 'returns a metrics JSON document' do - get :additional_metrics, params: environment_params(format: :json) + additional_metrics expect(response).to be_ok expect(json_response['success']).to be(true) @@ -420,6 +420,32 @@ describe Projects::EnvironmentsController do expect(json_response['last_update']).to eq(42) end end + + context 'when only one time param is provided' do + context 'when :metrics_time_window feature flag is disabled' do + before do + stub_feature_flags(metrics_time_window: false) + expect(environment).to receive(:additional_metrics).with(no_args).and_return(nil) + end + + it 'returns a time-window agnostic response' do + additional_metrics(start: '1552647300.651094') + + expect(response).to have_gitlab_http_status(204) + expect(json_response).to eq({}) + end + end + + it 'raises an error when start is missing' do + expect { additional_metrics(start: '1552647300.651094') } + .to raise_error(ActionController::ParameterMissing) + end + + it 'raises an error when end is missing' do + expect { additional_metrics(start: '1552647300.651094') } + .to raise_error(ActionController::ParameterMissing) + end + end end describe 'GET #search' do @@ -500,4 +526,8 @@ describe Projects::EnvironmentsController do project_id: project, id: environment.id) end + + def additional_metrics(opts = {}) + get :additional_metrics, params: environment_params(format: :json, **opts) + end end diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb index 5a88b23aa82..a6589f0c0a3 100644 --- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb @@ -9,9 +9,35 @@ describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do let(:query_params) { [environment.id] } it 'queries using specific time' do - expect(client).to receive(:query_range).with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f) - + expect(client).to receive(:query_range) + .with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f) expect(query_result).not_to be_nil end + + context 'when start and end time parameters are provided' do + let(:query_params) { [environment.id, start_time, end_time] } + + context 'as unix timestamps' do + let(:start_time) { 4.hours.ago.to_f } + let(:end_time) { 2.hours.ago.to_f } + + it 'queries using the provided times' do + expect(client).to receive(:query_range) + .with(anything, start: start_time, stop: end_time) + expect(query_result).not_to be_nil + end + end + + context 'as Date/Time objects' do + let(:start_time) { 4.hours.ago } + let(:end_time) { 2.hours.ago } + + it 'queries using the provided times converted to unix' do + expect(client).to receive(:query_range) + .with(anything, start: start_time.to_f, stop: end_time.to_f) + expect(query_result).not_to be_nil + end + end + end end end diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb index db20a8b4701..25a2d290f76 100644 --- a/spec/models/concerns/prometheus_adapter_spec.rb +++ b/spec/models/concerns/prometheus_adapter_spec.rb @@ -77,6 +77,28 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do end end end + + describe 'additional_metrics' do + let(:additional_metrics_environment_query) { Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery } + let(:environment) { build_stubbed(:environment, slug: 'env-slug') } + let(:time_window) { [1552642245.067, 1552642095.831] } + + around do |example| + Timecop.freeze { example.run } + end + + context 'with valid data' do + subject { service.query(:additional_metrics_environment, environment, *time_window) } + + before do + stub_reactive_cache(service, prometheus_data, additional_metrics_environment_query, environment.id, *time_window) + end + + it 'returns reactive data' do + expect(subject).to eq(prometheus_data) + end + end + end end describe '#calculate_reactive_cache' do @@ -121,4 +143,24 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do end end end + + describe '#build_query_args' do + subject { service.build_query_args(*args) } + + context 'when active record models are included' do + let(:args) { [double(:environment, id: 12)] } + + it 'serializes by id' do + is_expected.to eq [12] + end + end + + context 'when args are safe for serialization' do + let(:args) { ['stringy arg', 5, 6.0, :symbolic_arg] } + + it 'does nothing' do + is_expected.to eq args + end + end + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ca5eed60b56..cfe7c7ef0b0 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -687,7 +687,8 @@ describe Environment do describe '#additional_metrics' do let(:project) { create(:prometheus_project) } - subject { environment.additional_metrics } + let(:metric_params) { [] } + subject { environment.additional_metrics(*metric_params) } context 'when the environment has additional metrics' do before do @@ -695,12 +696,26 @@ describe Environment do end it 'returns the additional metrics from the deployment service' do - expect(environment.prometheus_adapter).to receive(:query) - .with(:additional_metrics_environment, environment) - .and_return(:fake_metrics) + expect(environment.prometheus_adapter) + .to receive(:query) + .with(:additional_metrics_environment, environment) + .and_return(:fake_metrics) is_expected.to eq(:fake_metrics) end + + context 'when time window arguments are provided' do + let(:metric_params) { [1552642245.067, Time.now] } + + it 'queries with the expected parameters' do + expect(environment.prometheus_adapter) + .to receive(:query) + .with(:additional_metrics_environment, environment, *metric_params.map(&:to_f)) + .and_return(:fake_metrics) + + is_expected.to eq(:fake_metrics) + end + end end context 'when the environment does not have metrics' do -- cgit v1.2.1 From b87b4d75b3da1deef9c9ba219a570dab339976d7 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 12:28:07 +0300 Subject: Move tag quick action spec to shared example --- .../commits/user_uses_quick_actions_spec.rb | 23 +-------------------- .../commit/tag_quick_action_shared_examples.rb | 24 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 22 deletions(-) (limited to 'spec') diff --git a/spec/features/commits/user_uses_quick_actions_spec.rb b/spec/features/commits/user_uses_quick_actions_spec.rb index 9a4b7bd2444..4b7e7465df1 100644 --- a/spec/features/commits/user_uses_quick_actions_spec.rb +++ b/spec/features/commits/user_uses_quick_actions_spec.rb @@ -22,27 +22,6 @@ describe 'Commit > User uses quick actions', :js do let(:tag_message) { 'Stable release' } let(:truncated_commit_sha) { Commit.truncate_sha(commit.sha) } - it 'tags this commit' do - add_note("/tag #{tag_name} #{tag_message}") - - expect(page).to have_content 'Commands applied' - expect(page).to have_content "tagged commit #{truncated_commit_sha}" - expect(page).to have_content tag_name - - visit project_tag_path(project, tag_name) - expect(page).to have_content tag_name - expect(page).to have_content tag_message - expect(page).to have_content truncated_commit_sha - end - - describe 'preview', :js do - it 'removes quick action from note and explains it' do - preview_note("/tag #{tag_name} #{tag_message}") - - expect(page).not_to have_content '/tag' - expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"} - expect(page).to have_content tag_name - end - end + it_behaves_like 'tag quick action' end end diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb index 4604d867507..b337a1c18d8 100644 --- a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb @@ -1,4 +1,28 @@ # frozen_string_literal: true shared_examples 'tag quick action' do + context "post note to existing commit" do + it 'tags this commit' do + add_note("/tag #{tag_name} #{tag_message}") + + expect(page).to have_content 'Commands applied' + expect(page).to have_content "tagged commit #{truncated_commit_sha}" + expect(page).to have_content tag_name + + visit project_tag_path(project, tag_name) + expect(page).to have_content tag_name + expect(page).to have_content tag_message + expect(page).to have_content truncated_commit_sha + end + end + + context 'preview', :js do + it 'removes quick action from note and explains it' do + preview_note("/tag #{tag_name} #{tag_message}") + + expect(page).not_to have_content '/tag' + expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"} + expect(page).to have_content tag_name + end + end end -- cgit v1.2.1 From 6ee1d8cf7778ecef0997c10f22b18ab4b61e9b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Wed, 3 Apr 2019 09:50:54 +0000 Subject: Add port section to CI Image object In order to implement https://gitlab.com/gitlab-org/gitlab-ee/issues/10179 we need several modifications on the CI config file. We are adding a new ports section in the default Image object. Each of these ports will accept: number, protocol and name. By default this new configuration will be only enabled in the Web IDE config file. --- spec/lib/api/entities/job_request/image_spec.rb | 31 ++++ spec/lib/api/entities/job_request/port_spec.rb | 22 +++ spec/lib/gitlab/ci/build/image_spec.rb | 25 +++- spec/lib/gitlab/ci/build/port_spec.rb | 27 ++++ spec/lib/gitlab/ci/config/entry/image_spec.rb | 46 +++++- spec/lib/gitlab/ci/config/entry/port_spec.rb | 173 +++++++++++++++++++++++ spec/lib/gitlab/ci/config/entry/ports_spec.rb | 70 +++++++++ spec/lib/gitlab/ci/config/entry/service_spec.rb | 70 ++++++++- spec/lib/gitlab/ci/config/entry/services_spec.rb | 87 ++++++++++++ spec/lib/gitlab/ci/config_spec.rb | 57 ++++++++ spec/lib/gitlab/ci/yaml_processor_spec.rb | 4 +- spec/requests/api/runner_spec.rb | 56 +++++++- spec/services/ci/create_pipeline_service_spec.rb | 50 +++++++ 13 files changed, 703 insertions(+), 15 deletions(-) create mode 100644 spec/lib/api/entities/job_request/image_spec.rb create mode 100644 spec/lib/api/entities/job_request/port_spec.rb create mode 100644 spec/lib/gitlab/ci/build/port_spec.rb create mode 100644 spec/lib/gitlab/ci/config/entry/port_spec.rb create mode 100644 spec/lib/gitlab/ci/config/entry/ports_spec.rb (limited to 'spec') diff --git a/spec/lib/api/entities/job_request/image_spec.rb b/spec/lib/api/entities/job_request/image_spec.rb new file mode 100644 index 00000000000..092c181ae9c --- /dev/null +++ b/spec/lib/api/entities/job_request/image_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Entities::JobRequest::Image do + let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]} + let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports)} + let(:entity) { described_class.new(image) } + + subject { entity.as_json } + + it 'returns the image name' do + expect(subject[:name]).to eq 'image_name' + end + + it 'returns the entrypoint' do + expect(subject[:entrypoint]).to eq ['foo'] + end + + it 'returns the ports' do + expect(subject[:ports]).to eq ports + end + + context 'when the ports param is nil' do + let(:ports) { nil } + + it 'does not return the ports' do + expect(subject[:ports]).to be_nil + end + end +end diff --git a/spec/lib/api/entities/job_request/port_spec.rb b/spec/lib/api/entities/job_request/port_spec.rb new file mode 100644 index 00000000000..40ab4cd6231 --- /dev/null +++ b/spec/lib/api/entities/job_request/port_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::API::Entities::JobRequest::Port do + let(:port) { double(number: 80, protocol: 'http', name: 'name')} + let(:entity) { described_class.new(port) } + + subject { entity.as_json } + + it 'returns the port number' do + expect(subject[:number]).to eq 80 + end + + it 'returns if the port protocol' do + expect(subject[:protocol]).to eq 'http' + end + + it 'returns the port name' do + expect(subject[:name]).to eq 'name' + end +end diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb index 773a52cdfbc..6e20e0ef5c3 100644 --- a/spec/lib/gitlab/ci/build/image_spec.rb +++ b/spec/lib/gitlab/ci/build/image_spec.rb @@ -18,11 +18,16 @@ describe Gitlab::Ci::Build::Image do it 'populates fabricated object with the proper name attribute' do expect(subject.name).to eq(image_name) end + + it 'does not populate the ports' do + expect(subject.ports).to be_empty + end end context 'when image is defined as hash' do let(:entrypoint) { '/bin/sh' } - let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint } } ) } + + let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint, ports: [80] } } ) } it 'fabricates an object of the proper class' do is_expected.to be_kind_of(described_class) @@ -32,6 +37,13 @@ describe Gitlab::Ci::Build::Image do expect(subject.name).to eq(image_name) expect(subject.entrypoint).to eq(entrypoint) end + + it 'populates the ports' do + port = subject.ports.first + expect(port.number).to eq 80 + expect(port.protocol).to eq 'http' + expect(port.name).to eq 'default_port' + end end context 'when image name is empty' do @@ -67,6 +79,10 @@ describe Gitlab::Ci::Build::Image do expect(subject.first).to be_kind_of(described_class) expect(subject.first.name).to eq(service_image_name) end + + it 'does not populate the ports' do + expect(subject.first.ports).to be_empty + end end context 'when service is defined as hash' do @@ -75,7 +91,7 @@ describe Gitlab::Ci::Build::Image do let(:service_command) { 'sleep 30' } let(:job) do create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint, - alias: service_alias, command: service_command }] }) + alias: service_alias, command: service_command, ports: [80] }] }) end it 'fabricates an non-empty array of objects' do @@ -89,6 +105,11 @@ describe Gitlab::Ci::Build::Image do expect(subject.first.entrypoint).to eq(service_entrypoint) expect(subject.first.alias).to eq(service_alias) expect(subject.first.command).to eq(service_command) + + port = subject.first.ports.first + expect(port.number).to eq 80 + expect(port.protocol).to eq 'http' + expect(port.name).to eq 'default_port' end end diff --git a/spec/lib/gitlab/ci/build/port_spec.rb b/spec/lib/gitlab/ci/build/port_spec.rb new file mode 100644 index 00000000000..1413780dfa6 --- /dev/null +++ b/spec/lib/gitlab/ci/build/port_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Build::Port do + subject { described_class.new(port) } + + context 'when port is defined as an integer' do + let(:port) { 80 } + + it 'populates the object' do + expect(subject.number).to eq 80 + expect(subject.protocol).to eq described_class::DEFAULT_PORT_PROTOCOL + expect(subject.name).to eq described_class::DEFAULT_PORT_NAME + end + end + + context 'when port is defined as hash' do + let(:port) { { number: 80, protocol: 'https', name: 'port_name' } } + + it 'populates the object' do + expect(subject.number).to eq 80 + expect(subject.protocol).to eq 'https' + expect(subject.name).to eq 'port_name' + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index 1a4d9ed5517..1ebdda398b9 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -35,6 +35,12 @@ describe Gitlab::Ci::Config::Entry::Image do expect(entry.entrypoint).to be_nil end end + + describe '#ports' do + it "returns image's ports" do + expect(entry.ports).to be_nil + end + end end context 'when configuration is a hash' do @@ -69,6 +75,38 @@ describe Gitlab::Ci::Config::Entry::Image do expect(entry.entrypoint).to eq %w(/bin/sh run) end end + + context 'when configuration has ports' do + let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } + let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run), ports: ports } } + let(:entry) { described_class.new(config, { with_image_ports: image_ports }) } + let(:image_ports) { false } + + context 'when with_image_ports metadata is not enabled' do + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + expect(entry.errors).to include("image config contains disallowed keys: ports") + end + end + end + + context 'when with_image_ports metadata is enabled' do + let(:image_ports) { true } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#ports' do + it "returns image's ports" do + expect(entry.ports).to eq ports + end + end + end + end end context 'when entry value is not correct' do @@ -76,8 +114,8 @@ describe Gitlab::Ci::Config::Entry::Image do describe '#errors' do it 'saves errors' do - expect(entry.errors) - .to include 'image config should be a hash or a string' + expect(entry.errors.first) + .to match /config should be a hash or a string/ end end @@ -93,8 +131,8 @@ describe Gitlab::Ci::Config::Entry::Image do describe '#errors' do it 'saves errors' do - expect(entry.errors) - .to include 'image config contains unknown keys: non_existing' + expect(entry.errors.first) + .to match /config contains unknown keys: non_existing/ end end diff --git a/spec/lib/gitlab/ci/config/entry/port_spec.rb b/spec/lib/gitlab/ci/config/entry/port_spec.rb new file mode 100644 index 00000000000..5f8f294334e --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/port_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Port do + let(:entry) { described_class.new(config) } + + before do + entry.compose! + end + + context 'when configuration is a string' do + let(:config) { 80 } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid hash' do + expect(entry.value).to eq(number: 80) + end + end + + describe '#number' do + it "returns port number" do + expect(entry.number).to eq 80 + end + end + + describe '#protocol' do + it "is nil" do + expect(entry.protocol).to be_nil + end + end + + describe '#name' do + it "is nil" do + expect(entry.name).to be_nil + end + end + end + + context 'when configuration is a hash' do + context 'with the complete hash' do + let(:config) do + { number: 80, + protocol: 'http', + name: 'foobar' } + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid hash' do + expect(entry.value).to eq config + end + end + + describe '#number' do + it "returns port number" do + expect(entry.number).to eq 80 + end + end + + describe '#protocol' do + it "returns port protocol" do + expect(entry.protocol).to eq 'http' + end + end + + describe '#name' do + it "returns port name" do + expect(entry.name).to eq 'foobar' + end + end + end + + context 'with only the port number' do + let(:config) { { number: 80 } } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid hash' do + expect(entry.value).to eq(number: 80) + end + end + + describe '#number' do + it "returns port number" do + expect(entry.number).to eq 80 + end + end + + describe '#protocol' do + it "is nil" do + expect(entry.protocol).to be_nil + end + end + + describe '#name' do + it "is nil" do + expect(entry.name).to be_nil + end + end + end + + context 'without the number' do + let(:config) { { protocol: 'http' } } + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + end + + context 'when configuration is invalid' do + let(:config) { '80' } + + describe '#valid?' do + it 'is valid' do + expect(entry).not_to be_valid + end + end + end + + context 'when protocol' do + let(:config) { { number: 80, protocol: protocol, name: 'foobar' } } + + context 'is http' do + let(:protocol) { 'http' } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'is https' do + let(:protocol) { 'https' } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'is neither http nor https' do + let(:protocol) { 'foo' } + + describe '#valid?' do + it 'is invalid' do + expect(entry.errors).to include("port protocol should be http or https") + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/ports_spec.rb b/spec/lib/gitlab/ci/config/entry/ports_spec.rb new file mode 100644 index 00000000000..2063bd1d86c --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/ports_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Ports do + let(:entry) { described_class.new(config) } + + before do + entry.compose! + end + + context 'when configuration is valid' do + let(:config) { [{ number: 80, protocol: 'http', name: 'foobar' }] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid array' do + expect(entry.value).to eq(config) + end + end + end + + context 'when configuration is invalid' do + let(:config) { 'postgresql:9.5' } + + describe '#valid?' do + it 'is invalid' do + expect(entry).not_to be_valid + end + end + + context 'when any of the ports' do + before do + expect(entry).not_to be_valid + expect(entry.errors.count).to eq 1 + end + + context 'have the same name' do + let(:config) do + [{ number: 80, protocol: 'http', name: 'foobar' }, + { number: 81, protocol: 'http', name: 'foobar' }] + end + + describe '#valid?' do + it 'is invalid' do + expect(entry.errors.first).to match /each port name must be different/ + end + end + end + + context 'have the same port' do + let(:config) do + [{ number: 80, protocol: 'http', name: 'foobar' }, + { number: 80, protocol: 'http', name: 'foobar1' }] + end + + describe '#valid?' do + it 'is invalid' do + expect(entry.errors.first).to match /each port number can only be referenced once/ + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index 9ebf947a751..d5bd139b5f1 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -39,6 +39,12 @@ describe Gitlab::Ci::Config::Entry::Service do expect(entry.command).to be_nil end end + + describe '#ports' do + it "returns service's ports" do + expect(entry.ports).to be_nil + end + end end context 'when configuration is a hash' do @@ -81,6 +87,40 @@ describe Gitlab::Ci::Config::Entry::Service do expect(entry.entrypoint).to eq %w(/bin/sh run) end end + + context 'when configuration has ports' do + let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } + let(:config) do + { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports } + end + let(:entry) { described_class.new(config, { with_image_ports: image_ports }) } + let(:image_ports) { false } + + context 'when with_image_ports metadata is not enabled' do + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + expect(entry.errors).to include("service config contains disallowed keys: ports") + end + end + end + + context 'when with_image_ports metadata is enabled' do + let(:image_ports) { true } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#ports' do + it "returns image's ports" do + expect(entry.ports).to eq ports + end + end + end + end end context 'when entry value is not correct' do @@ -88,8 +128,8 @@ describe Gitlab::Ci::Config::Entry::Service do describe '#errors' do it 'saves errors' do - expect(entry.errors) - .to include 'service config should be a hash or a string' + expect(entry.errors.first) + .to match /config should be a hash or a string/ end end @@ -105,8 +145,8 @@ describe Gitlab::Ci::Config::Entry::Service do describe '#errors' do it 'saves errors' do - expect(entry.errors) - .to include 'service config contains unknown keys: non_existing' + expect(entry.errors.first) + .to match /config contains unknown keys: non_existing/ end end @@ -116,4 +156,26 @@ describe Gitlab::Ci::Config::Entry::Service do end end end + + context 'when service has ports' do + let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } + let(:config) do + { name: 'postgresql:9.5', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports } + end + + it 'alias field is mandatory' do + expect(entry).not_to be_valid + expect(entry.errors).to include("service alias can't be blank") + end + end + + context 'when service does not have ports' do + let(:config) do + { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) } + end + + it 'alias field is optional' do + expect(entry).to be_valid + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb index 7c4319aee63..d5a1316f665 100644 --- a/spec/lib/gitlab/ci/config/entry/services_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb @@ -32,4 +32,91 @@ describe Gitlab::Ci::Config::Entry::Services do end end end + + context 'when configuration has ports' do + let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } + let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }] } + let(:entry) { described_class.new(config, { with_image_ports: image_ports }) } + let(:image_ports) { false } + + context 'when with_image_ports metadata is not enabled' do + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + expect(entry.errors).to include("service config contains disallowed keys: ports") + end + end + end + + context 'when with_image_ports metadata is enabled' do + let(:image_ports) { true } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid array' do + expect(entry.value).to eq([{ name: 'postgresql:9.5' }, { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }]) + end + end + + describe 'services alias' do + context 'when they are not unique' do + let(:config) do + ['postgresql:9.5', + { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] }, + { name: 'ruby', alias: 'postgres_old', ports: [81] }] + end + + describe '#valid?' do + it 'is invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include("services config alias must be unique in services with ports") + end + end + end + + context 'when they are unique' do + let(:config) do + ['postgresql:9.5', + { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] }, + { name: 'ruby', alias: 'ruby', ports: [81] }] + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when one of the duplicated alias is in a service without ports' do + let(:config) do + ['postgresql:9.5', + { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] }, + { name: 'ruby', alias: 'postgres_old' }] + end + + it 'is valid' do + expect(entry).to be_valid + end + end + + context 'when there are not any ports' do + let(:config) do + ['postgresql:9.5', + { name: 'postgresql:9.1', alias: 'postgres_old' }, + { name: 'ruby', alias: 'postgres_old' }] + end + + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 18f255c1ab7..00b2753c5fc 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -123,6 +123,63 @@ describe Gitlab::Ci::Config do ) end end + + context 'when ports have been set' do + context 'in the main image' do + let(:yml) do + <<-EOS + image: + name: ruby:2.2 + ports: + - 80 + EOS + end + + it 'raises an error' do + expect(config.errors).to include("image config contains disallowed keys: ports") + end + end + + context 'in the job image' do + let(:yml) do + <<-EOS + image: ruby:2.2 + + test: + script: rspec + image: + name: ruby:2.2 + ports: + - 80 + EOS + end + + it 'raises an error' do + expect(config.errors).to include("jobs:test:image config contains disallowed keys: ports") + end + end + + context 'in the services' do + let(:yml) do + <<-EOS + image: ruby:2.2 + + test: + script: rspec + image: ruby:2.2 + services: + - name: test + alias: test + ports: + - 80 + EOS + end + + it 'raises an error' do + expect(config.errors).to include("jobs:test:services:service config contains disallowed keys: ports") + end + end + end end context "when using 'include' directive" do diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 29638ef47c5..63a0d54dcfc 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1233,7 +1233,7 @@ module Gitlab config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "services:service config should be a hash or a string") end it "returns errors if job services parameter is not an array" do @@ -1247,7 +1247,7 @@ module Gitlab config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:services:service config should be a hash or a string") end it "returns error if job configuration is invalid" do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 3ccedd8dd06..5fdc7c64030 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -470,11 +470,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(json_response['token']).to eq(job.token) expect(json_response['job_info']).to eq(expected_job_info) expect(json_response['git_info']).to eq(expected_git_info) - expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' }) + expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh', 'ports' => [] }) expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil, - 'alias' => nil, 'command' => nil }, + 'alias' => nil, 'command' => nil, 'ports' => [] }, { 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh', - 'alias' => 'docker', 'command' => 'sleep 30' }]) + 'alias' => 'docker', 'command' => 'sleep 30', 'ports' => [] }]) expect(json_response['steps']).to eq(expected_steps) expect(json_response['artifacts']).to eq(expected_artifacts) expect(json_response['cache']).to eq(expected_cache) @@ -853,6 +853,56 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end end + describe 'port support' do + let(:job) { create(:ci_build, pipeline: pipeline, options: options) } + + context 'when job image has ports' do + let(:options) do + { + image: { + name: 'ruby', + ports: [80] + }, + services: ['mysql'] + } + end + + it 'returns the image ports' do + request_job + + expect(response).to have_http_status(:created) + expect(json_response).to include( + 'id' => job.id, + 'image' => a_hash_including('name' => 'ruby', 'ports' => [{ 'number' => 80, 'protocol' => 'http', 'name' => 'default_port' }]), + 'services' => all(a_hash_including('name' => 'mysql'))) + end + end + + context 'when job services settings has ports' do + let(:options) do + { + image: 'ruby', + services: [ + { + name: 'tomcat', + ports: [{ number: 8081, protocol: 'http', name: 'custom_port' }] + } + ] + } + end + + it 'returns the service ports' do + request_job + + expect(response).to have_http_status(:created) + expect(json_response).to include( + 'id' => job.id, + 'image' => a_hash_including('name' => 'ruby'), + 'services' => all(a_hash_including('name' => 'tomcat', 'ports' => [{ 'number' => 8081, 'protocol' => 'http', 'name' => 'custom_port' }]))) + end + end + end + def request_job(token = runner.token, **params) new_params = params.merge(token: token, last_update: last_update) post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent } diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 24707cd2d41..866d709d446 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -306,6 +306,56 @@ describe Ci::CreatePipelineService do it_behaves_like 'a failed pipeline' end + + context 'when config has ports' do + context 'in the main image' do + let(:ci_yaml) do + <<-EOS + image: + name: ruby:2.2 + ports: + - 80 + EOS + end + + it_behaves_like 'a failed pipeline' + end + + context 'in the job image' do + let(:ci_yaml) do + <<-EOS + image: ruby:2.2 + + test: + script: rspec + image: + name: ruby:2.2 + ports: + - 80 + EOS + end + + it_behaves_like 'a failed pipeline' + end + + context 'in the service' do + let(:ci_yaml) do + <<-EOS + image: ruby:2.2 + + test: + script: rspec + image: ruby:2.2 + services: + - name: test + ports: + - 80 + EOS + end + + it_behaves_like 'a failed pipeline' + end + end end context 'when commit contains a [ci skip] directive' do -- cgit v1.2.1 From 9fe8106e342dc89429d6fc260a5dfb6763cb6c09 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 3 Apr 2019 12:20:26 +0200 Subject: Add pipeline bridge presenter --- spec/presenters/ci/bridge_presenter_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 spec/presenters/ci/bridge_presenter_spec.rb (limited to 'spec') diff --git a/spec/presenters/ci/bridge_presenter_spec.rb b/spec/presenters/ci/bridge_presenter_spec.rb new file mode 100644 index 00000000000..986818a7b9e --- /dev/null +++ b/spec/presenters/ci/bridge_presenter_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Ci::BridgePresenter do + set(:project) { create(:project) } + set(:pipeline) { create(:ci_pipeline, project: project) } + set(:bridge) { create(:ci_bridge, pipeline: pipeline, status: :failed) } + + subject(:presenter) do + described_class.new(bridge) + end + + it 'presents information about recoverable state' do + expect(presenter).to be_recoverable + end +end -- cgit v1.2.1 From ebbcc17f243337581536bd1f34dc7d6b5712ebb9 Mon Sep 17 00:00:00 2001 From: Kieran Andrews Date: Wed, 3 Apr 2019 11:38:46 +0000 Subject: Merge branch 'feature/webide_escaping' of gitlab.com:hiddentiger/gitlab-ce into feature/webide_escaping --- spec/frontend/ide/lib/files_spec.js | 17 +++++++++-------- spec/helpers/blob_helper_spec.rb | 13 +++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js index fe791aa2b74..aa1fa0373db 100644 --- a/spec/frontend/ide/lib/files_spec.js +++ b/spec/frontend/ide/lib/files_spec.js @@ -1,5 +1,5 @@ import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils'; -import { decorateFiles, splitParent } from '~/ide/lib/files'; +import { decorateFiles, splitParent, escapeFileUrl } from '~/ide/lib/files'; import { decorateData } from '~/ide/stores/utils'; const TEST_BRANCH_ID = 'lorem-ipsum'; @@ -20,7 +20,7 @@ const createEntries = paths => { id: path, name, path, - url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${path}`), + url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${escapeFileUrl(path)}`), type, previewMode: viewerInformationForPath(path), parentPath: parent, @@ -28,7 +28,7 @@ const createEntries = paths => { ? parentEntry.url : createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}`), }), - tree: children.map(childName => jasmine.objectContaining({ name: childName })), + tree: children.map(childName => expect.objectContaining({ name: childName })), }; return acc; @@ -36,10 +36,10 @@ const createEntries = paths => { const entries = paths.reduce(createEntry, {}); - // Wrap entries in jasmine.objectContaining. + // Wrap entries in expect.objectContaining. // We couldn't do this earlier because we still need to select properties from parent entries. return Object.keys(entries).reduce((acc, key) => { - acc[key] = jasmine.objectContaining(entries[key]); + acc[key] = expect.objectContaining(entries[key]); return acc; }, {}); @@ -47,13 +47,14 @@ const createEntries = paths => { describe('IDE lib decorate files', () => { it('creates entries and treeList', () => { - const data = ['app/assets/apples/foo.js', 'app/bugs.js', 'README.md']; + const data = ['app/assets/apples/foo.js', 'app/bugs.js', 'app/#weird#file?.txt', 'README.md']; const expectedEntries = createEntries([ - { path: 'app', type: 'tree', children: ['assets', 'bugs.js'] }, + { path: 'app', type: 'tree', children: ['assets', '#weird#file?.txt', 'bugs.js'] }, { path: 'app/assets', type: 'tree', children: ['apples'] }, { path: 'app/assets/apples', type: 'tree', children: ['foo.js'] }, { path: 'app/assets/apples/foo.js', type: 'blob', children: [] }, { path: 'app/bugs.js', type: 'blob', children: [] }, + { path: 'app/#weird#file?.txt', type: 'blob', children: [] }, { path: 'README.md', type: 'blob', children: [] }, ]); @@ -64,7 +65,7 @@ describe('IDE lib decorate files', () => { }); // Here we test the keys and then each key/value individually because `expect(entries).toEqual(expectedEntries)` - // was taking a very long time for some reason. Probably due to large objects and nested `jasmine.objectContaining`. + // was taking a very long time for some reason. Probably due to large objects and nested `expect.objectContaining`. const entryKeys = Object.keys(entries); expect(entryKeys).toEqual(Object.keys(expectedEntries)); diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 2bc3933809f..6808ed86c9a 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -230,5 +230,18 @@ describe BlobHelper do expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master") end + + it 'escapes special characters' do + Rails.application.routes.default_url_options[:script_name] = nil + + expect(helper.ide_edit_path(project, "testing/#hashes", "readme.md#test")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/#hashes/-/readme.md%23test") + expect(helper.ide_edit_path(project, "testing/#hashes", "src#/readme.md#test")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/#hashes/-/src%23/readme.md%23test") + end + + it 'does not escape "/" character' do + Rails.application.routes.default_url_options[:script_name] = nil + + expect(helper.ide_edit_path(project, "testing/slashes", "readme.md/")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/slashes/-/readme.md/") + end end end -- cgit v1.2.1 From 79fdee35ecad48afe20ff32d85e1ae30ace1590b Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 15:00:18 +0300 Subject: Extract merge quick action to a shared example --- .../merge_request/user_uses_quick_actions_spec.rb | 70 +--------------------- .../merge_quick_action_shared_examples.rb | 47 +++++++++++++++ 2 files changed, 49 insertions(+), 68 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index a2b5859bd1e..56774896795 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -56,6 +56,8 @@ describe 'Merge request > User uses quick actions', :js do project.add_maintainer(user) end + it_behaves_like 'merge quick action' + describe 'toggling the WIP prefix in the title from note' do context 'when the current user can toggle the WIP prefix' do before do @@ -103,74 +105,6 @@ describe 'Merge request > User uses quick actions', :js do end end - describe 'merging the MR from the note' do - context 'when the current user can merge the MR' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it 'merges the MR' do - add_note("/merge") - - expect(page).to have_content 'Commands applied' - - expect(merge_request.reload).to be_merged - end - end - - context 'when the head diff changes in the meanwhile' do - before do - merge_request.source_branch = 'another_branch' - merge_request.save - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it 'does not merge the MR' do - add_note("/merge") - - expect(page).not_to have_content 'Your commands have been executed!' - - expect(merge_request.reload).not_to be_merged - end - end - - context 'when the current user cannot merge the MR' do - before do - project.add_guest(guest) - sign_in(guest) - visit project_merge_request_path(project, merge_request) - end - - it 'does not merge the MR' do - add_note("/merge") - - expect(page).not_to have_content 'Your commands have been executed!' - - expect(merge_request.reload).not_to be_merged - end - end - end - - describe 'adding a due date from note' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it_behaves_like 'due quick action not available' - end - - describe 'removing a due date from note' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it_behaves_like 'remove_due_date action not available' - end - describe '/target_branch command in merge request' do let(:another_project) { create(:project, :public, :repository) } let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb index 31d88183f0d..c454ddc4bba 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb @@ -1,4 +1,51 @@ # frozen_string_literal: true shared_examples 'merge quick action' do + context 'when the current user can merge the MR' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it 'merges the MR' do + add_note("/merge") + + expect(page).to have_content 'Commands applied' + + expect(merge_request.reload).to be_merged + end + end + + context 'when the head diff changes in the meanwhile' do + before do + merge_request.source_branch = 'another_branch' + merge_request.save + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it 'does not merge the MR' do + add_note("/merge") + + expect(page).not_to have_content 'Your commands have been executed!' + + expect(merge_request.reload).not_to be_merged + end + end + + context 'when the current user cannot merge the MR' do + before do + project.add_guest(guest) + sign_in(guest) + visit project_merge_request_path(project, merge_request) + end + + it 'does not merge the MR' do + add_note("/merge") + + expect(page).not_to have_content 'Your commands have been executed!' + + expect(merge_request.reload).not_to be_merged + end + end end -- cgit v1.2.1 From 5725be9508df7922c77cfab4a6c5501548e5e3ca Mon Sep 17 00:00:00 2001 From: Bastian Blank Date: Sat, 30 Mar 2019 00:50:02 +0100 Subject: Display maximum artifact size from runtime config The maximum artifact size was moved into runtime config some time ago. Update the instance configuration code to read this value. --- spec/models/instance_configuration_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb index e65f97df3c3..43954511858 100644 --- a/spec/models/instance_configuration_spec.rb +++ b/spec/models/instance_configuration_spec.rb @@ -82,6 +82,13 @@ describe InstanceConfiguration do it 'returns the key artifacts_max_size' do expect(gitlab_ci.keys).to include(:artifacts_max_size) end + + it 'returns the key artifacts_max_size with values' do + stub_application_setting(max_artifacts_size: 200) + + expect(gitlab_ci[:artifacts_max_size][:default]).to eq(100.megabytes) + expect(gitlab_ci[:artifacts_max_size][:value]).to eq(200.megabytes) + end end end end -- cgit v1.2.1 From 703d62a549ea18b678edf05d9bbb3bf09d3d7356 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 3 Apr 2019 16:10:21 -0300 Subject: Extract EE specific files/lines for Discussion spec/services This is part of moving GitLab to single codebase --- spec/services/system_note_service_spec.rb | 49 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) (limited to 'spec') diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index b917de14b2e..ff994f3b922 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -1,6 +1,9 @@ +# frozen_string_literal: true + require 'spec_helper' describe SystemNoteService do + include ProjectForksHelper include Gitlab::Routing include RepoHelpers include AssetsHelpers @@ -619,7 +622,7 @@ describe SystemNoteService do context 'commit with cross-reference from fork' do let(:author2) { create(:project_member, :reporter, user: create(:user), project: project).user } - let(:forked_project) { Projects::ForkService.new(project, author2).execute } + let(:forked_project) { fork_project(project, author2, repository: true) } let(:commit2) { forked_project.commit } before do @@ -896,6 +899,28 @@ describe SystemNoteService do end end + describe '.change_time_estimate' do + subject { described_class.change_time_estimate(noteable, project, author) } + + it_behaves_like 'a system note' do + let(:action) { 'time_tracking' } + end + + context 'with a time estimate' do + it 'sets the note text' do + noteable.update_attribute(:time_estimate, 277200) + + expect(subject.note).to eq "changed time estimate to 1w 4d 5h" + end + end + + context 'without a time estimate' do + it 'sets the note text' do + expect(subject.note).to eq "removed time estimate" + end + end + end + describe '.discussion_continued_in_issue' do let(:discussion) { create(:diff_note_on_merge_request, project: project).to_discussion } let(:merge_request) { discussion.noteable } @@ -922,28 +947,6 @@ describe SystemNoteService do end end - describe '.change_time_estimate' do - subject { described_class.change_time_estimate(noteable, project, author) } - - it_behaves_like 'a system note' do - let(:action) { 'time_tracking' } - end - - context 'with a time estimate' do - it 'sets the note text' do - noteable.update_attribute(:time_estimate, 277200) - - expect(subject.note).to eq "changed time estimate to 1w 4d 5h" - end - end - - context 'without a time estimate' do - it 'sets the note text' do - expect(subject.note).to eq "removed time estimate" - end - end - end - describe '.change_time_spent' do # We need a custom noteable in order to the shared examples to be green. let(:noteable) do -- cgit v1.2.1 From 119a5f0c86efbc2b3e853623880a1c49f9e7d352 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Tue, 26 Mar 2019 23:05:50 -0400 Subject: Specify new validation message selector Update QA selector to specify that API URL is a input type=url field --- spec/features/groups/clusters/user_spec.rb | 2 +- spec/features/projects/clusters/gcp_spec.rb | 2 +- spec/features/projects/clusters/user_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index 2410cd92e3f..b661b5cbaef 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -69,7 +69,7 @@ describe 'User Cluster', :js do end it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') + expect(page).to have_css('.gl-field-error') end end end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 9322e29d744..83e582c34f0 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -92,7 +92,7 @@ describe 'Gcp Cluster', :js do end it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') + expect(page).to have_css('.gl-field-error') end end end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 1f2f7592d8b..fe4f737a7da 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -53,7 +53,7 @@ describe 'User Cluster', :js do end it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') + expect(page).to have_css('.gl-field-error') end end end -- cgit v1.2.1 From 1d88fa9c82236e0c80a18d5e8d55284ce5472a16 Mon Sep 17 00:00:00 2001 From: Lucas Charles Date: Thu, 4 Apr 2019 00:21:44 +0000 Subject: Include DAST as dependent CI template --- spec/lib/gitlab/ci/templates/templates_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index e20a621168c..4e3681cd943 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -4,7 +4,9 @@ require 'spec_helper' describe "CI YML Templates" do ABSTRACT_TEMPLATES = %w[Serverless].freeze - PROJECT_DEPENDENT_TEMPLATES = %w[Auto-DevOps].freeze + # These templates depend on the presence of the `project` + # param to enable processing of `include:` within CI config. + PROJECT_DEPENDENT_TEMPLATES = %w[Auto-DevOps DAST].freeze def self.concrete_templates Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template| -- cgit v1.2.1 From 942fd15284df33e8e3e51beb175f38ca5cf65928 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 3 Apr 2019 22:08:19 -0300 Subject: Fix broken spec for Merge request > Revert modal --- spec/features/user_sees_revert_modal_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb index 3b48ea4786d..d2cdade88d1 100644 --- a/spec/features/user_sees_revert_modal_spec.rb +++ b/spec/features/user_sees_revert_modal_spec.rb @@ -17,12 +17,14 @@ describe 'Merge request > User sees revert modal', :js do end it 'shows the revert modal' do - expect(page).to have_content('Revert this merge request') + page.within('.modal-header') do + expect(page).to have_content 'Revert this merge request' + end end it 'closes the revert modal with escape keypress' do find('#modal-revert-commit').send_keys(:escape) - expect(page).not_to have_content('Revert this merge request') + expect(page).not_to have_selector('#modal-revert-commit', visible: true) end end -- cgit v1.2.1 From 6b7a9b7498a4c91ca9c7324a0e31d8a142fddeef Mon Sep 17 00:00:00 2001 From: Ash McKenzie Date: Thu, 28 Mar 2019 12:33:45 +1100 Subject: Allow console messages be sent to gitlab-shell Currently a no-op for CE --- spec/requests/api/internal_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 537194b8e11..6640ce2b07e 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -498,6 +498,40 @@ describe API::Internal do end end + context "console message" do + before do + project.add_developer(user) + end + + context "git pull" do + context "with no console message" do + it "has the correct payload" do + pull(key, project) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['gl_console_messages']).to eq([]) + end + end + + context "with a console message" do + let(:console_messages) { ['message for the console'] } + + it "has the correct payload" do + expect_next_instance_of(Gitlab::GitAccess) do |access| + expect(access).to receive(:check_for_console_messages) + .with('git-upload-pack') + .and_return(console_messages) + end + + pull(key, project) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['gl_console_messages']).to eq(console_messages) + end + end + end + end + context "blocked user" do let(:personal_project) { create(:project, namespace: user.namespace) } -- cgit v1.2.1 From 57f114566ad2ebd8c7a5c33497740de47a1d3f8b Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 28 Mar 2019 15:55:35 +0700 Subject: Backport attached merge request pipelines Backport changes to CE --- spec/services/merge_requests/create_service_spec.rb | 13 ------------- spec/services/merge_requests/refresh_service_spec.rb | 11 ----------- 2 files changed, 24 deletions(-) (limited to 'spec') diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 55e7b46248b..dc5d1cf2f04 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -263,19 +263,6 @@ describe MergeRequests::CreateService do expect(merge_request.actual_head_pipeline).to be_merge_request_event end end - - context "when the 'ci_merge_request_pipeline' feature flag is disabled" do - before do - stub_feature_flags(ci_merge_request_pipeline: false) - end - - it 'does not create a detached merge request pipeline' do - expect(merge_request).to be_persisted - - merge_request.reload - expect(merge_request.merge_request_pipelines.count).to eq(0) - end - end end context "when .gitlab-ci.yml does not have merge_requests keywords" do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 25cbac6d7ee..5cf3577f01f 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -230,17 +230,6 @@ describe MergeRequests::RefreshService do end.not_to change { @merge_request.merge_request_pipelines.count } end end - - context "when the 'ci_merge_request_pipeline' feature flag is disabled" do - before do - stub_feature_flags(ci_merge_request_pipeline: false) - end - - it 'does not create a detached merge request pipeline' do - expect { subject } - .not_to change { @merge_request.merge_request_pipelines.count } - end - end end context "when .gitlab-ci.yml does not have merge_requests keywords" do -- cgit v1.2.1 From 5eb2d6ea02631608095847647e02cd899613b8e1 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 4 Mar 2019 16:28:28 +1300 Subject: Show backtrace when logging to kubernetes.log Just the error message and error class alone makes it hard to determine the full context of any errors, so we need to know where the error is occuring as well. --- .../applications/check_installation_progress_service_spec.rb | 9 ++++++++- spec/services/clusters/applications/install_service_spec.rb | 12 ++++++++---- spec/services/clusters/applications/patch_service_spec.rb | 4 ++++ spec/services/clusters/applications/upgrade_service_spec.rb | 4 ++++ 4 files changed, 24 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb index 19446ce1cf8..6e37a30d142 100644 --- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb @@ -36,6 +36,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do shared_examples 'error logging' do context 'when installation raises a Kubeclient::HttpError' do let(:cluster) { create(:cluster, :provided_by_user, :project) } + let(:logger) { service.send(:logger) } before do application.update!(cluster: cluster) @@ -51,7 +52,13 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do end it 'should log error' do - expect(service.send(:logger)).to receive(:error) + expect(logger).to receive(:error) + + service.execute + end + + it 'logs error backtrace' do + expect(logger).to receive(:error).with(hash_including(backtrace: instance_of(Array))) service.execute end diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb index 018d9822d3e..8affc135bc5 100644 --- a/spec/services/clusters/applications/install_service_spec.rb +++ b/spec/services/clusters/applications/install_service_spec.rb @@ -51,6 +51,7 @@ describe Clusters::Applications::InstallService do { exception: 'Kubeclient::HttpError', message: 'system failure', + backtrace: instance_of(Array), service: 'Clusters::Applications::InstallService', app_id: application.id, project_ids: application.cluster.project_ids, @@ -61,15 +62,16 @@ describe Clusters::Applications::InstallService do expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( error, - extra: { + extra: hash_including( exception: 'Kubeclient::HttpError', message: 'system failure', + backtrace: instance_of(Array), service: 'Clusters::Applications::InstallService', app_id: application.id, project_ids: application.cluster.project_ids, group_ids: [], error_code: 500 - } + ) ) service.execute @@ -99,6 +101,7 @@ describe Clusters::Applications::InstallService do exception: 'StandardError', error_code: nil, message: 'something bad happened', + backtrace: instance_of(Array), service: 'Clusters::Applications::InstallService', app_id: application.id, project_ids: application.cluster.projects.pluck(:id), @@ -108,15 +111,16 @@ describe Clusters::Applications::InstallService do expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( error, - extra: { + extra: hash_including( exception: 'StandardError', error_code: nil, message: 'something bad happened', + backtrace: instance_of(Array), service: 'Clusters::Applications::InstallService', app_id: application.id, project_ids: application.cluster.projects.pluck(:id), group_ids: [] - } + ) ) service.execute diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb index d4ee3243b84..51ca84dcd67 100644 --- a/spec/services/clusters/applications/patch_service_spec.rb +++ b/spec/services/clusters/applications/patch_service_spec.rb @@ -53,6 +53,7 @@ describe Clusters::Applications::PatchService do { exception: 'Kubeclient::HttpError', message: 'system failure', + backtrace: instance_of(Array), service: 'Clusters::Applications::PatchService', app_id: application.id, project_ids: application.cluster.project_ids, @@ -66,6 +67,7 @@ describe Clusters::Applications::PatchService do extra: { exception: 'Kubeclient::HttpError', message: 'system failure', + backtrace: instance_of(Array), service: 'Clusters::Applications::PatchService', app_id: application.id, project_ids: application.cluster.project_ids, @@ -101,6 +103,7 @@ describe Clusters::Applications::PatchService do exception: 'StandardError', error_code: nil, message: 'something bad happened', + backtrace: instance_of(Array), service: 'Clusters::Applications::PatchService', app_id: application.id, project_ids: application.cluster.projects.pluck(:id), @@ -114,6 +117,7 @@ describe Clusters::Applications::PatchService do exception: 'StandardError', error_code: nil, message: 'something bad happened', + backtrace: instance_of(Array), service: 'Clusters::Applications::PatchService', app_id: application.id, project_ids: application.cluster.projects.pluck(:id), diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb index 1822fc38dbd..22e7555d260 100644 --- a/spec/services/clusters/applications/upgrade_service_spec.rb +++ b/spec/services/clusters/applications/upgrade_service_spec.rb @@ -53,6 +53,7 @@ describe Clusters::Applications::UpgradeService do { exception: 'Kubeclient::HttpError', message: 'system failure', + backtrace: instance_of(Array), service: 'Clusters::Applications::UpgradeService', app_id: application.id, project_ids: application.cluster.project_ids, @@ -66,6 +67,7 @@ describe Clusters::Applications::UpgradeService do extra: { exception: 'Kubeclient::HttpError', message: 'system failure', + backtrace: instance_of(Array), service: 'Clusters::Applications::UpgradeService', app_id: application.id, project_ids: application.cluster.project_ids, @@ -101,6 +103,7 @@ describe Clusters::Applications::UpgradeService do exception: 'StandardError', error_code: nil, message: 'something bad happened', + backtrace: instance_of(Array), service: 'Clusters::Applications::UpgradeService', app_id: application.id, project_ids: application.cluster.projects.pluck(:id), @@ -114,6 +117,7 @@ describe Clusters::Applications::UpgradeService do exception: 'StandardError', error_code: nil, message: 'something bad happened', + backtrace: instance_of(Array), service: 'Clusters::Applications::UpgradeService', app_id: application.id, project_ids: application.cluster.projects.pluck(:id), -- cgit v1.2.1 From 0f5b735685476ff51b26031e9b74fa8256354868 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 3 Apr 2019 22:00:29 -0700 Subject: Fix stage index migration failing in PostgreSQL 10 As discussed in https://www.postgresql.org/message-id/9922.1353433645%40sss.pgh.pa.us, the PostgreSQL window function last_value may not consider the right rows: Note that first_value, last_value, and nth_value consider only the rows within the "window frame", which by default contains the rows from the start of the partition through the last peer of the current row. This is likely to give unhelpful results for last_value and sometimes also nth_value. You can redefine the frame by adding a suitable frame specification (RANGE or ROWS) to the OVER clause. See Section 4.2.8 for more information about frame specifications. This query could be fixed by adding `RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING`, but that's quite verbose. It's simpler just to use the first_value function. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59985 --- spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb index f8107dd40b9..4db829b1e7b 100644 --- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb @@ -30,6 +30,6 @@ describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 201 described_class.new.perform(100, 101) - expect(stages.all.pluck(:position)).to eq [2, 3] + expect(stages.all.order(:id).pluck(:position)).to eq [2, 3] end end -- cgit v1.2.1 From 97ab8539968d52b709cd61c2dd4229d404903e14 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Tue, 2 Apr 2019 12:46:21 +0200 Subject: [frontend] backport of scoped labels Scoped labels in EE require additional changes in CE code. --- spec/frontend/labels_select_spec.js | 116 ++++++++++++++++----- spec/javascripts/boards/issue_spec.js | 2 + .../sidebar/labels_select/dropdown_button_spec.js | 15 ++- .../labels_select/dropdown_value_collapsed_spec.js | 20 ++-- .../sidebar/labels_select/dropdown_value_spec.js | 40 ++++++- .../components/sidebar/labels_select/mock_data.js | 7 ++ 6 files changed, 161 insertions(+), 39 deletions(-) (limited to 'spec') diff --git a/spec/frontend/labels_select_spec.js b/spec/frontend/labels_select_spec.js index acfdc885032..d54e0eab845 100644 --- a/spec/frontend/labels_select_spec.js +++ b/spec/frontend/labels_select_spec.js @@ -13,40 +13,104 @@ const mockLabels = [ }, ]; +const mockScopedLabels = [ + { + id: 27, + title: 'Foo::Bar', + description: 'Foobar', + color: '#333ABC', + text_color: '#FFFFFF', + }, +]; + describe('LabelsSelect', () => { describe('getLabelTemplate', () => { - const label = mockLabels[0]; - let $labelEl; - - beforeEach(() => { - $labelEl = $( - LabelsSelect.getLabelTemplate({ - labels: mockLabels, - issueUpdateURL: mockUrl, - }), - ); - }); + describe('when normal label is present', () => { + const label = mockLabels[0]; + let $labelEl; - it('generated label item template has correct label URL', () => { - expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label'); - }); + beforeEach(() => { + $labelEl = $( + LabelsSelect.getLabelTemplate({ + labels: mockLabels, + issueUpdateURL: mockUrl, + enableScopedLabels: true, + scopedLabelsDocumentationLink: 'docs-link', + }), + ); + }); - it('generated label item template has correct label title', () => { - expect($labelEl.find('span.label').text()).toBe(label.title); - }); + it('generated label item template has correct label URL', () => { + expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label'); + }); - it('generated label item template has label description as title attribute', () => { - expect($labelEl.find('span.label').attr('title')).toBe(label.description); - }); + it('generated label item template has correct label title', () => { + expect($labelEl.find('span.label').text()).toBe(label.title); + }); + + it('generated label item template has label description as title attribute', () => { + expect($labelEl.find('span.label').attr('title')).toBe(label.description); + }); - it('generated label item template has correct label styles', () => { - expect($labelEl.find('span.label').attr('style')).toBe( - `background-color: ${label.color}; color: ${label.text_color};`, - ); + it('generated label item template has correct label styles', () => { + expect($labelEl.find('span.label').attr('style')).toBe( + `background-color: ${label.color}; color: ${label.text_color};`, + ); + }); + + it('generated label item has a badge class', () => { + expect($labelEl.find('span').hasClass('badge')).toEqual(true); + }); + + it('generated label item template does not have scoped-label class', () => { + expect($labelEl.find('.scoped-label')).toHaveLength(0); + }); }); - it('generated label item has a badge class', () => { - expect($labelEl.find('span').hasClass('badge')).toEqual(true); + describe('when scoped label is present', () => { + const label = mockScopedLabels[0]; + let $labelEl; + + beforeEach(() => { + $labelEl = $( + LabelsSelect.getLabelTemplate({ + labels: mockScopedLabels, + issueUpdateURL: mockUrl, + enableScopedLabels: true, + scopedLabelsDocumentationLink: 'docs-link', + }), + ); + }); + + it('generated label item template has correct label URL', () => { + expect($labelEl.find('a').attr('href')).toBe('/foo/bar?label_name[]=Foo%3A%3ABar'); + }); + + it('generated label item template has correct label title', () => { + expect($labelEl.find('span.label').text()).toBe(label.title); + }); + + it('generated label item template has html flag as true', () => { + expect($labelEl.find('span.label').attr('data-html')).toBe('true'); + }); + + it('generated label item template has question icon', () => { + expect($labelEl.find('i.fa-question-circle')).toHaveLength(1); + }); + + it('generated label item template has scoped-label class', () => { + expect($labelEl.find('.scoped-label')).toHaveLength(1); + }); + + it('generated label item template has correct label styles', () => { + expect($labelEl.find('span.label').attr('style')).toBe( + `background-color: ${label.color}; color: ${label.text_color};`, + ); + }); + + it('generated label item has a badge class', () => { + expect($labelEl.find('span').hasClass('badge')).toEqual(true); + }); }); }); }); diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 54fb0e8228b..e4ff3eb381f 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -178,6 +178,7 @@ describe('Issue model', () => { spyOn(Vue.http, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([1]); done(); + return Promise.resolve(); }); issue.update('url'); @@ -187,6 +188,7 @@ describe('Issue model', () => { spyOn(Vue.http, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([0]); done(); + return Promise.resolve(); }); issue.removeAllAssignees(); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js index 5cf6afebd7e..0689fc1cf1f 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js @@ -45,12 +45,21 @@ describe('DropdownButtonComponent', () => { }); const vmMoreLabels = createComponent(mockMoreLabels); - expect(vmMoreLabels.dropdownToggleText).toBe('Foo Label +1 more'); + expect(vmMoreLabels.dropdownToggleText).toBe( + `Foo Label +${mockMoreLabels.labels.length - 1} more`, + ); vmMoreLabels.$destroy(); }); it('returns first label name when `labels` prop has only one item present', () => { - expect(vm.dropdownToggleText).toBe('Foo Label'); + const singleLabel = Object.assign({}, componentConfig, { + labels: [mockLabels[0]], + }); + const vmSingleLabel = createComponent(singleLabel); + + expect(vmSingleLabel.dropdownToggleText).toBe(mockLabels[0].title); + + vmSingleLabel.$destroy(); }); }); }); @@ -73,7 +82,7 @@ describe('DropdownButtonComponent', () => { const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text'); expect(dropdownToggleTextEl).not.toBeNull(); - expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label'); + expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label +1 more'); }); it('renders dropdown button icon', () => { diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js index 804b33422bd..cb49fa31d20 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js @@ -35,9 +35,12 @@ describe('DropdownValueCollapsedComponent', () => { }); it('returns labels names separated by coma when `labels` prop has more than one item', () => { - const vmMoreLabels = createComponent(mockLabels.concat(mockLabels)); + const labels = mockLabels.concat(mockLabels); + const vmMoreLabels = createComponent(labels); - expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label'); + const expectedText = labels.map(label => label.title).join(', '); + + expect(vmMoreLabels.labelsList).toBe(expectedText); vmMoreLabels.$destroy(); }); @@ -49,14 +52,19 @@ describe('DropdownValueCollapsedComponent', () => { const vmMoreLabels = createComponent(mockMoreLabels); - expect(vmMoreLabels.labelsList).toBe( - 'Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more', - ); + const expectedText = `${mockMoreLabels + .slice(0, 5) + .map(label => label.title) + .join(', ')}, and ${mockMoreLabels.length - 5} more`; + + expect(vmMoreLabels.labelsList).toBe(expectedText); vmMoreLabels.$destroy(); }); it('returns first label name when `labels` prop has only one item present', () => { - expect(vm.labelsList).toBe('Foo Label'); + const text = mockLabels.map(label => label.title).join(', '); + + expect(vm.labelsList).toBe(text); }); }); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js index 3fff781594f..35a9c300953 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import $ from 'jquery'; import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue'; @@ -15,6 +16,7 @@ const createComponent = ( return mountComponent(Component, { labels, labelFilterBasePath, + enableScopedLabels: true, }); }; @@ -67,6 +69,26 @@ describe('DropdownValueComponent', () => { expect(styleObj.backgroundColor).toBe(label.color); }); }); + + describe('scopedLabelsDescription', () => { + it('returns html for tooltip', () => { + const html = vm.scopedLabelsDescription(mockLabels[1]); + const $el = $.parseHTML(html); + + expect($el[0]).toHaveClass('scoped-label-tooltip-title'); + expect($el[2].textContent).toEqual(mockLabels[1].description); + }); + }); + + describe('showScopedLabels', () => { + it('returns true if the label is scoped label', () => { + expect(vm.showScopedLabels(mockLabels[1])).toBe(true); + }); + + it('returns false when label is a regular label', () => { + expect(vm.showScopedLabels(mockLabels[0])).toBe(false); + }); + }); }); describe('template', () => { @@ -91,15 +113,25 @@ describe('DropdownValueComponent', () => { ); }); - it('renders label element with tooltip and styles based on label details', () => { + it('renders label element and styles based on label details', () => { const labelEl = vm.$el.querySelector('a span.badge.color-label'); expect(labelEl).not.toBeNull(); - expect(labelEl.dataset.placement).toBe('bottom'); - expect(labelEl.dataset.container).toBe('body'); - expect(labelEl.dataset.originalTitle).toBe(mockLabels[0].description); expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);'); expect(labelEl.innerText.trim()).toBe(mockLabels[0].title); }); + + describe('label is of scoped-label type', () => { + it('renders a scoped-label-wrapper span to incorporate 2 anchors', () => { + expect(vm.$el.querySelector('span.scoped-label-wrapper')).not.toBeNull(); + }); + + it('renders anchor tag containing question icon', () => { + const anchor = vm.$el.querySelector('.scoped-label-wrapper a.scoped-label'); + + expect(anchor).not.toBeNull(); + expect(anchor.querySelector('i.fa-question-circle')).not.toBeNull(); + }); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js index 3fcb91b6f5e..70025f041a7 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js @@ -6,6 +6,13 @@ export const mockLabels = [ color: '#BADA55', text_color: '#FFFFFF', }, + { + id: 27, + title: 'Foo::Bar', + description: 'Foobar', + color: '#0033CC', + text_color: '#FFFFFF', + }, ]; export const mockSuggestedColors = [ -- cgit v1.2.1 From 1c8e99be48551b8b04791166886d2b2ac86d83d3 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 28 Mar 2019 18:07:27 +0700 Subject: Ignore merge if the status of the merge request pipeline is stale Merge request pipeline is meant for ensuring target branch's pipeline green. We should not let maintainers merge a merge request if the head pipeline of the merge request doesn't fulfill the criteria. --- spec/requests/api/merge_requests_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4259fda7f04..73d4072b9d1 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1353,7 +1353,12 @@ describe API::MergeRequests do end it 'returns 405 if the build failed for a merge request that requires success' do - allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false) + project.update!(only_allow_merge_if_pipeline_succeeds: true) + + create(:ci_pipeline, + :failed, + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user) -- cgit v1.2.1 From 473674a2c321396d8bea0cfc53eb483abfca2e1a Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Wed, 3 Apr 2019 20:49:06 +0800 Subject: Prevent premailer from converting HTML entities This prevents `&mdash` in our templates being change to `-` --- spec/mailers/notify_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'spec') diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 3c8897ed37c..5fa1369c00a 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -30,6 +30,19 @@ describe Notify do description: 'My awesome description!') end + describe 'with HTML-encoded entities' do + before do + described_class.test_email('test@test.com', 'Subject', 'Some body with —').deliver + end + + subject { ActionMailer::Base.deliveries.last } + + it 'retains 7bit encoding' do + expect(subject.body.ascii_only?).to eq(true) + expect(subject.body.encoding).to eq('7bit') + end + end + context 'for a project' do shared_examples 'an assignee email' do it 'is sent to the assignee as the author' do -- cgit v1.2.1 From f6a038b38ae6c256fb9d1f1cbe184b0d8bbb5fde Mon Sep 17 00:00:00 2001 From: Denys Mishunov Date: Tue, 26 Mar 2019 16:23:15 +0100 Subject: Create a new file if URL references non-existent one --- spec/javascripts/ide/stores/actions/project_spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 7b0963713fb..cd519eaed7c 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -279,5 +279,22 @@ describe('IDE store project actions', () => { .then(done) .catch(done.fail); }); + + it('creates a new file supplied via URL if the file does not exist yet', done => { + openBranch(store, { ...branch, basePath: 'not-existent.md' }) + .then(() => { + expect(store.dispatch).not.toHaveBeenCalledWith( + 'handleTreeEntryAction', + jasmine.anything(), + ); + + expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', { + name: 'not-existent.md', + type: 'blob', + }); + }) + .then(done) + .catch(done.fail); + }); }); }); -- cgit v1.2.1 From 5e0423ebc55089978291d0823574c8498994c3b9 Mon Sep 17 00:00:00 2001 From: Denys Mishunov Date: Tue, 26 Mar 2019 17:28:33 +0100 Subject: Set tree list accounting for already-opened files Since we can create files from URL now, this means that these files will not exist in the tree returned from API: they exist on the client only before the first commit. In order to still show the newly-created files in the tree, we should not override the tree, but merge the tree existing on the client and the one coming from API. Changelog entry Moved trees merging into mutation --- spec/javascripts/ide/stores/actions/tree_spec.js | 33 +++++- spec/javascripts/ide/stores/mutations/tree_spec.js | 61 ++++++++-- spec/javascripts/ide/stores/utils_spec.js | 125 +++++++++++++++++++++ 3 files changed, 206 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js index fbb676aab33..5ed9b9003a7 100644 --- a/spec/javascripts/ide/stores/actions/tree_spec.js +++ b/spec/javascripts/ide/stores/actions/tree_spec.js @@ -1,6 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'spec/helpers/vuex_action_helper'; -import { showTreeEntry, getFiles } from '~/ide/stores/actions/tree'; +import { showTreeEntry, getFiles, setDirectoryData } from '~/ide/stores/actions/tree'; import * as types from '~/ide/stores/mutation_types'; import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; @@ -206,4 +206,35 @@ describe('Multi-file store tree actions', () => { ); }); }); + + describe('setDirectoryData', () => { + it('sets tree correctly if there are no opened files yet', done => { + const treeFile = file({ name: 'README.md' }); + store.state.trees['abcproject/master'] = {}; + + testAction( + setDirectoryData, + { projectId: 'abcproject', branchId: 'master', treeList: [treeFile] }, + store.state, + [ + { + type: types.SET_DIRECTORY_DATA, + payload: { + treePath: 'abcproject/master', + data: [treeFile], + }, + }, + { + type: types.TOGGLE_LOADING, + payload: { + entry: {}, + forceValue: false, + }, + }, + ], + [], + done, + ); + }); + }); }); diff --git a/spec/javascripts/ide/stores/mutations/tree_spec.js b/spec/javascripts/ide/stores/mutations/tree_spec.js index 67e9f7509da..7f9c978aa46 100644 --- a/spec/javascripts/ide/stores/mutations/tree_spec.js +++ b/spec/javascripts/ide/stores/mutations/tree_spec.js @@ -26,17 +26,11 @@ describe('Multi-file store tree mutations', () => { }); describe('SET_DIRECTORY_DATA', () => { - const data = [ - { - name: 'tree', - }, - { - name: 'submodule', - }, - { - name: 'blob', - }, - ]; + let data; + + beforeEach(() => { + data = [file('tree'), file('foo'), file('blob')]; + }); it('adds directory data', () => { localState.trees['project/master'] = { @@ -52,7 +46,7 @@ describe('Multi-file store tree mutations', () => { expect(tree.tree.length).toBe(3); expect(tree.tree[0].name).toBe('tree'); - expect(tree.tree[1].name).toBe('submodule'); + expect(tree.tree[1].name).toBe('foo'); expect(tree.tree[2].name).toBe('blob'); }); @@ -65,6 +59,49 @@ describe('Multi-file store tree mutations', () => { expect(localState.trees['project/master'].loading).toBe(true); }); + + it('does not override tree already in state, but merges the two with correct order', () => { + const openedFile = file('new'); + + localState.trees['project/master'] = { + loading: true, + tree: [openedFile], + }; + + mutations.SET_DIRECTORY_DATA(localState, { + data, + treePath: 'project/master', + }); + + const { tree } = localState.trees['project/master']; + + expect(tree.length).toBe(4); + expect(tree[0].name).toBe('blob'); + expect(tree[1].name).toBe('foo'); + expect(tree[2].name).toBe('new'); + expect(tree[3].name).toBe('tree'); + }); + + it('returns tree unchanged if the opened file is already in the tree', () => { + const openedFile = file('foo'); + localState.trees['project/master'] = { + loading: true, + tree: [openedFile], + }; + + mutations.SET_DIRECTORY_DATA(localState, { + data, + treePath: 'project/master', + }); + + const { tree } = localState.trees['project/master']; + + expect(tree.length).toBe(3); + + expect(tree[0].name).toBe('tree'); + expect(tree[1].name).toBe('foo'); + expect(tree[2].name).toBe('blob'); + }); }); describe('REMOVE_ALL_CHANGES_FILES', () => { diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js index 9f18034f8a3..c4f122efdda 100644 --- a/spec/javascripts/ide/stores/utils_spec.js +++ b/spec/javascripts/ide/stores/utils_spec.js @@ -235,4 +235,129 @@ describe('Multi-file store utils', () => { ]); }); }); + + describe('mergeTrees', () => { + let fromTree; + let toTree; + + beforeEach(() => { + fromTree = [file('foo')]; + toTree = [file('bar')]; + }); + + it('merges simple trees with sorting the result', () => { + toTree = [file('beta'), file('alpha'), file('gamma')]; + const res = utils.mergeTrees(fromTree, toTree); + + expect(res.length).toEqual(4); + expect(res[0].name).toEqual('alpha'); + expect(res[1].name).toEqual('beta'); + expect(res[2].name).toEqual('foo'); + expect(res[3].name).toEqual('gamma'); + expect(res[2]).toBe(fromTree[0]); + }); + + it('handles edge cases', () => { + expect(utils.mergeTrees({}, []).length).toEqual(0); + + let res = utils.mergeTrees({}, toTree); + + expect(res.length).toEqual(1); + expect(res[0].name).toEqual('bar'); + + res = utils.mergeTrees(fromTree, []); + + expect(res.length).toEqual(1); + expect(res[0].name).toEqual('foo'); + expect(res[0]).toBe(fromTree[0]); + }); + + it('merges simple trees without producing duplicates', () => { + toTree.push(file('foo')); + + const res = utils.mergeTrees(fromTree, toTree); + + expect(res.length).toEqual(2); + expect(res[0].name).toEqual('bar'); + expect(res[1].name).toEqual('foo'); + expect(res[1]).not.toBe(fromTree[0]); + }); + + it('merges nested tree into the main one without duplicates', () => { + fromTree[0].tree.push({ + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('beta.md'), + path: 'foo/alpha/beta.md', + }, + ], + }); + + toTree.push({ + ...file('foo'), + tree: [ + { + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('gamma.md'), + path: 'foo/alpha/gamma.md', + }, + ], + }, + ], + }); + + const res = utils.mergeTrees(fromTree, toTree); + + expect(res.length).toEqual(2); + expect(res[1].name).toEqual('foo'); + + const finalBranch = res[1].tree[0].tree; + + expect(finalBranch.length).toEqual(2); + expect(finalBranch[0].name).toEqual('beta.md'); + expect(finalBranch[1].name).toEqual('gamma.md'); + }); + + it('marks correct folders as opened as the parsing goes on', () => { + fromTree[0].tree.push({ + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('beta.md'), + path: 'foo/alpha/beta.md', + }, + ], + }); + + toTree.push({ + ...file('foo'), + tree: [ + { + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('gamma.md'), + path: 'foo/alpha/gamma.md', + }, + ], + }, + ], + }); + + const res = utils.mergeTrees(fromTree, toTree); + + expect(res[1].name).toEqual('foo'); + expect(res[1].opened).toEqual(true); + + expect(res[1].tree[0].name).toEqual('alpha'); + expect(res[1].tree[0].opened).toEqual(true); + }); + }); }); -- cgit v1.2.1 From 7cd9cdbe540358ae94d8cee0a0dd36383c645e23 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Tue, 2 Apr 2019 16:27:33 +1300 Subject: Refactor to share tests for base class method DRY up tests for logging error. --- .../check_installation_progress_service_spec.rb | 27 ++++---- .../clusters/applications/install_service_spec.rb | 76 ++++------------------ .../clusters/applications/patch_service_spec.rb | 74 ++++----------------- .../clusters/applications/upgrade_service_spec.rb | 74 ++++----------------- .../services/base_helm_service_shared_examples.rb | 27 ++++++++ 5 files changed, 75 insertions(+), 203 deletions(-) create mode 100644 spec/support/shared_examples/services/base_helm_service_shared_examples.rb (limited to 'spec') diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb index 6e37a30d142..1cca89d31d7 100644 --- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb @@ -33,15 +33,22 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do end end - shared_examples 'error logging' do + shared_examples 'error handling' do context 'when installation raises a Kubeclient::HttpError' do let(:cluster) { create(:cluster, :provided_by_user, :project) } let(:logger) { service.send(:logger) } + let(:error) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) } before do application.update!(cluster: cluster) - expect(service).to receive(:installation_phase).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + expect(service).to receive(:installation_phase).and_raise(error) + end + + include_examples 'logs kubernetes errors' do + let(:error_name) { 'Kubeclient::HttpError' } + let(:error_message) { 'Unauthorized' } + let(:error_code) { 401 } end it 'shows the response code from the error' do @@ -50,18 +57,6 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do expect(application).to be_errored.or(be_update_errored) expect(application.status_reason).to eq('Kubernetes error: 401') end - - it 'should log error' do - expect(logger).to receive(:error) - - service.execute - end - - it 'logs error backtrace' do - expect(logger).to receive(:error).with(hash_including(backtrace: instance_of(Array))) - - service.execute - end end end @@ -73,7 +68,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do context 'when application is updating' do let(:application) { create(:clusters_applications_helm, :updating) } - include_examples 'error logging' + include_examples 'error handling' RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase } @@ -134,7 +129,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do end context 'when application is installing' do - include_examples 'error logging' + include_examples 'error handling' RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase } diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb index 8affc135bc5..db0c33a95b2 100644 --- a/spec/services/clusters/applications/install_service_spec.rb +++ b/spec/services/clusters/applications/install_service_spec.rb @@ -39,53 +39,34 @@ describe Clusters::Applications::InstallService do expect(helm_client).to receive(:install).with(install_command).and_raise(error) end + include_examples 'logs kubernetes errors' do + let(:error_name) { 'Kubeclient::HttpError' } + let(:error_message) { 'system failure' } + let(:error_code) { 500 } + end + it 'make the application errored' do service.execute expect(application).to be_errored expect(application.status_reason).to match('Kubernetes error: 500') end - - it 'logs errors' do - expect(service.send(:logger)).to receive(:error).with( - { - exception: 'Kubeclient::HttpError', - message: 'system failure', - backtrace: instance_of(Array), - service: 'Clusters::Applications::InstallService', - app_id: application.id, - project_ids: application.cluster.project_ids, - group_ids: [], - error_code: 500 - } - ) - - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( - error, - extra: hash_including( - exception: 'Kubeclient::HttpError', - message: 'system failure', - backtrace: instance_of(Array), - service: 'Clusters::Applications::InstallService', - app_id: application.id, - project_ids: application.cluster.project_ids, - group_ids: [], - error_code: 500 - ) - ) - - service.execute - end end context 'a non kubernetes error happens' do let(:application) { create(:clusters_applications_helm, :scheduled) } - let(:error) { StandardError.new("something bad happened") } + let(:error) { StandardError.new('something bad happened') } before do expect(application).to receive(:make_installing!).once.and_raise(error) end + include_examples 'logs kubernetes errors' do + let(:error_name) { 'StandardError' } + let(:error_message) { 'something bad happened' } + let(:error_code) { nil } + end + it 'make the application errored' do expect(helm_client).not_to receive(:install) @@ -94,37 +75,6 @@ describe Clusters::Applications::InstallService do expect(application).to be_errored expect(application.status_reason).to eq("Can't start installation process.") end - - it 'logs errors' do - expect(service.send(:logger)).to receive(:error).with( - { - exception: 'StandardError', - error_code: nil, - message: 'something bad happened', - backtrace: instance_of(Array), - service: 'Clusters::Applications::InstallService', - app_id: application.id, - project_ids: application.cluster.projects.pluck(:id), - group_ids: [] - } - ) - - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( - error, - extra: hash_including( - exception: 'StandardError', - error_code: nil, - message: 'something bad happened', - backtrace: instance_of(Array), - service: 'Clusters::Applications::InstallService', - app_id: application.id, - project_ids: application.cluster.projects.pluck(:id), - group_ids: [] - ) - ) - - service.execute - end end end end diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb index 51ca84dcd67..10b1379a127 100644 --- a/spec/services/clusters/applications/patch_service_spec.rb +++ b/spec/services/clusters/applications/patch_service_spec.rb @@ -41,49 +41,30 @@ describe Clusters::Applications::PatchService do expect(helm_client).to receive(:update).with(update_command).and_raise(error) end + include_examples 'logs kubernetes errors' do + let(:error_name) { 'Kubeclient::HttpError' } + let(:error_message) { 'system failure' } + let(:error_code) { 500 } + end + it 'make the application errored' do service.execute expect(application).to be_update_errored expect(application.status_reason).to match('Kubernetes error: 500') end - - it 'logs errors' do - expect(service.send(:logger)).to receive(:error).with( - { - exception: 'Kubeclient::HttpError', - message: 'system failure', - backtrace: instance_of(Array), - service: 'Clusters::Applications::PatchService', - app_id: application.id, - project_ids: application.cluster.project_ids, - group_ids: [], - error_code: 500 - } - ) - - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( - error, - extra: { - exception: 'Kubeclient::HttpError', - message: 'system failure', - backtrace: instance_of(Array), - service: 'Clusters::Applications::PatchService', - app_id: application.id, - project_ids: application.cluster.project_ids, - group_ids: [], - error_code: 500 - } - ) - - service.execute - end end context 'a non kubernetes error happens' do let(:application) { create(:clusters_applications_knative, :scheduled) } let(:error) { StandardError.new('something bad happened') } + include_examples 'logs kubernetes errors' do + let(:error_name) { 'StandardError' } + let(:error_message) { 'something bad happened' } + let(:error_code) { nil } + end + before do expect(application).to receive(:make_updating!).once.and_raise(error) end @@ -96,37 +77,6 @@ describe Clusters::Applications::PatchService do expect(application).to be_update_errored expect(application.status_reason).to eq("Can't start update process.") end - - it 'logs errors' do - expect(service.send(:logger)).to receive(:error).with( - { - exception: 'StandardError', - error_code: nil, - message: 'something bad happened', - backtrace: instance_of(Array), - service: 'Clusters::Applications::PatchService', - app_id: application.id, - project_ids: application.cluster.projects.pluck(:id), - group_ids: [] - } - ) - - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( - error, - extra: { - exception: 'StandardError', - error_code: nil, - message: 'something bad happened', - backtrace: instance_of(Array), - service: 'Clusters::Applications::PatchService', - app_id: application.id, - project_ids: application.cluster.projects.pluck(:id), - group_ids: [] - } - ) - - service.execute - end end end end diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb index 22e7555d260..dd2e6e94e4f 100644 --- a/spec/services/clusters/applications/upgrade_service_spec.rb +++ b/spec/services/clusters/applications/upgrade_service_spec.rb @@ -41,43 +41,18 @@ describe Clusters::Applications::UpgradeService do expect(helm_client).to receive(:update).with(install_command).and_raise(error) end + include_examples 'logs kubernetes errors' do + let(:error_name) { 'Kubeclient::HttpError' } + let(:error_message) { 'system failure' } + let(:error_code) { 500 } + end + it 'make the application errored' do service.execute expect(application).to be_update_errored expect(application.status_reason).to match('Kubernetes error: 500') end - - it 'logs errors' do - expect(service.send(:logger)).to receive(:error).with( - { - exception: 'Kubeclient::HttpError', - message: 'system failure', - backtrace: instance_of(Array), - service: 'Clusters::Applications::UpgradeService', - app_id: application.id, - project_ids: application.cluster.project_ids, - group_ids: [], - error_code: 500 - } - ) - - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( - error, - extra: { - exception: 'Kubeclient::HttpError', - message: 'system failure', - backtrace: instance_of(Array), - service: 'Clusters::Applications::UpgradeService', - app_id: application.id, - project_ids: application.cluster.project_ids, - group_ids: [], - error_code: 500 - } - ) - - service.execute - end end context 'a non kubernetes error happens' do @@ -88,6 +63,12 @@ describe Clusters::Applications::UpgradeService do expect(application).to receive(:make_updating!).once.and_raise(error) end + include_examples 'logs kubernetes errors' do + let(:error_name) { 'StandardError' } + let(:error_message) { 'something bad happened' } + let(:error_code) { nil } + end + it 'make the application errored' do expect(helm_client).not_to receive(:update) @@ -96,37 +77,6 @@ describe Clusters::Applications::UpgradeService do expect(application).to be_update_errored expect(application.status_reason).to eq("Can't start upgrade process.") end - - it 'logs errors' do - expect(service.send(:logger)).to receive(:error).with( - { - exception: 'StandardError', - error_code: nil, - message: 'something bad happened', - backtrace: instance_of(Array), - service: 'Clusters::Applications::UpgradeService', - app_id: application.id, - project_ids: application.cluster.projects.pluck(:id), - group_ids: [] - } - ) - - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( - error, - extra: { - exception: 'StandardError', - error_code: nil, - message: 'something bad happened', - backtrace: instance_of(Array), - service: 'Clusters::Applications::UpgradeService', - app_id: application.id, - project_ids: application.cluster.projects.pluck(:id), - group_ids: [] - } - ) - - service.execute - end end end end diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb new file mode 100644 index 00000000000..c38baa76b25 --- /dev/null +++ b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +shared_examples 'logs kubernetes errors' do + let(:error_hash) do + { + exception: error_name, + message: error_message, + backtrace: instance_of(Array), + service: service.class.name, + app_id: application.id, + project_ids: application.cluster.project_ids, + group_ids: [], + error_code: error_code + } + end + + it 'logs into kubernetes.log and Sentry' do + expect(service.send(:logger)).to receive(:error).with(error_hash) + + expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( + error, + extra: hash_including(error_hash) + ) + + service.execute + end +end -- cgit v1.2.1 From 12321771ff4d2aca723e7292ba9aec180470df13 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 4 Apr 2019 14:43:11 +1300 Subject: Don't send error backtrace to Sentry As it already has the full error sent to it --- .../services/base_helm_service_shared_examples.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb index c38baa76b25..e2f1a18e9f6 100644 --- a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb +++ b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb @@ -5,7 +5,6 @@ shared_examples 'logs kubernetes errors' do { exception: error_name, message: error_message, - backtrace: instance_of(Array), service: service.class.name, app_id: application.id, project_ids: application.cluster.project_ids, @@ -14,8 +13,14 @@ shared_examples 'logs kubernetes errors' do } end + let(:logger_hash) do + error_hash.merge( + backtrace: instance_of(Array) + ) + end + it 'logs into kubernetes.log and Sentry' do - expect(service.send(:logger)).to receive(:error).with(error_hash) + expect(service.send(:logger)).to receive(:error).with(logger_hash) expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( error, -- cgit v1.2.1 From a9c855f12c408651e1db41aa5e42aac33aac4084 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 4 Apr 2019 14:50:43 +1300 Subject: Do not send error name and message to Sentry It's duplication information as we already send the full error object to Sentry. --- .../shared_examples/services/base_helm_service_shared_examples.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb index e2f1a18e9f6..78a8e49fd76 100644 --- a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb +++ b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb @@ -3,8 +3,6 @@ shared_examples 'logs kubernetes errors' do let(:error_hash) do { - exception: error_name, - message: error_message, service: service.class.name, app_id: application.id, project_ids: application.cluster.project_ids, @@ -15,6 +13,8 @@ shared_examples 'logs kubernetes errors' do let(:logger_hash) do error_hash.merge( + exception: error_name, + message: error_message, backtrace: instance_of(Array) ) end -- cgit v1.2.1 From 52515dda0b2fc3818eae243a324f5dfb88414c37 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 4 Apr 2019 08:21:27 +0000 Subject: Copy sidebar todo component from EE --- spec/javascripts/sidebar/todo_spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js index 657e88ecb96..f46ea5a0499 100644 --- a/spec/javascripts/sidebar/todo_spec.js +++ b/spec/javascripts/sidebar/todo_spec.js @@ -116,7 +116,7 @@ describe('SidebarTodo', () => { const dataAttributes = { issuableId: '1', issuableType: 'epic', - originalTitle: 'Mark todo as done', + originalTitle: '', placement: 'left', container: 'body', boundary: 'viewport', @@ -130,6 +130,10 @@ describe('SidebarTodo', () => { }); }); + it('check button label computed property', () => { + expect(vm.buttonLabel).toEqual('Mark todo as done'); + }); + it('renders button label element when `collapsed` prop is `false`', () => { const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner'); -- cgit v1.2.1 From f4adb50ef2ddac2b49050b312fd79e8960ead301 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Thu, 4 Apr 2019 09:01:09 +0000 Subject: Expose head pipeline in the related merge requests Expose head pipeline for the MR in the api when requesting related merge requests for an issue and show a detailed status for the pipeline, which would include: details_path, favicon, group, icon, label, text, tooltip. https://gitlab.com/gitlab-org/gitlab-ce/issues/57662#note_152023412 --- .../api/schemas/public_api/v4/merge_request.json | 8 +++++- .../api/schemas/public_api/v4/pipeline/basic.json | 3 +- .../api/schemas/public_api/v4/pipeline/detail.json | 32 ++++++++++++++++++++++ spec/requests/api/merge_requests_spec.rb | 8 ++++++ spec/requests/api/pipelines_spec.rb | 7 +++++ 5 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json (limited to 'spec') diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_request.json b/spec/fixtures/api/schemas/public_api/v4/merge_request.json index cd50be00418..918f2c4b47d 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_request.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_request.json @@ -119,6 +119,12 @@ "merge_status", "sha", "merge_commit_sha", "user_notes_count", "should_remove_source_branch", "force_remove_source_branch", "web_url", "squash" - ] + ], + "head_pipeline": { + "oneOf": [ + { "type": "null" }, + { "$ref": "pipeline/detail.json" } + ] + } } } diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json index 56f86856dd4..a7207d2d991 100644 --- a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json +++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json @@ -13,6 +13,5 @@ "ref": { "type": "string" }, "status": { "type": "string" }, "web_url": { "type": "string" } - }, - "additionalProperties": false + } } diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json new file mode 100644 index 00000000000..63e130d4055 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json @@ -0,0 +1,32 @@ +{ + "type": "object", + "allOf": [ + { "$ref": "basic.json" }, + { + "properties": { + "before_sha": { "type": ["string", "null"] }, + "tag": { "type": ["boolean"] }, + "yaml_errors": { "type": ["string", "null"] }, + "user": { + "anyOf": [ + { "type": ["object", "null"] }, + { "$ref": "../user/basic.json" } + ] + }, + "created_at": { "type": ["date", "null"] }, + "updated_at": { "type": ["date", "null"] }, + "started_at": { "type": ["date", "null"] }, + "finished_at": { "type": ["date", "null"] }, + "committed_at": { "type": ["date", "null"] }, + "duration": { "type": ["number", "null"] }, + "coverage": { "type": ["string", "null"] }, + "detailed_status": { + "oneOf": [ + { "type": "null" }, + { "$ref": "../../../status/ci_detailed_status.json" } + ] + } + } + } + ] +} diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4259fda7f04..1d139200535 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -729,6 +729,14 @@ describe API::MergeRequests do end describe "GET /projects/:id/merge_requests/:merge_request_iid" do + it 'matches json schema' do + merge_request = create(:merge_request, :with_test_reports, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/merge_request') + end + it 'exposes known attributes' do create(:award_emoji, :downvote, awardable: merge_request) create(:award_emoji, :upvote, awardable: merge_request) diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 52599db9a9e..c26d31c5e0d 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -399,6 +399,13 @@ describe API::Pipelines do describe 'GET /projects/:id/pipelines/:pipeline_id' do context 'authorized user' do + it 'exposes known attributes' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/pipeline/detail') + end + it 'returns project pipelines' do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) -- cgit v1.2.1 From f37f28b962376843e7682dad15067edb131ecdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 18 Mar 2019 14:32:26 +0100 Subject: Fix race cond. in ApplicationSettingImplementation.create_from_defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/models/application_setting_spec.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index c5579dafb4a..c81572d739e 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -6,6 +6,7 @@ describe ApplicationSetting do let(:setting) { described_class.create_from_defaults } it { include(CacheableAttributes) } + it { include(ApplicationSettingImplementation) } it { expect(described_class.current_without_cache).to eq(described_class.last) } it { expect(setting).to be_valid } @@ -286,12 +287,10 @@ describe ApplicationSetting do end context 'restrict creating duplicates' do - before do - described_class.create_from_defaults - end + let!(:current_settings) { described_class.create_from_defaults } - it 'raises an record creation violation if already created' do - expect { described_class.create_from_defaults }.to raise_error(ActiveRecord::RecordNotUnique) + it 'returns the current settings' do + expect(described_class.create_from_defaults).to eq(current_settings) end end -- cgit v1.2.1 From 8e51439e809db6ff5ff86bd79cdf95af62821a1d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 10 Jan 2019 18:32:12 +0900 Subject: Drop legacy artifacts usage Legacy artifacts have been correctly migrated to new place - ci_job_artifacts. Now it's time to remove the related code, but before that we should ensure it doesn't break anything by using feature flag. --- spec/models/ci/build_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'spec') diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 2c41dfa65cd..697fe3fda06 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -117,6 +117,16 @@ describe Ci::Build do it 'returns the job' do is_expected.to include(job) end + + context 'when ci_enable_legacy_artifacts feature flag is disabled' do + before do + stub_feature_flags(ci_enable_legacy_artifacts: false) + end + + it 'does not return the job' do + is_expected.not_to include(job) + end + end end context 'when job has a job artifact archive' do @@ -471,6 +481,14 @@ describe Ci::Build do let(:build) { create(:ci_build, :legacy_artifacts) } it { is_expected.to be_truthy } + + context 'when ci_enable_legacy_artifacts feature flag is disabled' do + before do + stub_feature_flags(ci_enable_legacy_artifacts: false) + end + + it { is_expected.to be_falsy } + end end end end -- cgit v1.2.1 From 9f3f45bfb0fe0597822d1200cd89f6a4cff4cc94 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 25 Mar 2019 13:14:32 +0200 Subject: Add v2 to reserved top level routes Signed-off-by: Dmitriy Zaporozhets --- spec/lib/gitlab/path_regex_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 312e5e55af8..71e69a0d418 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -100,7 +100,7 @@ describe Gitlab::PathRegex do end let(:ee_top_level_words) do - ['unsubscribes'] + %w(unsubscribes v2) end let(:files_in_public) do -- cgit v1.2.1 From ec85debaf51067cc78d54188ec1eef94342d5a8b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 2 Apr 2019 12:52:28 +0100 Subject: Speed up avatar URLs with object storage With object storage enabled, calling `#filename` on an upload does this: 1. Call the `#filename` method on the CarrierWave object. 2. Generate the URL for that object. 3. If the uploader isn't public, do so by generating an authenticated URL, including signing that request. That's all correct behaviour, but for the case where we use `#filename`, it's typically to generate a GitLab URL. That URL doesn't need to be signed because we do our own auth. Signing the URLs can be very expensive, especially in batch (say, we need to get the avatar URLs for 150 users in one request). It's all unnecessary work. If we used the `RecordsUploads` concern, we have already recorded a `path` in the database. That `path` is actually generated from CarrierWave's `#filename` at upload time, so we don't need to recompute it - we can just use it and strip off the prefix if it's available. On a sample users autocomplete URL, at least 10% of the time before this change went to signing URLs. After this change, we spend no time in URL signing, and still get the correct results. --- spec/uploaders/records_uploads_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb index 3592a11360d..ab98976ec27 100644 --- a/spec/uploaders/records_uploads_spec.rb +++ b/spec/uploaders/records_uploads_spec.rb @@ -94,4 +94,13 @@ describe RecordsUploads do expect { uploader.remove! }.to change { Upload.count }.from(1).to(0) end end + + describe '#filename' do + it 'gets the filename from the path recorded in the database, not CarrierWave' do + uploader.store!(upload_fixture('rails_sample.jpg')) + expect_any_instance_of(GitlabUploader).not_to receive(:filename) + + expect(uploader.filename).to eq('rails_sample.jpg') + end + end end -- cgit v1.2.1 From 5543d897d077770b785341d5828e082885da808b Mon Sep 17 00:00:00 2001 From: Ryan Cobb Date: Thu, 4 Apr 2019 10:56:12 +0000 Subject: Filters branch and path labels for metrics --- spec/lib/gitlab/metrics/transaction_spec.rb | 229 ++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 spec/lib/gitlab/metrics/transaction_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb new file mode 100644 index 00000000000..e70fde09edd --- /dev/null +++ b/spec/lib/gitlab/metrics/transaction_spec.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Transaction do + let(:transaction) { described_class.new } + let(:metric) { transaction.metrics[0] } + + let(:sensitive_tags) do + { + path: 'private', + branch: 'sensitive' + } + end + + shared_examples 'tag filter' do |sane_tags| + it 'filters potentially sensitive tags' do + expect(metric.tags).to eq(sane_tags) + end + end + + describe '#duration' do + it 'returns the duration of a transaction in seconds' do + transaction.run { } + + expect(transaction.duration).to be > 0 + end + end + + describe '#allocated_memory' do + it 'returns the allocated memory in bytes' do + transaction.run { 'a' * 32 } + + expect(transaction.allocated_memory).to be_a_kind_of(Numeric) + end + end + + describe '#run' do + it 'yields the supplied block' do + expect { |b| transaction.run(&b) }.to yield_control + end + + it 'stores the transaction in the current thread' do + transaction.run do + expect(described_class.current).to eq(transaction) + end + end + + it 'removes the transaction from the current thread upon completion' do + transaction.run { } + + expect(described_class.current).to be_nil + end + end + + describe '#add_metric' do + it 'adds a metric to the transaction' do + transaction.add_metric('foo', value: 1) + + expect(metric.series).to eq('rails_foo') + expect(metric.tags).to eq({}) + expect(metric.values).to eq(value: 1) + end + + context 'with sensitive tags' do + before do + transaction + .add_metric('foo', { value: 1 }, **sensitive_tags.merge(sane: 'yes')) + end + + it_behaves_like 'tag filter', sane: 'yes' + end + end + + describe '#method_call_for' do + it 'returns a MethodCall' do + method = transaction.method_call_for('Foo#bar', :Foo, '#bar') + + expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall) + end + end + + describe '#increment' do + it 'increments a counter' do + transaction.increment(:time, 1) + transaction.increment(:time, 2) + + values = metric_values(time: 3) + + expect(transaction).to receive(:add_metric) + .with('transactions', values, {}) + + transaction.track_self + end + end + + describe '#set' do + it 'sets a value' do + transaction.set(:number, 10) + + values = metric_values(number: 10) + + expect(transaction).to receive(:add_metric) + .with('transactions', values, {}) + + transaction.track_self + end + end + + describe '#finish' do + it 'tracks the transaction details and submits them to Sidekiq' do + expect(transaction).to receive(:track_self) + expect(transaction).to receive(:submit) + + transaction.finish + end + end + + describe '#track_self' do + it 'adds a metric for the transaction itself' do + values = metric_values + + expect(transaction).to receive(:add_metric) + .with('transactions', values, {}) + + transaction.track_self + end + end + + describe '#submit' do + it 'submits the metrics to Sidekiq' do + transaction.track_self + + expect(Gitlab::Metrics).to receive(:submit_metrics) + .with([an_instance_of(Hash)]) + + transaction.submit + end + + it 'adds the action as a tag for every metric' do + allow(transaction) + .to receive(:labels) + .and_return(controller: 'Foo', action: 'bar') + + transaction.track_self + + hash = { + series: 'rails_transactions', + tags: { action: 'Foo#bar' }, + values: metric_values, + timestamp: a_kind_of(Integer) + } + + expect(Gitlab::Metrics).to receive(:submit_metrics) + .with([hash]) + + transaction.submit + end + + it 'does not add an action tag for events' do + allow(transaction) + .to receive(:labels) + .and_return(controller: 'Foo', action: 'bar') + + transaction.add_event(:meow) + + hash = { + series: 'events', + tags: { event: :meow }, + values: { count: 1 }, + timestamp: a_kind_of(Integer) + } + + expect(Gitlab::Metrics).to receive(:submit_metrics) + .with([hash]) + + transaction.submit + end + end + + describe '#add_event' do + it 'adds a metric' do + transaction.add_event(:meow) + + expect(metric).to be_an_instance_of(Gitlab::Metrics::Metric) + end + + it "does not prefix the metric's series name" do + transaction.add_event(:meow) + + expect(metric.series).to eq(described_class::EVENT_SERIES) + end + + it 'tracks a counter for every event' do + transaction.add_event(:meow) + + expect(metric.values).to eq(count: 1) + end + + it 'tracks the event name' do + transaction.add_event(:meow) + + expect(metric.tags).to eq(event: :meow) + end + + it 'allows tracking of custom tags' do + transaction.add_event(:meow, animal: 'cat') + + expect(metric.tags).to eq(event: :meow, animal: 'cat') + end + + context 'with sensitive tags' do + before do + transaction.add_event(:meow, **sensitive_tags.merge(sane: 'yes')) + end + + it_behaves_like 'tag filter', event: :meow, sane: 'yes' + end + end + + private + + def metric_values(opts = {}) + { + duration: 0.0, + allocated_memory: a_kind_of(Numeric) + }.merge(opts) + end +end -- cgit v1.2.1 From 38b40f91649d2aa670cf229f38cc70c9de3ebdd7 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 5 Apr 2019 00:04:30 +1300 Subject: Bump Helm to 2.13.1 and kubectl to 1.11.9 --- spec/lib/gitlab/kubernetes/helm/pod_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index 95b6b3fd953..ee7343c5eeb 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('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7') + expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.13.1-kube-1.11.9') 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"]) -- cgit v1.2.1 From 10ceb33ba271f603fa09d4a4b5fdca03fd7ea333 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 3 Apr 2019 10:46:13 +0100 Subject: Extend CTE search optimisation to projects When we use the `search` param on an `IssuableFinder`, we can run into issues. We have trigram indexes to support these searches. On GitLab.com, we often see Postgres's optimiser prioritise the (global) trigram indexes over the index on `project_id`. For group and project searches, we know that it will be quicker to filter by `project_id` first, as it returns fewer rows in most cases. For group issues search, we ran into this issue previously, and went through the following iterations: 1. Use a CTE on the project IDs as an optimisation fence. This prevents the planner from disregarding the index on `project_id`. Unfortunately it breaks some types of sorting, like priority and popularity, as they sort on a joined table. 2. Use a subquery for listing issues, and a CTE for counts. The subquery - in the case of group lists - didn't help as much as the CTE, but was faster than not including it. We can safely use a CTE for counts as they don't have sorting. Now, however, we're seeing the same issue in a project context. The subquery doesn't help at all there (it would only return one row, after all). In an attempt to keep total code complexity under control, this commit removes the subquery optimisation and applies the CTE optimisation only for sorts we know that are safe. This means that for more complicated sorts (like priority and popularity), the search will continue to be very slow. If this is a high-priority issue, we can consider introducing further optimisations, but this finder is already very complicated and additional complexity has a cost. The group CTE optimisation is controlled by the same feature flag as before, `attempt_group_search_optimizations`, which is enabled by default. The new project CTE optimisation is controlled by a new feature flag, `attempt_project_search_optimizations`, which is disabled by default. --- spec/finders/issues_finder_spec.rb | 89 ++++++---------------- .../issuables_list_metadata_shared_examples.rb | 35 +++++++-- 2 files changed, 52 insertions(+), 72 deletions(-) (limited to 'spec') diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 00b6cad1a66..fe53fabe54c 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -719,7 +719,7 @@ describe IssuesFinder do end end - describe '#use_subquery_for_search?' do + describe '#use_cte_for_search?' do let(:finder) { described_class.new(nil, params) } before do @@ -731,7 +731,7 @@ describe IssuesFinder do let(:params) { { attempt_group_search_optimizations: true } } it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end @@ -743,15 +743,15 @@ describe IssuesFinder do end it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end - context 'when the attempt_group_search_optimizations param is falsey' do + context 'when the force_cte param is falsey' do let(:params) { { search: 'foo' } } it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end @@ -763,80 +763,39 @@ describe IssuesFinder do end it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end - context 'when force_cte? is true' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true, force_cte: true } } - - it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey - end - end - - context 'when all conditions are met' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } - - it 'returns true' do - expect(finder.use_subquery_for_search?).to be_truthy - end - end - end + context 'when attempt_group_search_optimizations is unset and attempt_project_search_optimizations is set' do + let(:params) { { search: 'foo', attempt_project_search_optimizations: true } } - describe '#use_cte_for_count?' do - let(:finder) { described_class.new(nil, params) } - - before do - allow(Gitlab::Database).to receive(:postgresql?).and_return(true) - stub_feature_flags(attempt_group_search_optimizations: true) - end - - context 'when there is no search param' do - let(:params) { { attempt_group_search_optimizations: true, force_cte: true } } - - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey - end - end - - context 'when the database is not Postgres' do - let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } - - before do - allow(Gitlab::Database).to receive(:postgresql?).and_return(false) - end - - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey - end - end - - context 'when the force_cte param is falsey' do - let(:params) { { search: 'foo' } } + context 'and the corresponding feature flag is disabled' do + before do + stub_feature_flags(attempt_project_search_optimizations: false) + end - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey + it 'returns false' do + expect(finder.use_cte_for_search?).to be_falsey + end end - end - context 'when the attempt_group_search_optimizations flag is disabled' do - let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } - - before do - stub_feature_flags(attempt_group_search_optimizations: false) - end + context 'and the corresponding feature flag is enabled' do + before do + stub_feature_flags(attempt_project_search_optimizations: true) + end - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey + it 'returns true' do + expect(finder.use_cte_for_search?).to be_truthy + end end end context 'when all conditions are met' do - let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } + let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } it 'returns true' do - expect(finder.use_cte_for_count?).to be_truthy + expect(finder.use_cte_for_search?).to be_truthy end end end diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb index 90d67fd00fc..244f4766a84 100644 --- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb @@ -1,11 +1,11 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| include ProjectForksHelper - def get_action(action, project) + def get_action(action, project, extra_params = {}) if action - get action, params: { author_id: project.creator.id } + get action, params: { author_id: project.creator.id }.merge(extra_params) else - get :index, params: { namespace_id: project.namespace, project_id: project } + get :index, params: { namespace_id: project.namespace, project_id: project }.merge(extra_params) end end @@ -17,23 +17,44 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| end end - before do - @issuable_ids = %w[fix improve/awesome].map do |source_branch| - create_issuable(issuable_type, project, source_branch: source_branch).id + let!(:issuables) do + %w[fix improve/awesome].map do |source_branch| + create_issuable(issuable_type, project, source_branch: source_branch) end end + let(:issuable_ids) { issuables.map(&:id) } + it "creates indexed meta-data object for issuable notes and votes count" do get_action(action, project) meta_data = assigns(:issuable_meta_data) aggregate_failures do - expect(meta_data.keys).to match_array(@issuable_ids) + expect(meta_data.keys).to match_array(issuables.map(&:id)) expect(meta_data.values).to all(be_kind_of(Issuable::IssuableMeta)) end end + context 'searching' do + let(:result_issuable) { issuables.first } + let(:search) { result_issuable.title } + + before do + stub_feature_flags(attempt_project_search_optimizations: true) + end + + # .simple_sorts is the same across all Sortable classes + sorts = ::Issue.simple_sorts.keys + %w[popularity priority label_priority] + sorts.each do |sort| + it "works when sorting by #{sort}" do + get_action(action, project, search: search, sort: sort) + + expect(assigns(:issuable_meta_data).keys).to include(result_issuable.id) + end + end + end + it "avoids N+1 queries" do control = ActiveRecord::QueryRecorder.new { get_action(action, project) } issuable = create_issuable(issuable_type, project, source_branch: 'csv') -- cgit v1.2.1 From 5ec780f431587ff96b50c7cfb36676c6e5f76507 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 15:48:53 +0300 Subject: Extract target branch quick action to shared example --- .../merge_request/user_uses_quick_actions_spec.rb | 79 +--------------------- .../target_branch_quick_action_shared_examples.rb | 77 +++++++++++++++++++++ 2 files changed, 78 insertions(+), 78 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index 56774896795..5e466fb41d0 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -57,6 +57,7 @@ describe 'Merge request > User uses quick actions', :js do end it_behaves_like 'merge quick action' + it_behaves_like 'target_branch quick action' describe 'toggling the WIP prefix in the title from note' do context 'when the current user can toggle the WIP prefix' do @@ -104,83 +105,5 @@ describe 'Merge request > User uses quick actions', :js do end end end - - describe '/target_branch command in merge request' do - let(:another_project) { create(:project, :public, :repository) } - let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } - - before do - another_project.add_maintainer(user) - sign_in(user) - end - - it 'changes target_branch in new merge_request' do - visit project_new_merge_request_path(another_project, new_url_opts) - - fill_in "merge_request_title", with: 'My brand new feature' - fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:" - click_button "Submit merge request" - - merge_request = another_project.merge_requests.first - expect(merge_request.description).to eq "le feature \nFeature description:" - expect(merge_request.target_branch).to eq 'fix' - end - - it 'does not change target branch when merge request is edited' do - new_merge_request = create(:merge_request, source_project: another_project) - - visit edit_project_merge_request_path(another_project, new_merge_request) - fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n" - click_button "Save changes" - - new_merge_request = another_project.merge_requests.first - expect(new_merge_request.description).to include('/target_branch') - expect(new_merge_request.target_branch).not_to eq('fix') - end - end - - describe '/target_branch command from note' do - context 'when the current user can change target branch' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it 'changes target branch from a note' do - add_note("message start \n/target_branch merge-test\n message end.") - - wait_for_requests - expect(page).not_to have_content('/target_branch') - expect(page).to have_content('message start') - expect(page).to have_content('message end.') - - expect(merge_request.reload.target_branch).to eq 'merge-test' - end - - it 'does not fail when target branch does not exists' do - add_note('/target_branch totally_not_existing_branch') - - expect(page).not_to have_content('/target_branch') - - expect(merge_request.target_branch).to eq 'feature' - end - end - - context 'when current user can not change target branch' do - before do - project.add_guest(guest) - sign_in(guest) - visit project_merge_request_path(project, merge_request) - end - - it 'does not change target branch' do - add_note('/target_branch merge-test') - - expect(page).not_to have_content '/target_branch merge-test' - - expect(merge_request.target_branch).to eq 'feature' - end - end - end end end diff --git a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb index ccb4a85325b..cf2bdb1dd68 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb @@ -1,4 +1,81 @@ # frozen_string_literal: true shared_examples 'target_branch quick action' do + describe '/target_branch command in merge request' do + let(:another_project) { create(:project, :public, :repository) } + let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } + + before do + another_project.add_maintainer(user) + sign_in(user) + end + + it 'changes target_branch in new merge_request' do + visit project_new_merge_request_path(another_project, new_url_opts) + + fill_in "merge_request_title", with: 'My brand new feature' + fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:" + click_button "Submit merge request" + + merge_request = another_project.merge_requests.first + expect(merge_request.description).to eq "le feature \nFeature description:" + expect(merge_request.target_branch).to eq 'fix' + end + + it 'does not change target branch when merge request is edited' do + new_merge_request = create(:merge_request, source_project: another_project) + + visit edit_project_merge_request_path(another_project, new_merge_request) + fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n" + click_button "Save changes" + + new_merge_request = another_project.merge_requests.first + expect(new_merge_request.description).to include('/target_branch') + expect(new_merge_request.target_branch).not_to eq('fix') + end + end + + describe '/target_branch command from note' do + context 'when the current user can change target branch' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it 'changes target branch from a note' do + add_note("message start \n/target_branch merge-test\n message end.") + + wait_for_requests + expect(page).not_to have_content('/target_branch') + expect(page).to have_content('message start') + expect(page).to have_content('message end.') + + expect(merge_request.reload.target_branch).to eq 'merge-test' + end + + it 'does not fail when target branch does not exists' do + add_note('/target_branch totally_not_existing_branch') + + expect(page).not_to have_content('/target_branch') + + expect(merge_request.target_branch).to eq 'feature' + end + end + + context 'when current user can not change target branch' do + before do + project.add_guest(guest) + sign_in(guest) + visit project_merge_request_path(project, merge_request) + end + + it 'does not change target branch' do + add_note('/target_branch merge-test') + + expect(page).not_to have_content '/target_branch merge-test' + + expect(merge_request.target_branch).to eq 'feature' + end + end + end end -- cgit v1.2.1 From 8bf88876b8198af71bcfa20ae7038b6a6063e406 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 13:34:26 +0300 Subject: Extract confidential quick action spec to a shared example --- .../issues/user_uses_quick_actions_spec.rb | 39 ++-------------------- .../confidential_quick_action_shared_examples.rb | 31 +++++++++++++++++ 2 files changed, 34 insertions(+), 36 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 362f8a468ec..426e205b30b 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -43,6 +43,7 @@ describe 'Issues > User uses quick actions', :js do describe 'issue-only commands' do let(:user) { create(:user) } let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } before do project.add_maintainer(user) @@ -55,6 +56,8 @@ describe 'Issues > User uses quick actions', :js do wait_for_requests end + it_behaves_like 'confidential quick action' + describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } @@ -137,42 +140,6 @@ describe 'Issues > User uses quick actions', :js do end end - describe 'make issue confidential' do - let(:issue) { create(:issue, project: project) } - let(:original_issue) { create(:issue, project: project) } - - context 'when the current user can update issues' do - it 'does not create a note, and marks the issue as confidential' do - add_note("/confidential") - - expect(page).not_to have_content "/confidential" - expect(page).to have_content 'Commands applied' - expect(page).to have_content "made the issue confidential" - - expect(issue.reload).to be_confidential - end - end - - context 'when the current user cannot update the issue' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it 'does not create a note, and does not mark the issue as confidential' do - add_note("/confidential") - - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content "made the issue confidential" - - expect(issue.reload).not_to be_confidential - end - end - end - describe 'move the issue to another project' do let(:issue) { create(:issue, project: project) } diff --git a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb index c68e5aee842..336500487fe 100644 --- a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb @@ -1,4 +1,35 @@ # frozen_string_literal: true shared_examples 'confidential quick action' do + context 'when the current user can update issues' do + it 'does not create a note, and marks the issue as confidential' do + add_note('/confidential') + + expect(page).not_to have_content '/confidential' + expect(page).to have_content 'Commands applied' + expect(page).to have_content 'made the issue confidential' + + expect(issue.reload).to be_confidential + end + end + + context 'when the current user cannot update the issue' do + let(:guest) { create(:user) } + + before do + project.add_guest(guest) + gitlab_sign_out + sign_in(guest) + visit project_issue_path(project, issue) + end + + it 'does not create a note, and does not mark the issue as confidential' do + add_note('/confidential') + + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content 'made the issue confidential' + + expect(issue.reload).not_to be_confidential + end + end end -- cgit v1.2.1 From e540c0d71e00c4ce031b94cf11ec3de905e87da7 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 4 Apr 2019 13:08:34 +0000 Subject: Fixed test specs - added suggestions to mock data - fixed props to be not required --- spec/controllers/projects_controller_spec.rb | 10 ++ .../user_suggests_changes_on_diff_spec.rb | 111 +++++++++++++++++++-- .../markdown/suggestion_diff_row_spec.js | 98 ++++++++++++++++++ spec/javascripts/notes/mock_data.js | 6 +- .../vue_shared/components/markdown/header_spec.js | 2 +- .../components/markdown/suggestion_diff_spec.js | 66 ++++++++---- .../components/markdown/suggestions_spec.js | 109 +++++++++----------- spec/lib/banzai/suggestions_parser_spec.rb | 32 ------ spec/lib/gitlab/diff/suggestion_spec.rb | 87 ++++++++++++++-- spec/lib/gitlab/diff/suggestions_parser_spec.rb | 61 +++++++++++ spec/models/suggestion_spec.rb | 16 +++ spec/serializers/suggestion_entity_spec.rb | 3 +- spec/services/preview_markdown_service_spec.rb | 73 +++++++++++--- spec/services/suggestions/apply_service_spec.rb | 64 ++++++++++-- 14 files changed, 572 insertions(+), 166 deletions(-) create mode 100644 spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js delete mode 100644 spec/lib/banzai/suggestions_parser_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 356d606d5c5..56d38b9475e 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -703,6 +703,16 @@ describe ProjectsController do expect(JSON.parse(response.body).keys).to match_array(%w(body references)) end + context 'when not authorized' do + let(:private_project) { create(:project, :private) } + + it 'returns 404' do + post :preview_markdown, params: { namespace_id: private_project.namespace, id: private_project, text: '*Markdown* text' } + + expect(response).to have_gitlab_http_status(404) + end + end + context 'state filter on references' do let(:issue) { create(:issue, :closed, project: public_project) } let(:merge_request) { create(:merge_request, :closed, target_project: public_project) } diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index c19e299097e..1b5dd6945e0 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -6,6 +6,14 @@ describe 'User comments on a diff', :js do include MergeRequestDiffHelpers include RepoHelpers + def expect_suggestion_has_content(element, expected_changing_content, expected_suggested_content) + changing_content = element.all(:css, '.line_holder.old').map(&:text) + suggested_content = element.all(:css, '.line_holder.new').map(&:text) + + expect(changing_content).to eq(expected_changing_content) + expect(suggested_content).to eq(expected_suggested_content) + end + let(:project) { create(:project, :repository) } let(:merge_request) do create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test') @@ -33,8 +41,18 @@ describe 'User comments on a diff', :js do page.within('.diff-discussions') do expect(page).to have_button('Apply suggestion') expect(page).to have_content('Suggested change') - expect(page).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') - expect(page).to have_content('# change to a comment') + end + + page.within('.md-suggestion-diff') do + expected_changing_content = [ + "6 url = https://github.com/gitlabhq/gitlab-shell.git" + ] + + expected_suggested_content = [ + "6 # change to a comment" + ] + + expect_suggestion_has_content(page, expected_changing_content, expected_suggested_content) end end @@ -64,7 +82,7 @@ describe 'User comments on a diff', :js do click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) page.within('.js-discussion-note-form') do - fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion\n# or that\n```") + fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion:-2\n# or that\n# heh\n```") click_button('Comment') end @@ -74,11 +92,90 @@ describe 'User comments on a diff', :js do suggestion_1 = page.all(:css, '.md-suggestion-diff')[0] suggestion_2 = page.all(:css, '.md-suggestion-diff')[1] - expect(suggestion_1).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') - expect(suggestion_1).to have_content('# change to a comment') + suggestion_1_expected_changing_content = [ + "6 url = https://github.com/gitlabhq/gitlab-shell.git" + ] + suggestion_1_expected_suggested_content = [ + "6 # change to a comment" + ] + + suggestion_2_expected_changing_content = [ + "4 [submodule \"gitlab-shell\"]", + "5 path = gitlab-shell", + "6 url = https://github.com/gitlabhq/gitlab-shell.git" + ] + suggestion_2_expected_suggested_content = [ + "4 # or that", + "5 # heh" + ] + + expect_suggestion_has_content(suggestion_1, + suggestion_1_expected_changing_content, + suggestion_1_expected_suggested_content) + + expect_suggestion_has_content(suggestion_2, + suggestion_2_expected_changing_content, + suggestion_2_expected_suggested_content) + end + end + end + + context 'multi-line suggestions' do + it 'suggestion is presented' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```") + click_button('Comment') + end - expect(suggestion_2).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') - expect(suggestion_2).to have_content('# or that') + wait_for_requests + + page.within('.diff-discussions') do + expect(page).to have_button('Apply suggestion') + expect(page).to have_content('Suggested change') + end + + page.within('.md-suggestion-diff') do + expected_changing_content = [ + "3 url = git://github.com/randx/six.git", + "4 [submodule \"gitlab-shell\"]", + "5 path = gitlab-shell", + "6 url = https://github.com/gitlabhq/gitlab-shell.git", + "7 [submodule \"gitlab-grack\"]", + "8 path = gitlab-grack", + "9 url = https://gitlab.com/gitlab-org/gitlab-grack.git" + ] + + expected_suggested_content = [ + "3 # change to a", + "4 # comment", + "5 # with", + "6 # broken", + "7 # lines" + ] + + expect_suggestion_has_content(page, expected_changing_content, expected_suggested_content) + end + end + + it 'suggestion is appliable' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```") + click_button('Comment') + end + + wait_for_requests + + page.within('.diff-discussions') do + expect(page).not_to have_content('Applied') + + click_button('Apply suggestion') + wait_for_requests + + expect(page).to have_content('Applied') end end end diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js new file mode 100644 index 00000000000..866d6eb05c6 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js @@ -0,0 +1,98 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import SuggestionDiffRow from '~/vue_shared/components/markdown/suggestion_diff_row.vue'; + +const oldLine = { + can_receive_suggestion: false, + line_code: null, + meta_data: null, + new_line: null, + old_line: 5, + rich_text: '-oldtext', + text: '-oldtext', + type: 'old', +}; + +const newLine = { + can_receive_suggestion: false, + line_code: null, + meta_data: null, + new_line: 6, + old_line: null, + rich_text: '-newtext', + text: '-newtext', + type: 'new', +}; + +describe(SuggestionDiffRow.name, () => { + let wrapper; + + const factory = (options = {}) => { + const localVue = createLocalVue(); + + wrapper = shallowMount(SuggestionDiffRow, { + localVue, + ...options, + }); + }; + + const findOldLineWrapper = () => wrapper.find('.old_line'); + const findNewLineWrapper = () => wrapper.find('.new_line'); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders correctly', () => { + factory({ + propsData: { + line: oldLine, + }, + }); + + expect(wrapper.is('.line_holder')).toBe(true); + }); + + describe('when passed line has type old', () => { + beforeEach(() => { + factory({ + propsData: { + line: oldLine, + }, + }); + }); + + it('has old class when line has type old', () => { + expect(wrapper.find('td').classes()).toContain('old'); + }); + + it('has old line number rendered', () => { + expect(findOldLineWrapper().text()).toBe('5'); + }); + + it('has no new line number rendered', () => { + expect(findNewLineWrapper().text()).toBe(''); + }); + }); + + describe('when passed line has type new', () => { + beforeEach(() => { + factory({ + propsData: { + line: newLine, + }, + }); + }); + + it('has new class when line has type new', () => { + expect(wrapper.find('td').classes()).toContain('new'); + }); + + it('has no old line number rendered', () => { + expect(findOldLineWrapper().text()).toBe(''); + }); + + it('has no new line number rendered', () => { + expect(findNewLineWrapper().text()).toBe('6'); + }); + }); +}); diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 348743081eb..1df5cf9ef68 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -44,8 +44,7 @@ export const noteableDataMock = { milestone: null, milestone_id: null, moved_to_id: null, - preview_note_path: - '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue', + preview_note_path: '/gitlab-org/gitlab-ce/preview_markdown?target_id=98&target_type=Issue', project_id: 2, state: 'opened', time_estimate: 0, @@ -347,8 +346,7 @@ export const loggedOutnoteableData = { }, noteable_note_url: '/group/project/merge_requests/1#note_1', create_note_path: '/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue', - preview_note_path: - '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue', + preview_note_path: '/gitlab-org/gitlab-ce/preview_markdown?target_id=98&target_type=Issue', }; export const collapseNotesMock = [ diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index e733a95288e..d4be2451f0b 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -98,7 +98,7 @@ describe('Markdown field header component', () => { it('renders suggestion template', () => { vm.lineContent = 'Some content'; - expect(vm.mdSuggestion).toEqual('```suggestion\n{text}\n```'); + expect(vm.mdSuggestion).toEqual('```suggestion:-0+0\n{text}\n```'); }); it('does not render suggestion button if `canSuggest` is set to false', () => { diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js index f87c2a92f47..ea74cb9eb21 100644 --- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js @@ -1,21 +1,50 @@ import Vue from 'vue'; import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue'; +import { selectDiffLines } from '~/vue_shared/components/lib/utils/diff_utils'; const MOCK_DATA = { canApply: true, - newLines: [ - { content: 'Line 1\n', lineNumber: 1 }, - { content: 'Line 2\n', lineNumber: 2 }, - { content: 'Line 3\n', lineNumber: 3 }, - ], - fromLine: 1, - fromContent: 'Old content', suggestion: { id: 1, + diff_lines: [ + { + can_receive_suggestion: false, + line_code: null, + meta_data: null, + new_line: null, + old_line: 5, + rich_text: '-test', + text: '-test', + type: 'old', + }, + { + can_receive_suggestion: true, + line_code: null, + meta_data: null, + new_line: 5, + old_line: null, + rich_text: '+new test', + text: '+new test', + type: 'new', + }, + { + can_receive_suggestion: true, + line_code: null, + meta_data: null, + new_line: 5, + old_line: null, + rich_text: '+new test2', + text: '+new test2', + type: 'new', + }, + ], }, helpPagePath: 'path_to_docs', }; +const lines = selectDiffLines(MOCK_DATA.suggestion.diff_lines); +const newLines = lines.filter(line => line.type === 'new'); + describe('Suggestion Diff component', () => { let vm; @@ -39,30 +68,23 @@ describe('Suggestion Diff component', () => { }); it('renders the oldLineNumber', () => { - const fromLine = vm.$el.querySelector('.qa-old-diff-line-number').innerHTML; + const fromLine = vm.$el.querySelector('.old_line').innerHTML; - expect(parseInt(fromLine, 10)).toBe(vm.fromLine); + expect(parseInt(fromLine, 10)).toBe(lines[0].old_line); }); it('renders the oldLineContent', () => { const fromContent = vm.$el.querySelector('.line_content.old').innerHTML; - expect(fromContent.includes(vm.fromContent)).toBe(true); - }); - - it('renders the contents of newLines', () => { - const newLines = vm.$el.querySelectorAll('.line_holder.new'); - - newLines.forEach((line, i) => { - expect(newLines[i].innerHTML.includes(vm.newLines[i].content)).toBe(true); - }); + expect(fromContent.includes(lines[0].text)).toBe(true); }); - it('renders a line number for each line', () => { - const newLineNumbers = vm.$el.querySelectorAll('.qa-new-diff-line-number'); + it('renders new lines', () => { + const newLinesElements = vm.$el.querySelectorAll('.line_holder.new'); - newLineNumbers.forEach((line, i) => { - expect(newLineNumbers[i].innerHTML.includes(vm.newLines[i].lineNumber)).toBe(true); + newLinesElements.forEach((line, i) => { + expect(newLinesElements[i].innerHTML.includes(newLines[i].new_line)).toBe(true); + expect(newLinesElements[i].innerHTML.includes(newLines[i].text)).toBe(true); }); }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js index 33be63a3a1e..b7de40b4831 100644 --- a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js @@ -2,46 +2,52 @@ import Vue from 'vue'; import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue'; const MOCK_DATA = { - fromLine: 1, - fromContent: 'Old content', - suggestions: [], + suggestions: [ + { + id: 1, + appliable: true, + applied: false, + current_user: { + can_apply: true, + }, + diff_lines: [ + { + can_receive_suggestion: false, + line_code: null, + meta_data: null, + new_line: null, + old_line: 5, + rich_text: '-test', + text: '-test', + type: 'old', + }, + { + can_receive_suggestion: true, + line_code: null, + meta_data: null, + new_line: 5, + old_line: null, + rich_text: '+new test', + text: '+new test', + type: 'new', + }, + ], + }, + ], noteHtml: ` +
      +
      -oldtest
      +
      -
      Suggestion 1
      +
      +newtest
      - -
      -
      Suggestion 2
      -
      `, isApplied: false, helpPagePath: 'path_to_docs', }; -const generateLine = content => { - const line = document.createElement('div'); - line.className = 'line'; - line.innerHTML = content; - - return line; -}; - -const generateMockLines = () => { - const line1 = generateLine('Line 1'); - const line2 = generateLine('Line 2'); - const line3 = generateLine('- Line 3'); - const container = document.createElement('div'); - - container.appendChild(line1); - container.appendChild(line2); - container.appendChild(line3); - - return container; -}; - describe('Suggestion component', () => { let vm; - let extractedLines; let diffTable; beforeEach(done => { @@ -51,8 +57,7 @@ describe('Suggestion component', () => { propsData: MOCK_DATA, }).$mount(); - extractedLines = vm.extractNewLines(generateMockLines()); - diffTable = vm.generateDiff(extractedLines).$mount().$el; + diffTable = vm.generateDiff(0).$mount().$el; spyOn(vm, 'renderSuggestions'); vm.renderSuggestions(); @@ -70,32 +75,8 @@ describe('Suggestion component', () => { it('renders suggestions', () => { expect(vm.renderSuggestions).toHaveBeenCalled(); - expect(vm.$el.innerHTML.includes('Suggestion 1')).toBe(true); - expect(vm.$el.innerHTML.includes('Suggestion 2')).toBe(true); - }); - }); - - describe('extractNewLines', () => { - it('extracts suggested lines', () => { - const expectedReturn = [ - { content: 'Line 1\n', lineNumber: 1 }, - { content: 'Line 2\n', lineNumber: 2 }, - { content: '- Line 3\n', lineNumber: 3 }, - ]; - - expect(vm.extractNewLines(generateMockLines())).toEqual(expectedReturn); - }); - - it('increments line number for each extracted line', () => { - expect(extractedLines[0].lineNumber).toEqual(1); - expect(extractedLines[1].lineNumber).toEqual(2); - expect(extractedLines[2].lineNumber).toEqual(3); - }); - - it('returns empty array if no lines are found', () => { - const el = document.createElement('div'); - - expect(vm.extractNewLines(el)).toEqual([]); + expect(vm.$el.innerHTML.includes('oldtest')).toBe(true); + expect(vm.$el.innerHTML.includes('newtest')).toBe(true); }); }); @@ -109,17 +90,17 @@ describe('Suggestion component', () => { }); it('generates a diff table that contains contents the suggested lines', () => { - extractedLines.forEach((line, i) => { - expect(diffTable.innerHTML.includes(extractedLines[i].content)).toBe(true); + MOCK_DATA.suggestions[0].diff_lines.forEach(line => { + const text = line.text.substring(1); + + expect(diffTable.innerHTML.includes(text)).toBe(true); }); }); it('generates a diff table with the correct line number for each suggested line', () => { - const lines = diffTable.getElementsByClassName('qa-new-diff-line-number'); + const lines = diffTable.querySelectorAll('.old_line'); - expect([...lines][0].innerHTML).toBe('1'); - expect([...lines][1].innerHTML).toBe('2'); - expect([...lines][2].innerHTML).toBe('3'); + expect(parseInt([...lines][0].innerHTML, 10)).toBe(5); }); }); }); diff --git a/spec/lib/banzai/suggestions_parser_spec.rb b/spec/lib/banzai/suggestions_parser_spec.rb deleted file mode 100644 index 79658d710ce..00000000000 --- a/spec/lib/banzai/suggestions_parser_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Banzai::SuggestionsParser do - describe '.parse' do - it 'returns a list of suggestion contents' do - markdown = <<-MARKDOWN.strip_heredoc - ```suggestion - foo - bar - ``` - - ``` - nothing - ``` - - ```suggestion - xpto - baz - ``` - - ```thing - this is not a suggestion, it's a thing - ``` - MARKDOWN - - expect(described_class.parse(markdown)).to eq([" foo\n bar", - " xpto\n baz"]) - end - end -end diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb index 71fd25df698..d7ca0e0a522 100644 --- a/spec/lib/gitlab/diff/suggestion_spec.rb +++ b/spec/lib/gitlab/diff/suggestion_spec.rb @@ -10,6 +10,16 @@ describe Gitlab::Diff::Suggestion do lines_above: above, lines_below: below) end + + it 'returns diff lines with correct line numbers' do + diff_lines = suggestion.diff_lines + + expect(diff_lines).to all(be_a(Gitlab::Diff::Line)) + + expected_diff_lines.each_with_index do |expected_line, index| + expect(diff_lines[index].to_hash).to include(expected_line) + end + end end let(:merge_request) { create(:merge_request) } @@ -48,6 +58,18 @@ describe Gitlab::Diff::Suggestion do let(:expected_above) { line - 1 } let(:expected_below) { below } let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + let(:expected_diff_lines) do + [ + { old_pos: 1, new_pos: 1, type: 'old', text: "-require 'fileutils'" }, + { old_pos: 2, new_pos: 1, type: 'old', text: "-require 'open3'" }, + { old_pos: 3, new_pos: 1, type: 'old', text: "-" }, + { old_pos: 4, new_pos: 1, type: 'old', text: "-module Popen" }, + { old_pos: 5, new_pos: 1, type: 'old', text: "- extend self" }, + { old_pos: 6, new_pos: 1, type: 'old', text: "-" }, + { old_pos: 7, new_pos: 1, type: 'new', text: "+# parsed suggestion content" }, + { old_pos: 7, new_pos: 2, type: 'new', text: "+# with comments" } + ] + end it_behaves_like 'correct suggestion raw content' end @@ -59,6 +81,47 @@ describe Gitlab::Diff::Suggestion do let(:expected_below) { below } let(:expected_above) { above } let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + let(:expected_diff_lines) do + [ + { old_pos: 4, new_pos: 4, type: "match", text: "@@ -4 +4" }, + { old_pos: 4, new_pos: 4, type: "old", text: "-module Popen" }, + { old_pos: 5, new_pos: 4, type: "old", text: "- extend self" }, + { old_pos: 6, new_pos: 4, type: "old", text: "-" }, + { old_pos: 7, new_pos: 4, type: "old", text: "- def popen(cmd, path=nil)" }, + { old_pos: 8, new_pos: 4, type: "old", text: "- unless cmd.is_a?(Array)" }, + { old_pos: 9, new_pos: 4, type: "old", text: "- raise RuntimeError, \"System commands must be given as an array of strings\"" }, + { old_pos: 10, new_pos: 4, type: "old", text: "- end" }, + { old_pos: 11, new_pos: 4, type: "old", text: "-" }, + { old_pos: 12, new_pos: 4, type: "old", text: "- path ||= Dir.pwd" }, + { old_pos: 13, new_pos: 4, type: "old", text: "-" }, + { old_pos: 14, new_pos: 4, type: "old", text: "- vars = {" }, + { old_pos: 15, new_pos: 4, type: "old", text: "- \"PWD\" => path" }, + { old_pos: 16, new_pos: 4, type: "old", text: "- }" }, + { old_pos: 17, new_pos: 4, type: "old", text: "-" }, + { old_pos: 18, new_pos: 4, type: "old", text: "- options = {" }, + { old_pos: 19, new_pos: 4, type: "old", text: "- chdir: path" }, + { old_pos: 20, new_pos: 4, type: "old", text: "- }" }, + { old_pos: 21, new_pos: 4, type: "old", text: "-" }, + { old_pos: 22, new_pos: 4, type: "old", text: "- unless File.directory?(path)" }, + { old_pos: 23, new_pos: 4, type: "old", text: "- FileUtils.mkdir_p(path)" }, + { old_pos: 24, new_pos: 4, type: "old", text: "- end" }, + { old_pos: 25, new_pos: 4, type: "old", text: "-" }, + { old_pos: 26, new_pos: 4, type: "old", text: "- @cmd_output = \"\"" }, + { old_pos: 27, new_pos: 4, type: "old", text: "- @cmd_status = 0" }, + { old_pos: 28, new_pos: 4, type: "old", text: "-" }, + { old_pos: 29, new_pos: 4, type: "old", text: "- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|" }, + { old_pos: 30, new_pos: 4, type: "old", text: "- @cmd_output << stdout.read" }, + { old_pos: 31, new_pos: 4, type: "old", text: "- @cmd_output << stderr.read" }, + { old_pos: 32, new_pos: 4, type: "old", text: "- @cmd_status = wait_thr.value.exitstatus" }, + { old_pos: 33, new_pos: 4, type: "old", text: "- end" }, + { old_pos: 34, new_pos: 4, type: "old", text: "-" }, + { old_pos: 35, new_pos: 4, type: "old", text: "- return @cmd_output, @cmd_status" }, + { old_pos: 36, new_pos: 4, type: "old", text: "- end" }, + { old_pos: 37, new_pos: 4, type: "old", text: "-end" }, + { old_pos: 38, new_pos: 4, type: "new", text: "+# parsed suggestion content" }, + { old_pos: 38, new_pos: 5, type: "new", text: "+# with comments" } + ] + end it_behaves_like 'correct suggestion raw content' end @@ -70,17 +133,19 @@ describe Gitlab::Diff::Suggestion do let(:expected_below) { below } let(:expected_above) { above } let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } - - it_behaves_like 'correct suggestion raw content' - end - - context 'when no extra lines (single-line suggestion)' do - let(:line) { 5 } - let(:above) { 0 } - let(:below) { 0 } - let(:expected_below) { below } - let(:expected_above) { above } - let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + let(:expected_diff_lines) do + [ + { old_pos: 3, new_pos: 3, type: "match", text: "@@ -3 +3" }, + { old_pos: 3, new_pos: 3, type: "old", text: "-" }, + { old_pos: 4, new_pos: 3, type: "old", text: "-module Popen" }, + { old_pos: 5, new_pos: 3, type: "old", text: "- extend self" }, + { old_pos: 6, new_pos: 3, type: "old", text: "-" }, + { old_pos: 7, new_pos: 3, type: "old", text: "- def popen(cmd, path=nil)" }, + { old_pos: 8, new_pos: 3, type: "old", text: "- unless cmd.is_a?(Array)" }, + { old_pos: 9, new_pos: 3, type: "new", text: "+# parsed suggestion content" }, + { old_pos: 9, new_pos: 4, type: "new", text: "+# with comments" } + ] + end it_behaves_like 'correct suggestion raw content' end diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb index 1119ea04995..1f2af42f6e7 100644 --- a/spec/lib/gitlab/diff/suggestions_parser_spec.rb +++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb @@ -69,5 +69,66 @@ describe Gitlab::Diff::SuggestionsParser do lines_below: 0) end end + + context 'multi-line suggestions' do + let(:markdown) do + <<-MARKDOWN.strip_heredoc + ```suggestion:-2+1 + # above and below + ``` + + ``` + nothing + ``` + + ```suggestion:-3 + # only above + ``` + + ```suggestion:+3 + # only below + ``` + + ```thing + this is not a suggestion, it's a thing + ``` + MARKDOWN + end + + it 'returns a list of Gitlab::Diff::Suggestion' do + expect(subject).to all(be_a(Gitlab::Diff::Suggestion)) + expect(subject.size).to eq(3) + end + + it 'suggestion with above and below param has correct data' do + from_line = position.new_line - 2 + to_line = position.new_line + 1 + + expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line), + to_content: " # above and below\n", + lines_above: 2, + lines_below: 1) + end + + it 'suggestion with above param has correct data' do + from_line = position.new_line - 3 + to_line = position.new_line + + expect(subject.second.to_hash).to eq(from_content: blob_lines_data(from_line, to_line), + to_content: " # only above\n", + lines_above: 3, + lines_below: 0) + end + + it 'suggestion with below param has correct data' do + from_line = position.new_line + to_line = position.new_line + 3 + + expect(subject.third.to_hash).to eq(from_content: blob_lines_data(from_line, to_line), + to_content: " # only below\n", + lines_above: 0, + lines_below: 3) + end + end end end diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb index cafc725dddb..8d4e9070b19 100644 --- a/spec/models/suggestion_spec.rb +++ b/spec/models/suggestion_spec.rb @@ -21,6 +21,22 @@ describe Suggestion do end end + describe '#diff_lines' do + let(:suggestion) { create(:suggestion, :content_from_repo) } + + it 'returns parsed diff lines' do + expected_diff_lines = Gitlab::Diff::SuggestionDiff.new(suggestion).diff_lines + diff_lines = suggestion.diff_lines + + expect(diff_lines.size).to eq(expected_diff_lines.size) + expect(diff_lines).to all(be_a(Gitlab::Diff::Line)) + + expected_diff_lines.each_with_index do |expected_line, index| + expect(diff_lines[index].to_hash).to eq(expected_line.to_hash) + end + end + end + describe '#appliable?' do context 'when note does not support suggestions' do it 'returns false' do diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb index d38fc2b132b..d282a7f9c7a 100644 --- a/spec/serializers/suggestion_entity_spec.rb +++ b/spec/serializers/suggestion_entity_spec.rb @@ -13,8 +13,7 @@ describe SuggestionEntity do subject { entity.as_json } it 'exposes correct attributes' do - expect(subject).to include(:id, :from_line, :to_line, :appliable, - :applied, :from_content, :to_content) + expect(subject.keys).to match_array([:id, :appliable, :applied, :diff_lines, :current_user]) end it 'exposes current user abilities' do diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index 85515d548a7..a1d31464e07 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe PreviewMarkdownService do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do project.add_developer(user) @@ -20,23 +20,72 @@ describe PreviewMarkdownService do end describe 'suggestions' do - let(:params) { { text: "```suggestion\nfoo\n```", preview_suggestions: preview_suggestions } } + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project) + end + let(:text) { "```suggestion\nfoo\n```" } + let(:params) do + suggestion_params.merge(text: text, + target_type: 'MergeRequest', + target_id: merge_request.iid) + end let(:service) { described_class.new(project, user, params) } context 'when preview markdown param is present' do - let(:preview_suggestions) { true } + let(:path) { "files/ruby/popen.rb" } + let(:line) { 10 } + let(:diff_refs) { merge_request.diff_refs } + + let(:suggestion_params) do + { + preview_suggestions: true, + file_path: path, + line: line, + base_sha: diff_refs.base_sha, + start_sha: diff_refs.start_sha, + head_sha: diff_refs.head_sha + } + end + + it 'returns suggestions referenced in text' do + position = Gitlab::Diff::Position.new(new_path: path, + new_line: line, + diff_refs: diff_refs) + + expect(Gitlab::Diff::SuggestionsParser) + .to receive(:parse) + .with(text, position: position, project: merge_request.project) + .and_call_original - it 'returns users referenced in text' do result = service.execute - expect(result[:suggestions]).to eq(['foo']) + expect(result[:suggestions]).to all(be_a(Gitlab::Diff::Suggestion)) + end + + context 'when user is not authorized' do + let(:another_user) { create(:user) } + let(:service) { described_class.new(project, another_user, params) } + + before do + project.add_guest(another_user) + end + + it 'returns no suggestions' do + result = service.execute + + expect(result[:suggestions]).to be_empty + end end end context 'when preview markdown param is not present' do - let(:preview_suggestions) { false } + let(:suggestion_params) do + { + preview_suggestions: false + } + end - it 'returns users referenced in text' do + it 'returns suggestions referenced in text' do result = service.execute expect(result[:suggestions]).to eq([]) @@ -49,8 +98,8 @@ describe PreviewMarkdownService do let(:params) do { text: "Please do it\n/assign #{user.to_reference}", - quick_actions_target_type: 'Issue', - quick_actions_target_id: issue.id + target_type: 'Issue', + target_id: issue.id } end let(:service) { described_class.new(project, user, params) } @@ -72,7 +121,7 @@ describe PreviewMarkdownService do let(:params) do { text: "My work\n/estimate 2y", - quick_actions_target_type: 'MergeRequest' + target_type: 'MergeRequest' } end let(:service) { described_class.new(project, user, params) } @@ -96,8 +145,8 @@ describe PreviewMarkdownService do let(:params) do { text: "My work\n/tag v1.2.3 Stable release", - quick_actions_target_type: 'Commit', - quick_actions_target_id: commit.id + target_type: 'Commit', + target_id: commit.id } end let(:service) { described_class.new(project, user, params) } diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index 80b5dcac6c7..7732767137c 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -51,6 +51,10 @@ describe Suggestions::ApplyService do diff_refs: merge_request.diff_refs) end + let(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project) + end + let(:suggestion) do create(:suggestion, :content_from_repo, note: diff_note, to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n") @@ -108,12 +112,6 @@ describe Suggestions::ApplyService do target_project: project) end - let!(:diff_note) do - create(:diff_note_on_merge_request, noteable: merge_request, - position: position, - project: project) - end - before do project.add_maintainer(user) end @@ -192,11 +190,6 @@ describe Suggestions::ApplyService do CONTENT end - let(:merge_request) do - create(:merge_request, source_project: project, - target_project: project) - end - def create_suggestion(diff, old_line: nil, new_line: nil, from_content:, to_content:, path:) position = Gitlab::Diff::Position.new(old_path: path, new_path: path, @@ -291,6 +284,55 @@ describe Suggestions::ApplyService do expect(suggestion_2_diff.strip).to eq(expected_suggestion_2_diff.strip) end end + + context 'multi-line suggestion' do + let(:expected_content) do + <<~CONTENT + require 'fileutils' + require 'open3' + + module Popen + extend self + + # multi + # line + + vars = { + "PWD" => path + } + + options = { + chdir: path + } + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + @cmd_output = "" + @cmd_status = 0 + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus + end + + return @cmd_output, @cmd_status + end + end + CONTENT + end + + let(:suggestion) do + create(:suggestion, :content_from_repo, note: diff_note, + lines_above: 2, + lines_below: 3, + to_content: "# multi\n# line\n") + end + + it_behaves_like 'successfully creates commit and updates suggestion' + end end context 'fork-project' do -- cgit v1.2.1 From 7af20f8e510ed389ba39995038d10369a93afe77 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 4 Apr 2019 14:02:42 +0100 Subject: Fix an order-dependent spec failure in spec/migrations/schedule_sync_issuables_state_id_spec.rb --- spec/migrations/schedule_runners_token_encryption_spec.rb | 2 +- spec/migrations/schedule_sync_issuables_state_id_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/migrations/schedule_runners_token_encryption_spec.rb b/spec/migrations/schedule_runners_token_encryption_spec.rb index 376d2795277..97ff6c128f3 100644 --- a/spec/migrations/schedule_runners_token_encryption_spec.rb +++ b/spec/migrations/schedule_runners_token_encryption_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20181121111200_schedule_runners_token_encryption') -describe ScheduleRunnersTokenEncryption, :migration do +describe ScheduleRunnersTokenEncryption, :migration, :sidekiq do let(:settings) { table(:application_settings) } let(:namespaces) { table(:namespaces) } let(:projects) { table(:projects) } diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb index bf974d60b24..bc94f8820bd 100644 --- a/spec/migrations/schedule_sync_issuables_state_id_spec.rb +++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20190214112022_schedule_sync_issuables_state_id.rb') -describe ScheduleSyncIssuablesStateId, :migration do +describe ScheduleSyncIssuablesStateId, :migration, :sidekiq do let(:namespaces) { table(:namespaces) } let(:projects) { table(:projects) } let(:merge_requests) { table(:merge_requests) } -- cgit v1.2.1 From f458c561070d754cd546b07caf60dfa7ffb06293 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Wed, 27 Mar 2019 15:02:25 -0500 Subject: Initial field and query complexity limits It makes all Types::BaseField default to a complexity of 1. Queries themselves now have limited complexity, scaled to the type of user: no user, authenticated user, or an admin user. --- spec/graphql/gitlab_schema_spec.rb | 32 +++++++++++++++++++++++++ spec/graphql/types/base_field_spec.rb | 19 +++++++++++++++ spec/requests/api/graphql/gitlab_schema_spec.rb | 16 +++++++++++++ spec/support/helpers/graphql_helpers.rb | 8 +++++++ 4 files changed, 75 insertions(+) create mode 100644 spec/graphql/types/base_field_spec.rb create mode 100644 spec/requests/api/graphql/gitlab_schema_spec.rb (limited to 'spec') diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index b9ddb427e85..a535d9cdc7e 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GitlabSchema do @@ -31,6 +33,36 @@ describe GitlabSchema do expect(connection).to eq(Gitlab::Graphql::Connections::KeysetConnection) end + context 'for different types of users' do + it 'returns DEFAULT_MAX_COMPLEXITY for no user' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) + + described_class.execute('query') + end + + it 'returns AUTHENTICATED_COMPLEXITY for a logged in user' do + user = build :user + + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY)) + + described_class.execute('query', context: { current_user: user }) + end + + it 'returns ADMIN_COMPLEXITY for an admin user' do + user = build :user, :admin + + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY)) + + described_class.execute('query', context: { current_user: user }) + end + + it 'returns what was passed on the query' do + expect(GraphQL::Schema).to receive(:execute).with('query', { max_complexity: 1234 }) + + described_class.execute('query', max_complexity: 1234) + end + end + def field_instrumenters described_class.instrumenters[:field] end diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb new file mode 100644 index 00000000000..b5697ee5245 --- /dev/null +++ b/spec/graphql/types/base_field_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::BaseField do + context 'when considering complexity' do + it 'defaults to 1' do + field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true) + + expect(field.to_graphql.complexity).to eq 1 + end + + it 'has specified value' do + field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12) + + expect(field.to_graphql.complexity).to eq 12 + end + end +end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb new file mode 100644 index 00000000000..708a000532b --- /dev/null +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe 'GitlabSchema configurations' do + include GraphqlHelpers + + let(:project) { create(:project, :repository) } + let!(:query) { graphql_query_for('project', 'fullPath' => project.full_path) } + + it 'shows an error if complexity it too high' do + allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 + + post_graphql(query, current_user: nil) + + expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') + end +end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index ca28325eab9..f59f42ee902 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -93,6 +93,8 @@ module GraphqlHelpers end def all_graphql_fields_for(class_name, parent_types = Set.new) + allow_unlimited_graphql_complexity + type = GitlabSchema.types[class_name.to_s] return "" unless type @@ -170,4 +172,10 @@ module GraphqlHelpers field_type end + + # for most tests, we want to allow unlimited complexity + def allow_unlimited_graphql_complexity + allow_any_instance_of(GitlabSchema).to receive(:max_complexity).and_return nil + allow(GitlabSchema).to receive(:max_query_complexity).with(any_args).and_return nil + end end -- cgit v1.2.1 From 435d98c9cee0d32305a6fd20995f41849749e8eb Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 4 Apr 2019 13:50:31 +0000 Subject: Monitor GraphQL with Prometheus Extends graphql's platform tracing class to observe duration of graphql methods. In graphql 1.8.11 is added prometheus class but it's not very useful for us because it uses prometheus_exporter to export results. --- spec/lib/gitlab/graphql/tracing_spec.rb | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 spec/lib/gitlab/graphql/tracing_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/tracing_spec.rb b/spec/lib/gitlab/graphql/tracing_spec.rb new file mode 100644 index 00000000000..7300a9a572e --- /dev/null +++ b/spec/lib/gitlab/graphql/tracing_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Tracing do + let(:graphql_duration_seconds_histogram) { double('Gitlab::Metrics::NullMetric') } + + it 'updates graphql histogram with expected labels' do + query = 'query { users { id } }' + tracer = described_class.new + + allow(tracer) + .to receive(:graphql_duration_seconds) + .and_return(graphql_duration_seconds_histogram) + + expect_metric('graphql.lex', 'lex') + expect_metric('graphql.parse', 'parse') + expect_metric('graphql.validate', 'validate') + expect_metric('graphql.analyze', 'analyze_multiplex') + expect_metric('graphql.execute', 'execute_query_lazy') + expect_metric('graphql.execute', 'execute_multiplex') + + GitlabSchema.execute(query, context: { tracers: [tracer] }) + end + + private + + def expect_metric(platform_key, key) + expect(graphql_duration_seconds_histogram) + .to receive(:observe) + .with({ platform_key: platform_key, key: key }, be > 0.0) + end +end -- cgit v1.2.1 From 6643b92b8807e2d59f36d676303b89ea01824f22 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Wed, 20 Mar 2019 18:39:18 -0500 Subject: Use parent object when authorizing scalar types --- spec/graphql/features/authorization_spec.rb | 53 ++++++++++++ .../authorize/authorize_field_service_spec.rb | 95 +++++++++++++++------- 2 files changed, 117 insertions(+), 31 deletions(-) (limited to 'spec') diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb index f863c4444b8..00e31568a9e 100644 --- a/spec/graphql/features/authorization_spec.rb +++ b/spec/graphql/features/authorization_spec.rb @@ -75,6 +75,59 @@ describe 'Gitlab::Graphql::Authorization' do end end + describe 'Field authorizations when field is a built in type' do + let(:query_type) do + query_factory do |query| + query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } + end + end + + describe 'with a single permission' do + let(:type) do + type_factory do |type| + type.field :name, GraphQL::STRING_TYPE, null: true, authorize: permission_single + end + end + + it 'returns the protected field when user has permission' do + permit(permission_single) + + expect(subject).to eq('name' => test_object.name) + end + + it 'returns nil when user is not authorized' do + expect(subject).to eq('name' => nil) + end + end + + describe 'with a collection of permissions' do + let(:type) do + permissions = permission_collection + type_factory do |type| + type.field :name, GraphQL::STRING_TYPE, null: true do + authorize permissions + end + end + end + + it 'returns the protected field when user has all permissions' do + permit(*permission_collection) + + expect(subject).to eq('name' => test_object.name) + end + + it 'returns nil when user only has one of the permissions' do + permit(permission_collection.first) + + expect(subject).to eq('name' => nil) + end + + it 'returns nil when user only has none of the permissions' do + expect(subject).to eq('name' => nil) + end + end + end + describe 'Type authorizations' do let(:query_type) do query_factory do |query| diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb index ce320a2bdb0..6114aca0616 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb @@ -9,55 +9,88 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do let(:current_user) { double(:current_user) } let(:abilities) { [double(:first_ability), double(:last_ability)] } - let(:checker) do - service = described_class.new(double(resolve_proc: proc {})) - allow(service).to receive(:authorizations).and_return(abilities) - service.__send__(:build_checker, current_user) - end + context 'when authorizing against the object' do + let(:checker) do + service = described_class.new(double(resolve_proc: proc {})) + allow(service).to receive(:authorizations).and_return(abilities) + service.__send__(:build_checker, current_user, nil) + end - it 'returns a checker which checks for a single object' do - object = double(:object) + it 'returns a checker which checks for a single object' do + object = double(:object) - abilities.each do |ability| - spy_ability_check_for(ability, object, passed: true) - end + abilities.each do |ability| + spy_ability_check_for(ability, object, passed: true) + end - expect(checker.call(object)).to eq(object) - end + expect(checker.call(object)).to eq(object) + end - it 'returns a checker which checks for all objects' do - objects = [double(:first), double(:last)] + it 'returns a checker which checks for all objects' do + objects = [double(:first), double(:last)] - abilities.each do |ability| - objects.each do |object| - spy_ability_check_for(ability, object, passed: true) + abilities.each do |ability| + objects.each do |object| + spy_ability_check_for(ability, object, passed: true) + end end + + expect(checker.call(objects)).to eq(objects) end - expect(checker.call(objects)).to eq(objects) - end + context 'when some objects would not pass the check' do + it 'returns nil when it is single object' do + disallowed = double(:object) + + spy_ability_check_for(abilities.first, disallowed, passed: false) - context 'when some objects would not pass the check' do - it 'returns nil when it is single object' do - disallowed = double(:object) + expect(checker.call(disallowed)).to be_nil + end + + it 'returns only objects which passed when there are more than one' do + allowed = double(:allowed) + disallowed = double(:disallowed) - spy_ability_check_for(abilities.first, disallowed, passed: false) + spy_ability_check_for(abilities.first, disallowed, passed: false) - expect(checker.call(disallowed)).to be_nil + abilities.each do |ability| + spy_ability_check_for(ability, allowed, passed: true) + end + + expect(checker.call([disallowed, allowed])).to contain_exactly(allowed) + end end + end + + context 'when authorizing against another object' do + let(:authorizing_obj) { double(:object) } - it 'returns only objects which passed when there are more than one' do - allowed = double(:allowed) - disallowed = double(:disallowed) + let(:checker) do + service = described_class.new(double(resolve_proc: proc {})) + allow(service).to receive(:authorizations).and_return(abilities) + service.__send__(:build_checker, current_user, authorizing_obj) + end + + it 'returns a checker which checks for a single object' do + object = double(:object) + + abilities.each do |ability| + spy_ability_check_for(ability, authorizing_obj, passed: true) + end + + expect(checker.call(object)).to eq(object) + end - spy_ability_check_for(abilities.first, disallowed, passed: false) + it 'returns a checker which checks for all objects' do + objects = [double(:first), double(:last)] abilities.each do |ability| - spy_ability_check_for(ability, allowed, passed: true) + objects.each do |object| + spy_ability_check_for(ability, authorizing_obj, passed: true) + end end - expect(checker.call([disallowed, allowed])) - .to contain_exactly(allowed) + expect(checker.call(objects)).to eq(objects) end end end -- cgit v1.2.1 From 17bee986bc971cc7d04c4b767cc026577eb56c6a Mon Sep 17 00:00:00 2001 From: Gosia Ksionek Date: Thu, 4 Apr 2019 14:19:57 +0000 Subject: Add cr remarks Chnage method used in model to make it more efficient database-wise Add additional spec --- spec/models/group_spec.rb | 26 ++++++++ spec/policies/group_member_policy_spec.rb | 105 ++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 spec/policies/group_member_policy_spec.rb (limited to 'spec') diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 2c6abddca17..b2ffd5812ab 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -364,6 +364,32 @@ describe Group do it { expect(group.has_maintainer?(nil)).to be_falsey } end + describe '#last_owner?' do + before do + @members = setup_group_members(group) + end + + it { expect(group.last_owner?(@members[:owner])).to be_truthy } + + context 'with two owners' do + before do + create(:group_member, :owner, group: group) + end + + it { expect(group.last_owner?(@members[:owner])).to be_falsy } + end + + context 'with owners from a parent', :postgresql do + before do + parent_group = create(:group) + create(:group_member, :owner, group: parent_group) + group.update(parent: parent_group) + end + + it { expect(group.last_owner?(@members[:owner])).to be_falsy } + end + end + describe '#lfs_enabled?' do context 'LFS enabled globally' do before do diff --git a/spec/policies/group_member_policy_spec.rb b/spec/policies/group_member_policy_spec.rb new file mode 100644 index 00000000000..7bd7184cffe --- /dev/null +++ b/spec/policies/group_member_policy_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GroupMemberPolicy do + let(:guest) { create(:user) } + let(:owner) { create(:user) } + let(:group) { create(:group, :private) } + + before do + group.add_guest(guest) + group.add_owner(owner) + end + + let(:member_related_permissions) do + [:update_group_member, :destroy_group_member] + end + + let(:membership) { current_user.members.first } + + subject { described_class.new(current_user, membership) } + + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } + end + + context 'with guest user' do + let(:current_user) { guest } + + it do + expect_disallowed(:member_related_permissions) + end + end + + context 'with one owner' do + let(:current_user) { owner } + + it do + expect_disallowed(:destroy_group_member) + expect_disallowed(:update_group_member) + end + end + + context 'with more than one owner' do + let(:current_user) { owner } + + before do + group.add_owner(create(:user)) + end + + it do + expect_allowed(:destroy_group_member) + expect_allowed(:update_group_member) + end + end + + context 'with the group parent', :postgresql do + let(:current_user) { create :user } + let(:subgroup) { create(:group, :private, parent: group)} + + before do + group.add_owner(owner) + subgroup.add_owner(current_user) + end + + it do + expect_allowed(:destroy_group_member) + expect_allowed(:update_group_member) + end + end + + context 'without group parent' do + let(:current_user) { create :user } + let(:subgroup) { create(:group, :private)} + + before do + subgroup.add_owner(current_user) + end + + it do + expect_disallowed(:destroy_group_member) + expect_disallowed(:update_group_member) + end + end + + context 'without group parent with two owners' do + let(:current_user) { create :user } + let(:other_user) { create :user } + let(:subgroup) { create(:group, :private)} + + before do + subgroup.add_owner(current_user) + subgroup.add_owner(other_user) + end + + it do + expect_allowed(:destroy_group_member) + expect_allowed(:update_group_member) + end + end +end -- cgit v1.2.1 From 5b6db251a8b8d4528ed161b0462a62ab0bdba458 Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 4 Apr 2019 14:54:25 +0000 Subject: Consider array params on rendering MR list on dashboard This fixes the bug, when approver filter is provided, but dashboard asks to enter any filter --- .../concerns/issuable_collections_spec.rb | 4 +-- spec/controllers/dashboard_controller_spec.rb | 33 ++++++++++++++++++++++ spec/features/dashboard/merge_requests_spec.rb | 25 ++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb index 8580900215c..a82b66361ca 100644 --- a/spec/controllers/concerns/issuable_collections_spec.rb +++ b/spec/controllers/concerns/issuable_collections_spec.rb @@ -117,7 +117,7 @@ describe IssuableCollections do due_date: '2017-01-01', group_id: '3', iids: '4', - label_name: 'foo', + label_name: ['foo'], milestone_title: 'bar', my_reaction_emoji: 'thumbsup', non_archived: 'true', @@ -142,7 +142,7 @@ describe IssuableCollections do 'author_id' => '2', 'author_username' => 'user2', 'confidential' => true, - 'label_name' => 'foo', + 'label_name' => ['foo'], 'milestone_title' => 'bar', 'my_reaction_emoji' => 'thumbsup', 'due_date' => '2017-01-01', diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb index c857a78d5e8..b039ec2906c 100644 --- a/spec/controllers/dashboard_controller_spec.rb +++ b/spec/controllers/dashboard_controller_spec.rb @@ -23,4 +23,37 @@ describe DashboardController do it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics + + describe "#check_filters_presence!" do + let(:user) { create(:user) } + + before do + sign_in(user) + get :merge_requests, params: params + end + + context "no filters" do + let(:params) { {} } + + it 'sets @no_filters_set to false' do + expect(assigns[:no_filters_set]).to eq(true) + end + end + + context "scalar filters" do + let(:params) { { author_id: user.id } } + + it 'sets @no_filters_set to false' do + expect(assigns[:no_filters_set]).to eq(false) + end + end + + context "array filters" do + let(:params) { { label_name: ['bug'] } } + + it 'sets @no_filters_set to false' do + expect(assigns[:no_filters_set]).to eq(false) + end + end + end end diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 9ffa75aee47..4965770605a 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -44,6 +44,8 @@ describe 'Dashboard Merge Requests' do end context 'merge requests exist' do + let(:label) { create(:label) } + let!(:assigned_merge_request) do create(:merge_request, assignee: current_user, @@ -72,6 +74,14 @@ describe 'Dashboard Merge Requests' do target_project: public_project, source_project: forked_project) end + let!(:labeled_merge_request) do + create(:labeled_merge_request, + source_branch: 'labeled', + labels: [label], + author: current_user, + source_project: project) + end + let!(:other_merge_request) do create(:merge_request, source_branch: 'fix', @@ -90,6 +100,7 @@ describe 'Dashboard Merge Requests' do expect(page).not_to have_content(authored_merge_request.title) expect(page).not_to have_content(authored_merge_request_from_fork.title) expect(page).not_to have_content(other_merge_request.title) + expect(page).not_to have_content(labeled_merge_request.title) end it 'shows authored merge requests', :js do @@ -98,7 +109,21 @@ describe 'Dashboard Merge Requests' do expect(page).to have_content(authored_merge_request.title) expect(page).to have_content(authored_merge_request_from_fork.title) + expect(page).to have_content(labeled_merge_request.title) + + expect(page).not_to have_content(assigned_merge_request.title) + expect(page).not_to have_content(assigned_merge_request_from_fork.title) + expect(page).not_to have_content(other_merge_request.title) + end + + it 'shows labeled merge requests', :js do + reset_filters + input_filtered_search("label:#{label.name}") + expect(page).to have_content(labeled_merge_request.title) + + expect(page).not_to have_content(authored_merge_request.title) + expect(page).not_to have_content(authored_merge_request_from_fork.title) expect(page).not_to have_content(assigned_merge_request.title) expect(page).not_to have_content(assigned_merge_request_from_fork.title) expect(page).not_to have_content(other_merge_request.title) -- cgit v1.2.1 From bd1122ee2fd5c8083c945390a1593b2b5774d8a1 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 4 Apr 2019 02:44:16 -0500 Subject: Fix vue render error for IDE status bar **What?** A Vue warning that `ide_status_bar` sent a `Boolean` to a `String` property (`img-src). **What was the fix?** Previously, `latestPipeline` could be one of the following values: | | | |----------|--------| | `null` | The pipeline hasn't loaded yet | | `false` | The pipeline has loaded, but nothing was returned. | | `Object` | The piepline has loaded. | Giving a semantic meaning to different falsey values hurts maintainability. This commit fixes the above problem by removing the `false` value and introducing a `hasLoadedPipeline` state property. --- .../ide/components/pipelines/list_spec.js | 31 ++++-- .../ide/stores/modules/pipelines/mutations_spec.js | 106 +++++++++++---------- 2 files changed, 81 insertions(+), 56 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js index 68487733cb9..80829f2358a 100644 --- a/spec/javascripts/ide/components/pipelines/list_spec.js +++ b/spec/javascripts/ide/components/pipelines/list_spec.js @@ -11,6 +11,8 @@ describe('IDE pipelines list', () => { let vm; let mock; + const findLoadingState = () => vm.$el.querySelector('.loading-container'); + beforeEach(done => { const store = createStore(); @@ -95,7 +97,7 @@ describe('IDE pipelines list', () => { describe('empty state', () => { it('renders pipelines empty state', done => { - vm.$store.state.pipelines.latestPipeline = false; + vm.$store.state.pipelines.latestPipeline = null; vm.$nextTick(() => { expect(vm.$el.querySelector('.empty-state')).not.toBe(null); @@ -106,15 +108,30 @@ describe('IDE pipelines list', () => { }); describe('loading state', () => { - it('renders loading state when there is no latest pipeline', done => { - vm.$store.state.pipelines.latestPipeline = null; + beforeEach(() => { vm.$store.state.pipelines.isLoadingPipeline = true; + }); - vm.$nextTick(() => { - expect(vm.$el.querySelector('.loading-container')).not.toBe(null); + it('does not render when pipeline has loaded before', done => { + vm.$store.state.pipelines.hasLoadedPipeline = true; - done(); - }); + vm.$nextTick() + .then(() => { + expect(findLoadingState()).toBe(null); + }) + .then(done) + .catch(done.fail); + }); + + it('renders loading state when there is no latest pipeline', done => { + vm.$store.state.pipelines.hasLoadedPipeline = false; + + vm.$nextTick() + .then(() => { + expect(findLoadingState()).not.toBe(null); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js index eb7346bd5fc..b558c45f574 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js @@ -27,63 +27,71 @@ describe('IDE pipelines mutations', () => { }); describe(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, () => { - it('sets loading to false on success', () => { - mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS]( - mockedState, - fullPipelinesResponse.data.pipelines[0], - ); + const itSetsPipelineLoadingStates = () => { + it('sets has loaded to true', () => { + expect(mockedState.hasLoadedPipeline).toBe(true); + }); - expect(mockedState.isLoadingPipeline).toBe(false); - }); + it('sets loading to false on success', () => { + expect(mockedState.isLoadingPipeline).toBe(false); + }); + }; + + describe('with pipeline', () => { + beforeEach(() => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS]( + mockedState, + fullPipelinesResponse.data.pipelines[0], + ); + }); - it('sets latestPipeline', () => { - mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS]( - mockedState, - fullPipelinesResponse.data.pipelines[0], - ); + itSetsPipelineLoadingStates(); - expect(mockedState.latestPipeline).toEqual({ - id: '51', - path: 'test', - commit: { id: '123' }, - details: { status: jasmine.any(Object) }, - yamlError: undefined, + it('sets latestPipeline', () => { + expect(mockedState.latestPipeline).toEqual({ + id: '51', + path: 'test', + commit: { id: '123' }, + details: { status: jasmine.any(Object) }, + yamlError: undefined, + }); }); - }); - it('does not set latest pipeline if pipeline is null', () => { - mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null); - - expect(mockedState.latestPipeline).toEqual(false); + it('sets stages', () => { + expect(mockedState.stages.length).toBe(2); + expect(mockedState.stages).toEqual([ + { + id: 0, + dropdownPath: stages[0].dropdown_path, + name: stages[0].name, + status: stages[0].status, + isCollapsed: false, + isLoading: false, + jobs: [], + }, + { + id: 1, + dropdownPath: stages[1].dropdown_path, + name: stages[1].name, + status: stages[1].status, + isCollapsed: false, + isLoading: false, + jobs: [], + }, + ]); + }); }); - it('sets stages', () => { - mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS]( - mockedState, - fullPipelinesResponse.data.pipelines[0], - ); + describe('with null', () => { + beforeEach(() => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null); + }); - expect(mockedState.stages.length).toBe(2); - expect(mockedState.stages).toEqual([ - { - id: 0, - dropdownPath: stages[0].dropdown_path, - name: stages[0].name, - status: stages[0].status, - isCollapsed: false, - isLoading: false, - jobs: [], - }, - { - id: 1, - dropdownPath: stages[1].dropdown_path, - name: stages[1].name, - status: stages[1].status, - isCollapsed: false, - isLoading: false, - jobs: [], - }, - ]); + itSetsPipelineLoadingStates(); + + it('does not set latest pipeline if pipeline is null', () => { + expect(mockedState.latestPipeline).toEqual(null); + }); }); }); -- cgit v1.2.1 From 8a833c720e91c7b4d764e85c30e3be18ee5221fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 4 Apr 2019 15:00:56 +0000 Subject: Allow to use untrusted Regexp via feature flag This brings support for untrusted regexp for 'only:refs:' when enabled via feature flag: alllow_unsafe_ruby_regexp. This is by default disabled, and should not be used in production --- spec/lib/gitlab/ci/build/policy/refs_spec.rb | 26 +++++++++ spec/lib/gitlab/ci/config/entry/policy_spec.rb | 67 +++++++++++++++++++++ .../gitlab/untrusted_regexp/ruby_syntax_spec.rb | 68 ++++++++++++++++++---- 3 files changed, 150 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb index ec0450643c3..22ca681cfd3 100644 --- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb @@ -101,6 +101,32 @@ describe Gitlab::Ci::Build::Policy::Refs do expect(described_class.new(['/fix-.*/'])) .not_to be_satisfied_by(pipeline) end + + context 'when unsafe regexp is used' do + let(:subject) { described_class.new(['/^(?!master).+/']) } + + context 'when allow_unsafe_ruby_regexp is disabled' do + before do + stub_feature_flags(allow_unsafe_ruby_regexp: false) + end + + it 'ignores invalid regexp' do + expect(subject) + .not_to be_satisfied_by(pipeline) + end + end + + context 'when allow_unsafe_ruby_regexp is enabled' do + before do + stub_feature_flags(allow_unsafe_ruby_regexp: true) + end + + it 'is satisfied by regexp' do + expect(subject) + .to be_satisfied_by(pipeline) + end + end + end end context 'malicious regexp' do diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 1c987e13a9a..fba5671594d 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,4 +1,5 @@ require 'fast_spec_helper' +require 'support/helpers/stub_feature_flags' require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Policy do @@ -33,6 +34,44 @@ describe Gitlab::Ci::Config::Entry::Policy do end end + context 'when config is an empty regexp' do + let(:config) { ['//'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when using unsafe regexp' do + include StubFeatureFlags + + let(:config) { ['/^(?!master).+/'] } + + subject { described_class.new([regexp]) } + + context 'when allow_unsafe_ruby_regexp is disabled' do + before do + stub_feature_flags(allow_unsafe_ruby_regexp: false) + end + + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + context 'when allow_unsafe_ruby_regexp is enabled' do + before do + stub_feature_flags(allow_unsafe_ruby_regexp: true) + end + + it 'is valid' do + expect(entry).to be_valid + end + end + end + context 'when config is a special keyword' do let(:config) { %w[tags triggers branches] } @@ -67,6 +106,34 @@ describe Gitlab::Ci::Config::Entry::Policy do end end + context 'when using unsafe regexp' do + include StubFeatureFlags + + let(:config) { { refs: ['/^(?!master).+/'] } } + + subject { described_class.new([regexp]) } + + context 'when allow_unsafe_ruby_regexp is disabled' do + before do + stub_feature_flags(allow_unsafe_ruby_regexp: false) + end + + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + context 'when allow_unsafe_ruby_regexp is enabled' do + before do + stub_feature_flags(allow_unsafe_ruby_regexp: true) + end + + it 'is valid' do + expect(entry).to be_valid + end + end + end + context 'when specifying kubernetes policy' do let(:config) { { kubernetes: 'active' } } diff --git a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb index 005d41580de..f1882e03581 100644 --- a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb @@ -1,5 +1,6 @@ require 'fast_spec_helper' require 'support/shared_examples/malicious_regexp_shared_examples' +require 'support/helpers/stub_feature_flags' describe Gitlab::UntrustedRegexp::RubySyntax do describe '.matches_syntax?' do @@ -33,6 +34,12 @@ describe Gitlab::UntrustedRegexp::RubySyntax do end end + context 'when regexp is empty' do + it 'fabricates regexp correctly' do + expect(described_class.fabricate('//')).not_to be_nil + end + end + context 'when regexp is a raw pattern' do it 'returns error' do expect(described_class.fabricate('some .* thing')).to be_nil @@ -41,24 +48,63 @@ describe Gitlab::UntrustedRegexp::RubySyntax do end describe '.fabricate!' do - context 'when regexp is using /regexp/ scheme with flags' do - it 'fabricates regexp with a single flag' do - regexp = described_class.fabricate!('/something/i') + context 'safe regexp is used' do + context 'when regexp is using /regexp/ scheme with flags' do + it 'fabricates regexp with a single flag' do + regexp = described_class.fabricate!('/something/i') + + expect(regexp).to eq Gitlab::UntrustedRegexp.new('(?i)something') + expect(regexp.scan('SOMETHING')).to be_one + end - expect(regexp).to eq Gitlab::UntrustedRegexp.new('(?i)something') - expect(regexp.scan('SOMETHING')).to be_one + it 'fabricates regexp with multiple flags' do + regexp = described_class.fabricate!('/something/im') + + expect(regexp).to eq Gitlab::UntrustedRegexp.new('(?im)something') + end + + it 'fabricates regexp without flags' do + regexp = described_class.fabricate!('/something/') + + expect(regexp).to eq Gitlab::UntrustedRegexp.new('something') + end end + end - it 'fabricates regexp with multiple flags' do - regexp = described_class.fabricate!('/something/im') + context 'when unsafe regexp is used' do + include StubFeatureFlags - expect(regexp).to eq Gitlab::UntrustedRegexp.new('(?im)something') + before do + stub_feature_flags(allow_unsafe_ruby_regexp: true) + + allow(Gitlab::UntrustedRegexp).to receive(:new).and_raise(RegexpError) end - it 'fabricates regexp without flags' do - regexp = described_class.fabricate!('/something/') + context 'when no fallback is enabled' do + it 'raises an exception' do + expect { described_class.fabricate!('/something/') } + .to raise_error(RegexpError) + end + end + + context 'when fallback is used' do + it 'fabricates regexp with a single flag' do + regexp = described_class.fabricate!('/something/i', fallback: true) + + expect(regexp).to eq Regexp.new('something', Regexp::IGNORECASE) + end + + it 'fabricates regexp with multiple flags' do + regexp = described_class.fabricate!('/something/im', fallback: true) + + expect(regexp).to eq Regexp.new('something', Regexp::IGNORECASE | Regexp::MULTILINE) + end + + it 'fabricates regexp without flags' do + regexp = described_class.fabricate!('/something/', fallback: true) - expect(regexp).to eq Gitlab::UntrustedRegexp.new('something') + expect(regexp).to eq Regexp.new('something') + end end end -- cgit v1.2.1 From 0adedbb4822a8daaa215b33c88ace136c31d042d Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Mon, 1 Apr 2019 15:11:08 +0900 Subject: Fix the bug that the project statistics is not updated --- spec/workers/project_cache_worker_spec.rb | 36 +++++++++------- .../update_project_statistics_worker_spec.rb | 48 ++++++++++++++++++++++ 2 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 spec/workers/update_project_statistics_worker_spec.rb (limited to 'spec') diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index a7353227043..724947228b7 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -7,9 +7,9 @@ describe ProjectCacheWorker do let(:worker) { described_class.new } let(:project) { create(:project, :repository) } - let(:statistics) { project.statistics } - let(:lease_key) { "project_cache_worker:#{project.id}:update_statistics" } + let(:lease_key) { (["project_cache_worker", project.id] + statistics.sort).join(":") } let(:lease_timeout) { ProjectCacheWorker::LEASE_TIMEOUT } + let(:statistics) { [] } describe '#perform' do before do @@ -35,14 +35,6 @@ describe ProjectCacheWorker do end context 'with an existing project' do - it 'updates the project statistics' do - expect(worker).to receive(:update_statistics) - .with(kind_of(Project), %i(repository_size)) - .and_call_original - - worker.perform(project.id, [], %w(repository_size)) - end - it 'refreshes the method caches' do expect_any_instance_of(Repository).to receive(:refresh_method_caches) .with(%i(readme)) @@ -51,6 +43,18 @@ describe ProjectCacheWorker do worker.perform(project.id, %w(readme)) end + context 'with statistics' do + let(:statistics) { %w(repository_size) } + + it 'updates the project statistics' do + expect(worker).to receive(:update_statistics) + .with(kind_of(Project), %i(repository_size)) + .and_call_original + + worker.perform(project.id, [], statistics) + end + end + context 'with plain readme' do it 'refreshes the method caches' do allow(MarkupHelper).to receive(:gitlab_markdown?).and_return(false) @@ -66,13 +70,15 @@ describe ProjectCacheWorker do end describe '#update_statistics' do + let(:statistics) { %w(repository_size) } + context 'when a lease could not be obtained' do it 'does not update the repository size' do stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) - expect(statistics).not_to receive(:refresh!) + expect(UpdateProjectStatisticsWorker).not_to receive(:perform_in) - worker.update_statistics(project) + worker.update_statistics(project, statistics.map(&:to_sym)) end end @@ -80,11 +86,11 @@ describe ProjectCacheWorker do it 'updates the project statistics' do stub_exclusive_lease(lease_key, timeout: lease_timeout) - expect(statistics).to receive(:refresh!) - .with(only: %i(repository_size)) + expect(UpdateProjectStatisticsWorker).to receive(:perform_in) + .with(lease_timeout, project.id, statistics.map(&:to_sym)) .and_call_original - worker.update_statistics(project, %i(repository_size)) + worker.update_statistics(project, statistics.map(&:to_sym)) end end end diff --git a/spec/workers/update_project_statistics_worker_spec.rb b/spec/workers/update_project_statistics_worker_spec.rb new file mode 100644 index 00000000000..3411e10da7e --- /dev/null +++ b/spec/workers/update_project_statistics_worker_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe UpdateProjectStatisticsWorker do + let(:worker) { described_class.new } + let(:project) { create(:project, :repository) } + + describe '#perform' do + context 'with a non-existing project' do + it 'does nothing' do + expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!) + + worker.perform(-1) + end + end + + context 'with an existing project without a repository' do + it 'does nothing' do + allow_any_instance_of(Repository).to receive(:exists?).and_return(false) + + expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!) + + worker.perform(project.id) + end + end + + context 'with an existing project' do + it 'refreshes the project statistics' do + expect_any_instance_of(ProjectStatistics).to receive(:refresh!) + .with(only: []) + .and_call_original + + worker.perform(project.id) + end + + context 'with a specific statistics target' do + it 'refreshes the project repository size' do + statistics_target = %w(repository_size) + + expect_any_instance_of(ProjectStatistics).to receive(:refresh!) + .with(only: statistics_target.map(&:to_sym)) + .and_call_original + + worker.perform(project.id, statistics_target) + end + end + end + end +end -- cgit v1.2.1 From e6780501cbfd0cae31fadd15b2964204171091cc Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Fri, 5 Apr 2019 00:17:52 +0900 Subject: Refactor project_cache_worker_key --- spec/workers/project_cache_worker_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index 724947228b7..7053bfc2bfd 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -7,7 +7,7 @@ describe ProjectCacheWorker do let(:worker) { described_class.new } let(:project) { create(:project, :repository) } - let(:lease_key) { (["project_cache_worker", project.id] + statistics.sort).join(":") } + let(:lease_key) { ["project_cache_worker", project.id, *statistics.sort].join(":") } let(:lease_timeout) { ProjectCacheWorker::LEASE_TIMEOUT } let(:statistics) { [] } -- cgit v1.2.1 From 074a1797fe581c8eb5cc65bd56af584d5c500688 Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Fri, 5 Apr 2019 00:18:17 +0900 Subject: Update the project statistics immediatelly --- spec/workers/project_cache_worker_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index 7053bfc2bfd..d2445f420f8 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -86,6 +86,10 @@ describe ProjectCacheWorker do it 'updates the project statistics' do stub_exclusive_lease(lease_key, timeout: lease_timeout) + expect(project.statistics).to receive(:refresh!) + .with(only: statistics.map(&:to_sym)) + .and_call_original + expect(UpdateProjectStatisticsWorker).to receive(:perform_in) .with(lease_timeout, project.id, statistics.map(&:to_sym)) .and_call_original -- cgit v1.2.1 From 552d38861ad77bf6a64b1e61a91e36fcd26d057c Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Thu, 4 Apr 2019 15:38:37 +0000 Subject: Automatically set Prometheus step interval By computing the step interval passed to the query_range Prometheus API call we improve the performance on the Prometheus server and GitLab by reducing the amount of data points sent back and prevent Prometheus from sending errors when requesting longer intervals. --- spec/lib/gitlab/prometheus_client_spec.rb | 28 ++++++++++++++++++++++++++++ spec/support/helpers/prometheus_helpers.rb | 10 +++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index 4c3b8deefb9..2517ee71f24 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -230,4 +230,32 @@ describe Gitlab::PrometheusClient do let(:execute_query) { subject.query_range(prometheus_query) } end end + + describe '.compute_step' do + using RSpec::Parameterized::TableSyntax + + let(:now) { Time.now.utc } + + subject { described_class.compute_step(start, stop) } + + where(:time_interval_in_seconds, :step) do + 0 | 60 + 10.hours | 60 + 10.hours + 1 | 61 + # frontend options + 30.minutes | 60 + 3.hours | 60 + 8.hours | 60 + 1.day | 144 + 3.days | 432 + 1.week | 1008 + end + + with_them do + let(:start) { now - time_interval_in_seconds } + let(:stop) { now } + + it { is_expected.to eq(step) } + end + end end diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index ce1f9fce10d..08d1d7a6059 100644 --- a/spec/support/helpers/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb @@ -25,12 +25,16 @@ module PrometheusHelpers "https://prometheus.example.com/api/v1/query?#{query}" end - def prometheus_query_range_url(prometheus_query, start: 8.hours.ago, stop: Time.now.to_f) + def prometheus_query_range_url(prometheus_query, start: 8.hours.ago, stop: Time.now, step: nil) + start = start.to_f + stop = stop.to_f + step ||= Gitlab::PrometheusClient.compute_step(start, stop) + query = { query: prometheus_query, - start: start.to_f, + start: start, end: stop, - step: 1.minute.to_i + step: step }.to_query "https://prometheus.example.com/api/v1/query_range?#{query}" -- cgit v1.2.1 From 2571856fc52eecf0d39f517905e14e7a9d2267da Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Tue, 2 Apr 2019 15:27:34 -0400 Subject: Shortcut to create MR in web IDE Before the user had to choose between committing to a new branch, committing to a new branch AND creating an MR, or committing to the current branch regardless of whether or not it already has an MR. This commit separates the creation of an MR from whether or not they commit to an existing or new branch --- .../ide/stores/modules/commit/mutations_spec.js | 18 ++++- .../ide/components/commit_sidebar/actions_spec.js | 84 +++++++++++++++++++--- .../ide/stores/modules/commit/actions_spec.js | 20 +++++- .../ide/stores/modules/commit/getters_spec.js | 62 ++++++++++++---- 4 files changed, 155 insertions(+), 29 deletions(-) (limited to 'spec') diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js index 5de7a281d34..40d47aaad03 100644 --- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js +++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js @@ -18,7 +18,7 @@ describe('IDE commit module mutations', () => { describe('UPDATE_COMMIT_ACTION', () => { it('updates commitAction', () => { - mutations.UPDATE_COMMIT_ACTION(state, 'testing'); + mutations.UPDATE_COMMIT_ACTION(state, { commitAction: 'testing' }); expect(state.commitAction).toBe('testing'); }); @@ -39,4 +39,20 @@ describe('IDE commit module mutations', () => { expect(state.submitCommitLoading).toBeTruthy(); }); }); + + describe('TOGGLE_SHOULD_CREATE_MR', () => { + it('changes shouldCreateMR to true when initial state is false', () => { + state.shouldCreateMR = false; + mutations.TOGGLE_SHOULD_CREATE_MR(state); + + expect(state.shouldCreateMR).toBe(true); + }); + + it('changes shouldCreateMR to false when initial state is true', () => { + state.shouldCreateMR = true; + mutations.TOGGLE_SHOULD_CREATE_MR(state); + + expect(state.shouldCreateMR).toBe(false); + }); + }); }); diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js index 3a5d6c8a90b..23e6a055518 100644 --- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import store from '~/ide/stores'; +import consts from '~/ide/stores/modules/commit/constants'; import commitActions from '~/ide/components/commit_sidebar/actions.vue'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from 'spec/ide/helpers'; @@ -7,20 +8,33 @@ import { projectData } from 'spec/ide/mock_data'; describe('IDE commit sidebar actions', () => { let vm; - - beforeEach(done => { + const createComponent = ({ + hasMR = false, + commitAction = consts.COMMIT_TO_NEW_BRANCH, + mergeRequestsEnabled = true, + currentBranchId = 'master', + shouldCreateMR = false, + } = {}) => { const Component = Vue.extend(commitActions); vm = createComponentWithStore(Component, store); - vm.$store.state.currentBranchId = 'master'; + vm.$store.state.currentBranchId = currentBranchId; vm.$store.state.currentProjectId = 'abcproject'; + vm.$store.state.commit.commitAction = commitAction; Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData }); + vm.$store.state.projects.abcproject.merge_requests_enabled = mergeRequestsEnabled; + vm.$store.state.commit.shouldCreateMR = shouldCreateMR; - vm.$mount(); + if (hasMR) { + vm.$store.state.currentMergeRequestId = '1'; + vm.$store.state.projects[store.state.currentProjectId].mergeRequests[ + store.state.currentMergeRequestId + ] = { foo: 'bar' }; + } - Vue.nextTick(done); - }); + return vm.$mount(); + }; afterEach(() => { vm.$destroy(); @@ -28,16 +42,20 @@ describe('IDE commit sidebar actions', () => { resetStore(vm.$store); }); - it('renders 3 groups', () => { - expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(3); + it('renders 2 groups', () => { + createComponent(); + + expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2); }); it('renders current branch text', () => { + createComponent(); + expect(vm.$el.textContent).toContain('Commit to master branch'); }); it('hides merge request option when project merge requests are disabled', done => { - vm.$store.state.projects.abcproject.merge_requests_enabled = false; + createComponent({ mergeRequestsEnabled: false }); vm.$nextTick(() => { expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2); @@ -49,9 +67,53 @@ describe('IDE commit sidebar actions', () => { describe('commitToCurrentBranchText', () => { it('escapes current branch', () => { - vm.$store.state.currentBranchId = ''; + const injectedSrc = ''; + createComponent({ currentBranchId: injectedSrc }); + + expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc); + }); + }); + + describe('create new MR checkbox', () => { + it('disables `createMR` button when an MR already exists and committing to current branch', () => { + createComponent({ hasMR: true, commitAction: consts.COMMIT_TO_CURRENT_BRANCH }); + + expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(true); + }); + + it('does not disable checkbox if MR does not exist', () => { + createComponent({ hasMR: false }); + + expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false); + }); + + it('does not disable checkbox when creating a new branch', () => { + createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH }); + + expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false); + }); + + it('toggles off new MR when switching back to commit to current branch and MR exists', () => { + createComponent({ + commitAction: consts.COMMIT_TO_NEW_BRANCH, + shouldCreateMR: true, + }); + const currentBranchRadio = vm.$el.querySelector( + `input[value="${consts.COMMIT_TO_CURRENT_BRANCH}"`, + ); + currentBranchRadio.click(); + + vm.$nextTick(() => { + expect(vm.$store.state.commit.shouldCreateMR).toBe(false); + }); + }); + + it('toggles `shouldCreateMR` when clicking checkbox', () => { + createComponent(); + const el = vm.$el.querySelector('input[type="checkbox"]'); + el.dispatchEvent(new Event('change')); - expect(vm.commitToCurrentBranchText).not.toContain(''); + expect(vm.$store.state.commit.shouldCreateMR).toBe(true); }); }); }); diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index 34d97347438..abc97f3072c 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -3,7 +3,7 @@ import store from '~/ide/stores'; import service from '~/ide/services'; import router from '~/ide/ide_router'; import eventHub from '~/ide/eventhub'; -import * as consts from '~/ide/stores/modules/commit/constants'; +import consts from '~/ide/stores/modules/commit/constants'; import { resetStore, file } from 'spec/ide/helpers'; describe('IDE commit module actions', () => { @@ -389,7 +389,8 @@ describe('IDE commit module actions', () => { it('redirects to new merge request page', done => { spyOn(eventHub, '$on'); - store.state.commit.commitAction = '3'; + store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; + store.state.commit.shouldCreateMR = true; store .dispatch('commit/commitChanges') @@ -405,6 +406,21 @@ describe('IDE commit module actions', () => { .catch(done.fail); }); + it('does not redirect to new merge request page when shouldCreateMR is not checked', done => { + spyOn(eventHub, '$on'); + + store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; + store.state.commit.shouldCreateMR = false; + + store + .dispatch('commit/commitChanges') + .then(() => { + expect(visitUrl).not.toHaveBeenCalled(); + done(); + }) + .catch(done.fail); + }); + it('resets changed files before redirecting', done => { spyOn(eventHub, '$on'); diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js index 702e78ef773..e00fd7199d7 100644 --- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js @@ -1,5 +1,5 @@ import commitState from '~/ide/stores/modules/commit/state'; -import * as consts from '~/ide/stores/modules/commit/constants'; +import consts from '~/ide/stores/modules/commit/constants'; import * as getters from '~/ide/stores/modules/commit/getters'; describe('IDE commit module getters', () => { @@ -46,7 +46,7 @@ describe('IDE commit module getters', () => { currentBranchId: 'master', }; const localGetters = { - placeholderBranchName: 'newBranchName', + placeholderBranchName: 'placeholder-branch-name', }; beforeEach(() => { @@ -59,25 +59,28 @@ describe('IDE commit module getters', () => { expect(getters.branchName(state, null, rootState)).toBe('master'); }); - ['COMMIT_TO_NEW_BRANCH', 'COMMIT_TO_NEW_BRANCH_MR'].forEach(type => { - describe(type, () => { - beforeEach(() => { - Object.assign(state, { - commitAction: consts[type], - }); + describe('COMMIT_TO_NEW_BRANCH', () => { + beforeEach(() => { + Object.assign(state, { + commitAction: consts.COMMIT_TO_NEW_BRANCH, }); + }); - it('uses newBranchName when not empty', () => { - expect(getters.branchName(state, localGetters, rootState)).toBe('state-newBranchName'); + it('uses newBranchName when not empty', () => { + const newBranchName = 'nonempty-branch-name'; + Object.assign(state, { + newBranchName, }); - it('uses placeholderBranchName when state newBranchName is empty', () => { - Object.assign(state, { - newBranchName: '', - }); + expect(getters.branchName(state, localGetters, rootState)).toBe(newBranchName); + }); - expect(getters.branchName(state, localGetters, rootState)).toBe('newBranchName'); + it('uses placeholderBranchName when state newBranchName is empty', () => { + Object.assign(state, { + newBranchName: '', }); + + expect(getters.branchName(state, localGetters, rootState)).toBe('placeholder-branch-name'); }); }); }); @@ -141,4 +144,33 @@ describe('IDE commit module getters', () => { }); }); }); + + describe('shouldDisableNewMrOption', () => { + it('returns false if commitAction `COMMIT_TO_NEW_BRANCH`', () => { + state.commitAction = consts.COMMIT_TO_NEW_BRANCH; + const rootState = { + currentMergeRequest: { foo: 'bar' }, + }; + + expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy(); + }); + + it('returns false if there is no current merge request', () => { + state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + const rootState = { + currentMergeRequest: null, + }; + + expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy(); + }); + + it('returns true an MR exists and commit action is `COMMIT_TO_CURRENT_BRANCH`', () => { + state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + const rootState = { + currentMergeRequest: { foo: 'bar' }, + }; + + expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeTruthy(); + }); + }); }); -- cgit v1.2.1 From 866b58a54e95415ad74b8f004db40abb4343c753 Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 4 Apr 2019 16:28:56 +0000 Subject: Allow to sort wiki pages by date and title - Add controls for sorting by title and date - Execute Gitaly call which now accepts sorting params for wikis --- .../projects/wiki/user_views_wiki_pages_spec.rb | 89 ++++++++++++++++++++++ spec/helpers/wiki_helper_spec.rb | 52 +++++++++++++ spec/models/wiki_page_spec.rb | 45 +++++++---- 3 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 spec/features/projects/wiki/user_views_wiki_pages_spec.rb (limited to 'spec') diff --git a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb new file mode 100644 index 00000000000..5c16d7783f0 --- /dev/null +++ b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'User views wiki pages' do + include WikiHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } + + let!(:wiki_page1) do + create(:wiki_page, wiki: project.wiki, attrs: { title: '3 home', content: '3' }) + end + let!(:wiki_page2) do + create(:wiki_page, wiki: project.wiki, attrs: { title: '1 home', content: '1' }) + end + let!(:wiki_page3) do + create(:wiki_page, wiki: project.wiki, attrs: { title: '2 home', content: '2' }) + end + + let(:pages) do + page.find('.wiki-pages-list').all('li').map { |li| li.find('a') } + end + + before do + project.add_maintainer(user) + sign_in(user) + visit(project_wikis_pages_path(project)) + end + + context 'ordered by title' do + let(:pages_ordered_by_title) { [wiki_page2, wiki_page3, wiki_page1] } + + context 'asc' do + it 'pages are displayed in direct order' do + pages.each.with_index do |page_title, index| + expect(page_title.text).to eq(pages_ordered_by_title[index].title) + end + end + end + + context 'desc' do + before do + page.within('.wiki-sort-dropdown') do + page.find('.qa-reverse-sort').click + end + end + + it 'pages are displayed in reversed order' do + pages.reverse_each.with_index do |page_title, index| + expect(page_title.text).to eq(pages_ordered_by_title[index].title) + end + end + end + end + + context 'ordered by created_at' do + let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] } + + before do + page.within('.wiki-sort-dropdown') do + click_button('Title') + click_link('Created date') + end + end + + context 'asc' do + it 'pages are displayed in direct order' do + pages.each.with_index do |page_title, index| + expect(page_title.text).to eq(pages_ordered_by_created_at[index].title) + end + end + end + + context 'desc' do + before do + page.within('.wiki-sort-dropdown') do + page.find('.qa-reverse-sort').click + end + end + + it 'pages are displayed in reversed order' do + pages.reverse_each.with_index do |page_title, index| + expect(page_title.text).to eq(pages_ordered_by_created_at[index].title) + end + end + end + end +end diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb index 92c6f27a867..8eab40aeaf3 100644 --- a/spec/helpers/wiki_helper_spec.rb +++ b/spec/helpers/wiki_helper_spec.rb @@ -18,4 +18,56 @@ describe WikiHelper do end end end + + describe '#wiki_sort_controls' do + let(:project) { create(:project) } + let(:wiki_link) { helper.wiki_sort_controls(project, sort, direction) } + let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort" } + + def expected_link(sort, direction, icon_class) + path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}" + + helper.link_to(path, type: 'button', class: classes, title: 'Sort direction') do + helper.sprite_icon("sort-#{icon_class}", size: 16) + end + end + + context 'initial call' do + let(:sort) { nil } + let(:direction) { nil } + + it 'renders with default values' do + expect(wiki_link).to eq(expected_link('title', 'desc', 'lowest')) + end + end + + context 'sort by title' do + let(:sort) { 'title' } + let(:direction) { 'asc' } + + it 'renders a link with opposite direction' do + expect(wiki_link).to eq(expected_link('title', 'desc', 'lowest')) + end + end + + context 'sort by created_at' do + let(:sort) { 'created_at' } + let(:direction) { 'desc' } + + it 'renders a link with opposite direction' do + expect(wiki_link).to eq(expected_link('created_at', 'asc', 'highest')) + end + end + end + + describe '#wiki_sort_title' do + it 'returns a title corresponding to a key' do + expect(helper.wiki_sort_title('created_at')).to eq('Created date') + expect(helper.wiki_sort_title('title')).to eq('Title') + end + + it 'defaults to Title if a key is unknown' do + expect(helper.wiki_sort_title('unknown')).to eq('Title') + end + end end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index e68da67818a..cacdb0e0595 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -20,12 +20,16 @@ describe WikiPage do context 'when there are pages' do before do create_page('dir_1/dir_1_1/page_3', 'content') + create_page('page_1', 'content') create_page('dir_1/page_2', 'content') create_page('dir_2/page_5', 'content') + create_page('page_6', 'content') create_page('dir_2/page_4', 'content') - create_page('page_1', 'content') end + let(:page_1) { wiki.find_page('page_1') } + let(:page_6) { wiki.find_page('page_6') } + let(:dir_1) do WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')]) end @@ -38,25 +42,38 @@ describe WikiPage do WikiDirectory.new('dir_2', pages) end - it 'returns an array with pages and directories' do - expected_grouped_entries = [page_1, dir_1, dir_1_1, dir_2] + context 'sort by title' do + let(:grouped_entries) { described_class.group_by_directory(wiki.pages) } + let(:expected_grouped_entries) { [dir_1_1, dir_1, dir_2, page_1, page_6] } - grouped_entries = described_class.group_by_directory(wiki.pages) + it 'returns an array with pages and directories' do + grouped_entries.each_with_index do |page_or_dir, i| + expected_page_or_dir = expected_grouped_entries[i] + expected_slugs = get_slugs(expected_page_or_dir) + slugs = get_slugs(page_or_dir) - grouped_entries.each_with_index do |page_or_dir, i| - expected_page_or_dir = expected_grouped_entries[i] - expected_slugs = get_slugs(expected_page_or_dir) - slugs = get_slugs(page_or_dir) + expect(slugs).to match_array(expected_slugs) + end + end + end + + context 'sort by created_at' do + let(:grouped_entries) { described_class.group_by_directory(wiki.pages(sort: 'created_at')) } + let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, dir_2, page_6] } - expect(slugs).to match_array(expected_slugs) + it 'returns an array with pages and directories' do + grouped_entries.each_with_index do |page_or_dir, i| + expected_page_or_dir = expected_grouped_entries[i] + expected_slugs = get_slugs(expected_page_or_dir) + slugs = get_slugs(page_or_dir) + + expect(slugs).to match_array(expected_slugs) + end end end - it 'returns an array sorted by alphabetical position' do - # Directories and pages within directories are sorted alphabetically. - # Pages at root come before everything. - expected_order = ['page_1', 'dir_1/page_2', 'dir_1/dir_1_1/page_3', - 'dir_2/page_4', 'dir_2/page_5'] + it 'returns an array with retained order with directories at the top' do + expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6'] grouped_entries = described_class.group_by_directory(wiki.pages) -- cgit v1.2.1 From 5caced3650bf51bd1035347b9823367dd9095e02 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Thu, 4 Apr 2019 17:27:29 +0000 Subject: Allow reactive caching to be used in services Add support for defining a reactive_cache_worker_finder function that will be used by the reactive_caching_worker to generate/initialize the calling object. This allows reactive caching to work with Services where the object cannot be obtained from DB but a new object can be initialized. --- spec/models/concerns/reactive_caching_spec.rb | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'spec') diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 32e13d5abed..53df9e0bc05 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -16,6 +16,10 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do attr_reader :id + def self.primary_key + :id + end + def initialize(id, &blk) @id = id @calculator = blk @@ -106,6 +110,46 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do end end + describe '.reactive_cache_worker_finder' do + context 'with default reactive_cache_worker_finder' do + let(:args) { %w(other args) } + + before do + allow(instance.class).to receive(:find_by).with(id: instance.id) + .and_return(instance) + end + + it 'calls the activerecord find_by method' do + result = instance.class.reactive_cache_worker_finder.call(instance.id, *args) + + expect(result).to eq(instance) + expect(instance.class).to have_received(:find_by).with(id: instance.id) + end + end + + context 'with custom reactive_cache_worker_finder' do + let(:args) { %w(arg1 arg2) } + let(:instance) { CustomFinderCacheTest.new(666, &calculation) } + + class CustomFinderCacheTest < CacheTest + self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } + + def self.from_cache(*args); end + end + + before do + allow(instance.class).to receive(:from_cache).with(*args).and_return(instance) + end + + it 'overrides the default reactive_cache_worker_finder' do + result = instance.class.reactive_cache_worker_finder.call(instance.id, *args) + + expect(result).to eq(instance) + expect(instance.class).to have_received(:from_cache).with(*args) + end + end + end + describe '#clear_reactive_cache!' do before do stub_reactive_cache(instance, 4) -- cgit v1.2.1 From f88607923afd5a2adfb7a28df297528cbb27885b Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 4 Apr 2019 15:34:55 +0200 Subject: Fix scoped_labels feature check Check this feature per project/group instead of globally. Also do not show tooltip if it's disabled. --- .../api/schemas/entities/merge_request_sidebar.json | 3 +-- spec/helpers/labels_helper_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json index 7e9e048a9fd..214b67a9a0f 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json +++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json @@ -51,6 +51,5 @@ "toggle_subscription_path": { "type": "string" }, "move_issue_path": { "type": "string" }, "projects_autocomplete_path": { "type": "string" } - }, - "additionalProperties": false + } } diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 012678db9c2..a049b5a6133 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -249,4 +249,24 @@ describe LabelsHelper do .to match_array([label2, label4, label1, label3]) end end + + describe 'label_from_hash' do + it 'builds a group label with whitelisted attributes' do + label = label_from_hash({ title: 'foo', color: 'bar', id: 1, group_id: 1 }) + + expect(label).to be_a(GroupLabel) + expect(label.id).to be_nil + expect(label.title).to eq('foo') + expect(label.color).to eq('bar') + end + + it 'builds a project label with whitelisted attributes' do + label = label_from_hash({ title: 'foo', color: 'bar', id: 1, project_id: 1 }) + + expect(label).to be_a(ProjectLabel) + expect(label.id).to be_nil + expect(label.title).to eq('foo') + expect(label.color).to eq('bar') + end + end end -- cgit v1.2.1 From beeeede2eed9bd78a5948178e270935fa9d92c51 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 4 Apr 2019 18:52:29 +0100 Subject: Fix a KeyError in GitlabSchema --- spec/graphql/gitlab_schema_spec.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index a535d9cdc7e..74e93b2c4df 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -34,12 +34,22 @@ describe GitlabSchema do end context 'for different types of users' do - it 'returns DEFAULT_MAX_COMPLEXITY for no user' do - expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) + it 'returns DEFAULT_MAX_COMPLEXITY for no context' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) described_class.execute('query') end + it 'returns DEFAULT_MAX_COMPLEXITY for no user' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) + + described_class.execute('query', context: {}) + end + it 'returns AUTHENTICATED_COMPLEXITY for a logged in user' do user = build :user -- cgit v1.2.1 From 8a134f4c6505c4f8f3c89e0ae4d4ea2293765be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Thu, 4 Apr 2019 18:32:02 +0000 Subject: Renamed terminal_specification to channel_specification We're moving from using terminology related to terminals when we refer to Websockets connections in Workhorse. It's more appropiate a concept like channel. --- .../controllers/projects/environments_controller_spec.rb | 2 +- spec/controllers/projects/jobs_controller_spec.rb | 2 +- spec/lib/gitlab/workhorse_spec.rb | 10 +++++----- spec/models/ci/build_runner_session_spec.rb | 16 ++++++++++++---- 4 files changed, 19 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 2dca2c3976f..43639875265 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -283,7 +283,7 @@ describe Projects::EnvironmentsController do .and_return([:fake_terminal]) expect(Gitlab::Workhorse) - .to receive(:terminal_websocket) + .to receive(:channel_websocket) .with(:fake_terminal) .and_return(workhorse: :response) diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index d8a331b3cf0..23e4e9806c2 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -989,7 +989,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do context 'and valid id' do it 'returns the terminal for the job' do expect(Gitlab::Workhorse) - .to receive(:terminal_websocket) + .to receive(:channel_websocket) .and_return(workhorse: :response) get_terminal_websocket(id: job.id) diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index fed7834e2a9..f8ce399287a 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -94,7 +94,7 @@ describe Gitlab::Workhorse do end end - describe '.terminal_websocket' do + describe '.channel_websocket' do def terminal(ca_pem: nil) out = { subprotocols: ['foo'], @@ -108,25 +108,25 @@ describe Gitlab::Workhorse do def workhorse(ca_pem: nil) out = { - 'Terminal' => { + 'Channel' => { 'Subprotocols' => ['foo'], 'Url' => 'wss://example.com/terminal.ws', 'Header' => { 'Authorization' => ['Token x'] }, 'MaxSessionTime' => 600 } } - out['Terminal']['CAPem'] = ca_pem if ca_pem + out['Channel']['CAPem'] = ca_pem if ca_pem out end context 'without ca_pem' do - subject { described_class.terminal_websocket(terminal) } + subject { described_class.channel_websocket(terminal) } it { is_expected.to eq(workhorse) } end context 'with ca_pem' do - subject { described_class.terminal_websocket(terminal(ca_pem: "foo")) } + subject { described_class.channel_websocket(terminal(ca_pem: "foo")) } it { is_expected.to eq(workhorse(ca_pem: "foo")) } end diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb index a52c10019e6..e51fd009f50 100644 --- a/spec/models/ci/build_runner_session_spec.rb +++ b/spec/models/ci/build_runner_session_spec.rb @@ -13,25 +13,33 @@ describe Ci::BuildRunnerSession, model: true do it { is_expected.to validate_presence_of(:url).with_message('must be a valid URL') } describe '#terminal_specification' do - let(:terminal_specification) { subject.terminal_specification } + let(:specification) { subject.terminal_specification } + + it 'returns terminal.gitlab.com protocol' do + expect(specification[:subprotocols]).to eq ['terminal.gitlab.com'] + end + + it 'returns a wss url' do + expect(specification[:url]).to start_with('wss://') + end it 'returns empty hash if no url' do subject.url = '' - expect(terminal_specification).to be_empty + expect(specification).to be_empty end context 'when url is present' do it 'returns ca_pem nil if empty certificate' do subject.certificate = '' - expect(terminal_specification[:ca_pem]).to be_nil + expect(specification[:ca_pem]).to be_nil end it 'adds Authorization header if authorization is present' do subject.authorization = 'whatever' - expect(terminal_specification[:headers]).to include(Authorization: ['whatever']) + expect(specification[:headers]).to include(Authorization: ['whatever']) end end end -- cgit v1.2.1 From baab836b9bb5cab5fada8e0ce155b80b805c07b7 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Tue, 2 Apr 2019 11:24:36 +0530 Subject: Add a proxy method to PrometheusClient - Also refactor the get and json_api_get methods so that the get method can be reused by the new proxy method. - The new proxy method makes no changes to the request to the prometheus server and response from the prometheus server. This allows it to be used as a proxy to the Prometheus server, hence the name. --- spec/lib/gitlab/prometheus_client_spec.rb | 89 ++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index 2517ee71f24..d3a28b53da9 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -60,15 +60,13 @@ describe Gitlab::PrometheusClient do end describe 'failure to reach a provided prometheus url' do - let(:prometheus_url) {"https://prometheus.invalid.example.com"} + let(:prometheus_url) {"https://prometheus.invalid.example.com/api/v1/query?query=1"} - subject { described_class.new(RestClient::Resource.new(prometheus_url)) } - - context 'exceptions are raised' do + shared_examples 'exceptions are raised' do it 'raises a Gitlab::PrometheusClient::Error error when a SocketError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "Can't connect to #{prometheus_url}") expect(req_stub).to have_been_requested end @@ -76,7 +74,7 @@ describe Gitlab::PrometheusClient do it 'raises a Gitlab::PrometheusClient::Error error when a SSLError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "#{prometheus_url} contains invalid SSL data") expect(req_stub).to have_been_requested end @@ -84,11 +82,23 @@ describe Gitlab::PrometheusClient do it 'raises a Gitlab::PrometheusClient::Error error when a RestClient::Exception is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error") expect(req_stub).to have_been_requested end end + + context 'ping' do + subject { described_class.new(RestClient::Resource.new(prometheus_url)).ping } + + it_behaves_like 'exceptions are raised' + end + + context 'proxy' do + subject { described_class.new(RestClient::Resource.new(prometheus_url)).proxy('query', { query: '1' }) } + + it_behaves_like 'exceptions are raised' + end end describe '#query' do @@ -258,4 +268,69 @@ describe Gitlab::PrometheusClient do it { is_expected.to eq(step) } end end + + describe 'proxy' do + context 'query' do + let(:prometheus_query) { prometheus_cpu_query('env-slug') } + let(:query_url) { prometheus_query_url(prometheus_query) } + + around do |example| + Timecop.freeze { example.run } + end + + it 'returns full response from the API call' do + req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector')) + + response = subject.proxy('query', { query: prometheus_query }) + json_response = JSON.parse(response.body) + + expect(response.code).to eq(200) + expect(json_response).to eq({ + 'status' => 'success', + 'data' => { + 'resultType' => 'vector', + 'result' => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] + } + }) + expect(req_stub).to have_been_requested + end + end + + context 'query_range' do + let(:prometheus_query) { prometheus_memory_query('env-slug') } + let(:query_url) { prometheus_query_range_url(prometheus_query, start: 2.hours.ago) } + + around do |example| + Timecop.freeze { example.run } + end + + it 'returns full response' do + req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector')) + + response = subject.proxy('query_range', { + query: prometheus_query, + start: 2.hours.ago.to_f, + end: Time.now.to_f, + step: 60 + }) + json_response = JSON.parse(response.body) + + expect(response.code).to eq(200) + expect(json_response).to eq({ + "status" => "success", + "data" => { + "resultType" => "vector", + "result" => [{ + "metric" => {}, + "values" => [ + [1488758662.506, "0.00002996364761904785"], + [1488758722.506, "0.00003090239047619091"] + ] + }] + } + }) + expect(req_stub).to have_been_requested + end + end + end end -- cgit v1.2.1 From 399173d6fad09b8a6334d38c3b8412617fe1dd9a Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Tue, 2 Apr 2019 11:35:29 +0530 Subject: Add a Prometheus::ProxyService - The service uses the PrometheusClient.proxy method to call the Prometheus API with the given parameters, and returns the body and http status code of the API response to the caller of the service. - The service uses reactive caching in order to prevent Puma/Unicorn threads from being blocked until the Prometheus API responds. --- spec/services/prometheus/proxy_service_spec.rb | 204 +++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 spec/services/prometheus/proxy_service_spec.rb (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb new file mode 100644 index 00000000000..53e852aa79c --- /dev/null +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Prometheus::ProxyService do + include ReactiveCachingHelpers + + set(:project) { create(:project) } + set(:environment) { create(:environment, project: project) } + + describe '#initialize' do + let(:params) { ActionController::Parameters.new({ query: '1' }).permit! } + + it 'initializes attributes' do + result = described_class.new(environment, 'GET', 'query', { query: '1' }) + + expect(result.prometheus_owner).to eq(environment) + expect(result.method).to eq('GET') + expect(result.path).to eq('query') + expect(result.params).to eq({ query: '1' }) + end + + it 'converts ActionController::Parameters into hash' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.params).to be_an_instance_of(Hash) + end + end + + describe '#execute' do + let(:prometheus_adapter) { instance_double(PrometheusService) } + + subject { described_class.new(environment, 'GET', 'query', { query: '1' }) } + + context 'When prometheus_adapter is nil' do + before do + allow(environment).to receive(:prometheus_adapter).and_return(nil) + end + + it 'should return error' do + expect(subject.execute).to eq({ + status: :error, + message: 'No prometheus server found', + http_status: :service_unavailable + }) + end + end + + context 'When prometheus_adapter cannot query' do + before do + allow(environment).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(false) + end + + it 'should return error' do + expect(subject.execute).to eq({ + status: :error, + message: 'No prometheus server found', + http_status: :service_unavailable + }) + end + end + + context 'Cannot proxy' do + subject { described_class.new(environment, 'POST', 'query', { query: '1' }) } + + it 'returns error' do + expect(subject.execute).to eq({ + message: 'Proxy support for this API is not available currently', + status: :error + }) + end + end + + context 'When cached', :use_clean_rails_memory_store_caching do + let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } + let(:opts) { [environment.class.name, environment.id, 'GET', 'query', { query: '1' }] } + + before do + stub_reactive_cache(subject, return_value, opts) + + allow(environment).to receive(:prometheus_adapter) + .and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(true) + end + + it 'returns cached value' do + result = subject.execute + + expect(result[:http_status]).to eq(return_value[:http_status]) + expect(result[:body]).to eq(return_value[:body]) + end + end + + context 'When not cached' do + let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } + let(:opts) { [environment.class.name, environment.id, 'GET', 'query', { query: '1' }] } + + before do + allow(environment).to receive(:prometheus_adapter) + .and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(true) + end + + it 'returns nil' do + expect(ReactiveCachingWorker) + .to receive(:perform_async) + .with(subject.class, subject.id, *opts) + + result = subject.execute + + expect(result).to eq(nil) + end + end + + context 'Call prometheus api' do + let(:prometheus_client) { instance_double(Gitlab::PrometheusClient) } + + before do + synchronous_reactive_cache(subject) + + allow(environment).to receive(:prometheus_adapter) + .and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(true) + allow(prometheus_adapter).to receive(:prometheus_client_wrapper) + .and_return(prometheus_client) + end + + context 'Connection to prometheus server succeeds' do + let(:rest_client_response) { instance_double(RestClient::Response) } + + before do + allow(prometheus_client).to receive(:proxy).and_return(rest_client_response) + + allow(rest_client_response).to receive(:code) + .and_return(prometheus_http_status_code) + allow(rest_client_response).to receive(:body).and_return(response_body) + end + + shared_examples 'return prometheus http status code and body' do + it do + expect(subject.execute).to eq({ + http_status: prometheus_http_status_code, + body: response_body, + status: :success + }) + end + end + + context 'prometheus returns success' do + let(:prometheus_http_status_code) { 200 } + + let(:response_body) do + '{"status":"success","data":{"resultType":"scalar","result":[1553864609.117,"1"]}}' + end + + before do + end + + it_behaves_like 'return prometheus http status code and body' + end + + context 'prometheus returns error' do + let(:prometheus_http_status_code) { 400 } + + let(:response_body) do + '{"status":"error","errorType":"bad_data","error":"parse error at char 1: no expression found in input"}' + end + + it_behaves_like 'return prometheus http status code and body' + end + end + + context 'connection to prometheus server fails' do + context 'prometheus client raises Gitlab::PrometheusClient::Error' do + before do + allow(prometheus_client).to receive(:proxy) + .and_raise(Gitlab::PrometheusClient::Error, 'Network connection error') + end + + it 'returns error' do + expect(subject.execute).to eq({ + status: :error, + message: 'Network connection error', + http_status: :service_unavailable + }) + end + end + end + end + end + + describe '.from_cache' do + it 'initializes an instance of ProxyService class' do + result = described_class.from_cache(environment.class.name, environment.id, 'GET', 'query', { query: '1' }) + + expect(result).to be_an_instance_of(described_class) + expect(result.prometheus_owner).to eq(environment) + expect(result.method).to eq('GET') + expect(result.path).to eq('query') + expect(result.params).to eq({ query: '1' }) + end + end +end -- cgit v1.2.1 From 6938977a0a2e2cf7c1916f92fb9d8e2119b73e3c Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Tue, 2 Apr 2019 20:37:49 +0530 Subject: Improvements after review - Add a method for returning a service_unavailable response. - Keep only one spec to test that the prometheus response is returned as-is. --- spec/services/prometheus/proxy_service_spec.rb | 42 +++++++------------------- 1 file changed, 11 insertions(+), 31 deletions(-) (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index 53e852aa79c..391a108c862 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -128,6 +128,11 @@ describe Prometheus::ProxyService do context 'Connection to prometheus server succeeds' do let(:rest_client_response) { instance_double(RestClient::Response) } + let(:prometheus_http_status_code) { 400 } + + let(:response_body) do + '{"status":"error","errorType":"bad_data","error":"parse error at char 1: no expression found in input"}' + end before do allow(prometheus_client).to receive(:proxy).and_return(rest_client_response) @@ -137,37 +142,12 @@ describe Prometheus::ProxyService do allow(rest_client_response).to receive(:body).and_return(response_body) end - shared_examples 'return prometheus http status code and body' do - it do - expect(subject.execute).to eq({ - http_status: prometheus_http_status_code, - body: response_body, - status: :success - }) - end - end - - context 'prometheus returns success' do - let(:prometheus_http_status_code) { 200 } - - let(:response_body) do - '{"status":"success","data":{"resultType":"scalar","result":[1553864609.117,"1"]}}' - end - - before do - end - - it_behaves_like 'return prometheus http status code and body' - end - - context 'prometheus returns error' do - let(:prometheus_http_status_code) { 400 } - - let(:response_body) do - '{"status":"error","errorType":"bad_data","error":"parse error at char 1: no expression found in input"}' - end - - it_behaves_like 'return prometheus http status code and body' + it 'returns the http status code and body from prometheus' do + expect(subject.execute).to eq( + http_status: prometheus_http_status_code, + body: response_body, + status: :success + ) end end -- cgit v1.2.1 From f71d243a6d7fc0675b0b3fc9e35f9523d8dcb145 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Tue, 2 Apr 2019 20:43:36 +0530 Subject: Remove all superfluous braces --- spec/services/prometheus/proxy_service_spec.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index 391a108c862..1291c7a0217 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -9,7 +9,7 @@ describe Prometheus::ProxyService do set(:environment) { create(:environment, project: project) } describe '#initialize' do - let(:params) { ActionController::Parameters.new({ query: '1' }).permit! } + let(:params) { ActionController::Parameters.new(query: '1').permit! } it 'initializes attributes' do result = described_class.new(environment, 'GET', 'query', { query: '1' }) @@ -17,7 +17,7 @@ describe Prometheus::ProxyService do expect(result.prometheus_owner).to eq(environment) expect(result.method).to eq('GET') expect(result.path).to eq('query') - expect(result.params).to eq({ query: '1' }) + expect(result.params).to eq(query: '1') end it 'converts ActionController::Parameters into hash' do @@ -38,11 +38,11 @@ describe Prometheus::ProxyService do end it 'should return error' do - expect(subject.execute).to eq({ + expect(subject.execute).to eq( status: :error, message: 'No prometheus server found', http_status: :service_unavailable - }) + ) end end @@ -53,11 +53,11 @@ describe Prometheus::ProxyService do end it 'should return error' do - expect(subject.execute).to eq({ + expect(subject.execute).to eq( status: :error, message: 'No prometheus server found', http_status: :service_unavailable - }) + ) end end @@ -65,10 +65,10 @@ describe Prometheus::ProxyService do subject { described_class.new(environment, 'POST', 'query', { query: '1' }) } it 'returns error' do - expect(subject.execute).to eq({ + expect(subject.execute).to eq( message: 'Proxy support for this API is not available currently', status: :error - }) + ) end end @@ -159,11 +159,11 @@ describe Prometheus::ProxyService do end it 'returns error' do - expect(subject.execute).to eq({ + expect(subject.execute).to eq( status: :error, message: 'Network connection error', http_status: :service_unavailable - }) + ) end end end @@ -178,7 +178,7 @@ describe Prometheus::ProxyService do expect(result.prometheus_owner).to eq(environment) expect(result.method).to eq('GET') expect(result.path).to eq('query') - expect(result.params).to eq({ query: '1' }) + expect(result.params).to eq(query: '1') end end end -- cgit v1.2.1 From 9817124f14c378a018530a604fd63fc30584b1ff Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Tue, 2 Apr 2019 20:44:06 +0530 Subject: Start context names with lowercase letter --- spec/services/prometheus/proxy_service_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index 1291c7a0217..1b98c68f99d 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -32,7 +32,7 @@ describe Prometheus::ProxyService do subject { described_class.new(environment, 'GET', 'query', { query: '1' }) } - context 'When prometheus_adapter is nil' do + context 'when prometheus_adapter is nil' do before do allow(environment).to receive(:prometheus_adapter).and_return(nil) end @@ -46,7 +46,7 @@ describe Prometheus::ProxyService do end end - context 'When prometheus_adapter cannot query' do + context 'when prometheus_adapter cannot query' do before do allow(environment).to receive(:prometheus_adapter).and_return(prometheus_adapter) allow(prometheus_adapter).to receive(:can_query?).and_return(false) @@ -61,7 +61,7 @@ describe Prometheus::ProxyService do end end - context 'Cannot proxy' do + context 'cannot proxy' do subject { described_class.new(environment, 'POST', 'query', { query: '1' }) } it 'returns error' do @@ -72,7 +72,7 @@ describe Prometheus::ProxyService do end end - context 'When cached', :use_clean_rails_memory_store_caching do + context 'when cached', :use_clean_rails_memory_store_caching do let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } let(:opts) { [environment.class.name, environment.id, 'GET', 'query', { query: '1' }] } @@ -92,7 +92,7 @@ describe Prometheus::ProxyService do end end - context 'When not cached' do + context 'when not cached' do let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } let(:opts) { [environment.class.name, environment.id, 'GET', 'query', { query: '1' }] } @@ -113,7 +113,7 @@ describe Prometheus::ProxyService do end end - context 'Call prometheus api' do + context 'call prometheus api' do let(:prometheus_client) { instance_double(Gitlab::PrometheusClient) } before do @@ -126,7 +126,7 @@ describe Prometheus::ProxyService do .and_return(prometheus_client) end - context 'Connection to prometheus server succeeds' do + context 'connection to prometheus server succeeds' do let(:rest_client_response) { instance_double(RestClient::Response) } let(:prometheus_http_status_code) { 400 } -- cgit v1.2.1 From 359d00cd5ded5ef7d3cf33b34c1e4869f1e5c9c5 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Tue, 2 Apr 2019 23:44:50 +0530 Subject: Add spec for RestClient raising exception --- spec/lib/gitlab/prometheus_client_spec.rb | 84 ++++++++++++++----------------- 1 file changed, 37 insertions(+), 47 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index d3a28b53da9..f15ae83a02c 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -270,7 +270,7 @@ describe Gitlab::PrometheusClient do end describe 'proxy' do - context 'query' do + context 'get API' do let(:prometheus_query) { prometheus_cpu_query('env-slug') } let(:query_url) { prometheus_query_url(prometheus_query) } @@ -278,58 +278,48 @@ describe Gitlab::PrometheusClient do Timecop.freeze { example.run } end - it 'returns full response from the API call' do - req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector')) - - response = subject.proxy('query', { query: prometheus_query }) - json_response = JSON.parse(response.body) - - expect(response.code).to eq(200) - expect(json_response).to eq({ - 'status' => 'success', - 'data' => { - 'resultType' => 'vector', - 'result' => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] - } - }) - expect(req_stub).to have_been_requested + context 'when response status code is 200' do + it 'returns response object' do + req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector')) + + response = subject.proxy('query', { query: prometheus_query }) + json_response = JSON.parse(response.body) + + expect(response.code).to eq(200) + expect(json_response).to eq({ + 'status' => 'success', + 'data' => { + 'resultType' => 'vector', + 'result' => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] + } + }) + expect(req_stub).to have_been_requested + end end - end - context 'query_range' do - let(:prometheus_query) { prometheus_memory_query('env-slug') } - let(:query_url) { prometheus_query_range_url(prometheus_query, start: 2.hours.ago) } + context 'when response status code is not 200' do + it 'returns response object' do + req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'error' }) - around do |example| - Timecop.freeze { example.run } + response = subject.proxy('query', { query: prometheus_query }) + json_response = JSON.parse(response.body) + + expect(req_stub).to have_been_requested + expect(response.code).to eq(400) + expect(json_response).to eq('error' => 'error') + end end - it 'returns full response' do - req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector')) + context 'when RestClient::Exception is raised' do + before do + stub_prometheus_request_with_exception(query_url, RestClient::Exception) + end - response = subject.proxy('query_range', { - query: prometheus_query, - start: 2.hours.ago.to_f, - end: Time.now.to_f, - step: 60 - }) - json_response = JSON.parse(response.body) - - expect(response.code).to eq(200) - expect(json_response).to eq({ - "status" => "success", - "data" => { - "resultType" => "vector", - "result" => [{ - "metric" => {}, - "values" => [ - [1488758662.506, "0.00002996364761904785"], - [1488758722.506, "0.00003090239047619091"] - ] - }] - } - }) - expect(req_stub).to have_been_requested + it 'raises PrometheusClient::Error' do + expect { subject.proxy('query', { query: prometheus_query }) }.to( + raise_error(Gitlab::PrometheusClient::Error, 'Network connection error') + ) + end end end end -- cgit v1.2.1 From aa27c9491f58ec30e5e3afd035e1337694102a80 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Wed, 3 Apr 2019 00:06:13 +0530 Subject: Rename prometheus_owner to proxyable - proxyable is a better name for any model object that has a prometheus server to which requests can be proxied. --- spec/services/prometheus/proxy_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index 1b98c68f99d..c271fc586c1 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -14,7 +14,7 @@ describe Prometheus::ProxyService do it 'initializes attributes' do result = described_class.new(environment, 'GET', 'query', { query: '1' }) - expect(result.prometheus_owner).to eq(environment) + expect(result.proxyable).to eq(environment) expect(result.method).to eq('GET') expect(result.path).to eq('query') expect(result.params).to eq(query: '1') @@ -175,7 +175,7 @@ describe Prometheus::ProxyService do result = described_class.from_cache(environment.class.name, environment.id, 'GET', 'query', { query: '1' }) expect(result).to be_an_instance_of(described_class) - expect(result.prometheus_owner).to eq(environment) + expect(result.proxyable).to eq(environment) expect(result.method).to eq('GET') expect(result.path).to eq('query') expect(result.params).to eq(query: '1') -- cgit v1.2.1 From b3aba1203d7f6a0c96c836593f19165bbf066cd0 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Wed, 3 Apr 2019 14:50:48 +0530 Subject: Filter params based on the proxy_path - Permit params in ProxyService class to avoid having to make changes to both ProxyService and to PrometheusApiController when adding support for more prometheus apis. - Also refactor the cache specs. --- spec/services/prometheus/proxy_service_spec.rb | 67 +++++++++++++++----------- 1 file changed, 39 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index c271fc586c1..8de5f491427 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -9,7 +9,7 @@ describe Prometheus::ProxyService do set(:environment) { create(:environment, project: project) } describe '#initialize' do - let(:params) { ActionController::Parameters.new(query: '1').permit! } + let(:params) { ActionController::Parameters.new(query: '1') } it 'initializes attributes' do result = described_class.new(environment, 'GET', 'query', { query: '1' }) @@ -25,12 +25,23 @@ describe Prometheus::ProxyService do expect(result.params).to be_an_instance_of(Hash) end + + context 'with unknown params' do + let(:params) { ActionController::Parameters.new(query: '1', other_param: 'val') } + + it 'filters unknown params' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.params).to eq('query' => '1') + end + end end describe '#execute' do let(:prometheus_adapter) { instance_double(PrometheusService) } + let(:params) { ActionController::Parameters.new(query: '1') } - subject { described_class.new(environment, 'GET', 'query', { query: '1' }) } + subject { described_class.new(environment, 'GET', 'query', params) } context 'when prometheus_adapter is nil' do before do @@ -62,7 +73,7 @@ describe Prometheus::ProxyService do end context 'cannot proxy' do - subject { described_class.new(environment, 'POST', 'query', { query: '1' }) } + subject { described_class.new(environment, 'POST', 'garbage', params) } it 'returns error' do expect(subject.execute).to eq( @@ -72,44 +83,42 @@ describe Prometheus::ProxyService do end end - context 'when cached', :use_clean_rails_memory_store_caching do + context 'with caching', :use_clean_rails_memory_store_caching do let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } - let(:opts) { [environment.class.name, environment.id, 'GET', 'query', { query: '1' }] } - before do - stub_reactive_cache(subject, return_value, opts) + let(:opts) do + [environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' }] + end + before do allow(environment).to receive(:prometheus_adapter) .and_return(prometheus_adapter) allow(prometheus_adapter).to receive(:can_query?).and_return(true) end - it 'returns cached value' do - result = subject.execute - - expect(result[:http_status]).to eq(return_value[:http_status]) - expect(result[:body]).to eq(return_value[:body]) - end - end + context 'when value present in cache' do + before do + stub_reactive_cache(subject, return_value, opts) + end - context 'when not cached' do - let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } - let(:opts) { [environment.class.name, environment.id, 'GET', 'query', { query: '1' }] } + it 'returns cached value' do + result = subject.execute - before do - allow(environment).to receive(:prometheus_adapter) - .and_return(prometheus_adapter) - allow(prometheus_adapter).to receive(:can_query?).and_return(true) + expect(result[:http_status]).to eq(return_value[:http_status]) + expect(result[:body]).to eq(return_value[:body]) + end end - it 'returns nil' do - expect(ReactiveCachingWorker) - .to receive(:perform_async) - .with(subject.class, subject.id, *opts) + context 'when value not present in cache' do + it 'returns nil' do + expect(ReactiveCachingWorker) + .to receive(:perform_async) + .with(subject.class, subject.id, *opts) - result = subject.execute + result = subject.execute - expect(result).to eq(nil) + expect(result).to eq(nil) + end end end @@ -172,7 +181,9 @@ describe Prometheus::ProxyService do describe '.from_cache' do it 'initializes an instance of ProxyService class' do - result = described_class.from_cache(environment.class.name, environment.id, 'GET', 'query', { query: '1' }) + result = described_class.from_cache( + environment.class.name, environment.id, 'GET', 'query', { query: '1' } + ) expect(result).to be_an_instance_of(described_class) expect(result.proxyable).to eq(environment) -- cgit v1.2.1 From 833206abf833b0c43e35d97b3f2fea98d91d5572 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Wed, 3 Apr 2019 18:24:52 +0530 Subject: Use hash methods to filter params - This is so that we don't have to check that params is of type ActionController::Parameters in ProxyService. --- spec/services/prometheus/proxy_service_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index 8de5f491427..3e2eea12d95 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -9,15 +9,15 @@ describe Prometheus::ProxyService do set(:environment) { create(:environment, project: project) } describe '#initialize' do - let(:params) { ActionController::Parameters.new(query: '1') } + let(:params) { ActionController::Parameters.new(query: '1').permit! } it 'initializes attributes' do - result = described_class.new(environment, 'GET', 'query', { query: '1' }) + result = described_class.new(environment, 'GET', 'query', params) expect(result.proxyable).to eq(environment) expect(result.method).to eq('GET') expect(result.path).to eq('query') - expect(result.params).to eq(query: '1') + expect(result.params).to eq('query' => '1') end it 'converts ActionController::Parameters into hash' do @@ -27,7 +27,7 @@ describe Prometheus::ProxyService do end context 'with unknown params' do - let(:params) { ActionController::Parameters.new(query: '1', other_param: 'val') } + let(:params) { ActionController::Parameters.new(query: '1', other_param: 'val').permit! } it 'filters unknown params' do result = described_class.new(environment, 'GET', 'query', params) @@ -39,7 +39,7 @@ describe Prometheus::ProxyService do describe '#execute' do let(:prometheus_adapter) { instance_double(PrometheusService) } - let(:params) { ActionController::Parameters.new(query: '1') } + let(:params) { ActionController::Parameters.new(query: '1').permit! } subject { described_class.new(environment, 'GET', 'query', params) } @@ -182,14 +182,14 @@ describe Prometheus::ProxyService do describe '.from_cache' do it 'initializes an instance of ProxyService class' do result = described_class.from_cache( - environment.class.name, environment.id, 'GET', 'query', { query: '1' } + environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' } ) expect(result).to be_an_instance_of(described_class) expect(result.proxyable).to eq(environment) expect(result.method).to eq('GET') expect(result.path).to eq('query') - expect(result.params).to eq(query: '1') + expect(result.params).to eq('query' => '1') end end end -- cgit v1.2.1 From 5dd6e752c0c3d572c645ac56a79c6c5a3fbd5157 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Thu, 4 Apr 2019 16:14:08 +0530 Subject: Some code improvements - Use strong_memoize for prometheus_adapter since it can be nil in some cases. - Do not phrase spec descriptions with 'should'. --- spec/services/prometheus/proxy_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index 3e2eea12d95..4bdb20de4c9 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -48,7 +48,7 @@ describe Prometheus::ProxyService do allow(environment).to receive(:prometheus_adapter).and_return(nil) end - it 'should return error' do + it 'returns error' do expect(subject.execute).to eq( status: :error, message: 'No prometheus server found', @@ -63,7 +63,7 @@ describe Prometheus::ProxyService do allow(prometheus_adapter).to receive(:can_query?).and_return(false) end - it 'should return error' do + it 'returns error' do expect(subject.execute).to eq( status: :error, message: 'No prometheus server found', -- cgit v1.2.1 From c1a097e1cee43b127de17d769546ffa244cbf484 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 4 Apr 2019 13:55:49 -0500 Subject: Refactor new_dropdown/index to receive isOpen prop **Why?** Previously it managed this as an internal data property, but there was extra complexity involved because the component doesn't have all the information it needs (i.e. the `mouseOver` prop). This is a good opportunity to colocate the data prop with the component that has the information to change it (i.e. `file_row`) and let `new_dropdown/index` receive this value as a prop. --- .../ide/components/file_row_extra_spec.js | 2 +- .../ide/components/new_dropdown/index_spec.js | 4 +- .../vue_shared/components/file_row_spec.js | 60 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js index c93a939ad71..d7fed3f0681 100644 --- a/spec/javascripts/ide/components/file_row_extra_spec.js +++ b/spec/javascripts/ide/components/file_row_extra_spec.js @@ -20,7 +20,7 @@ describe('IDE extra file row component', () => { file: { ...file('test'), }, - mouseOver: false, + dropdownOpen: false, }); spyOnProperty(vm, 'getUnstagedFilesCountForPath').and.returnValue(() => unstagedFilesCount); diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js index 83e530f0a6a..aaebe88f314 100644 --- a/spec/javascripts/ide/components/new_dropdown/index_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js @@ -56,11 +56,11 @@ describe('new dropdown component', () => { }); }); - describe('dropdownOpen', () => { + describe('isOpen', () => { it('scrolls dropdown into view', done => { spyOn(vm.$refs.dropdownMenu, 'scrollIntoView'); - vm.dropdownOpen = true; + vm.isOpen = true; setTimeout(() => { expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({ diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js index d1fd899c1a8..7da69e3fa84 100644 --- a/spec/javascripts/vue_shared/components/file_row_spec.js +++ b/spec/javascripts/vue_shared/components/file_row_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import FileRow from '~/vue_shared/components/file_row.vue'; +import FileRowExtra from '~/ide/components/file_row_extra.vue'; import { file } from 'spec/ide/helpers'; import mountComponent from '../../helpers/vue_mount_component_helper'; @@ -16,6 +17,10 @@ describe('File row component', () => { vm.$destroy(); }); + const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown'); + const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button'); + const findFileRow = () => vm.$el.querySelector('.file-row'); + it('renders name', () => { createComponent({ file: file('t4'), @@ -84,4 +89,59 @@ describe('File row component', () => { expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null); }); + + describe('new dropdown', () => { + beforeEach(() => { + createComponent({ + file: file('t5'), + level: 1, + extraComponent: FileRowExtra, + }); + }); + + it('renders in extra component', () => { + expect(findNewDropdown()).not.toBe(null); + }); + + it('is hidden at start', () => { + expect(findNewDropdown()).not.toHaveClass('show'); + }); + + it('is opened when button is clicked', done => { + expect(vm.dropdownOpen).toBe(false); + findNewDropdownButton().dispatchEvent(new Event('click')); + + vm.$nextTick() + .then(() => { + expect(vm.dropdownOpen).toBe(true); + expect(findNewDropdown()).toHaveClass('show'); + }) + .then(done) + .catch(done.fail); + }); + + describe('when opened', () => { + beforeEach(() => { + vm.dropdownOpen = true; + }); + + it('stays open when button triggers mouseout', () => { + findNewDropdownButton().dispatchEvent(new Event('mouseout')); + + expect(vm.dropdownOpen).toBe(true); + }); + + it('stays open when button triggers mouseleave', () => { + findNewDropdownButton().dispatchEvent(new Event('mouseleave')); + + expect(vm.dropdownOpen).toBe(true); + }); + + it('closes when row triggers mouseleave', () => { + findFileRow().dispatchEvent(new Event('mouseleave')); + + expect(vm.dropdownOpen).toBe(false); + }); + }); + }); }); -- cgit v1.2.1 From e1a167ee234fbb8886ac1a882002b76adeb67edf Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Tue, 2 Apr 2019 11:56:09 +0530 Subject: Add a Prometheus API per environment The api will proxy requests to the environment's prometheus server. The Prometheus::ProxyService class can be reused when we add support for group prometheus servers. --- .../environments/prometheus_api_controller_spec.rb | 147 +++++++++++++++++++++ .../policies/project_policy_shared_context.rb | 1 + 2 files changed, 148 insertions(+) create mode 100644 spec/controllers/projects/environments/prometheus_api_controller_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb new file mode 100644 index 00000000000..d943d006ae1 --- /dev/null +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Environments::PrometheusApiController do + set(:project) { create(:project) } + set(:environment) { create(:environment, project: project) } + set(:user) { create(:user) } + + before do + project.add_reporter(user) + sign_in(user) + end + + describe 'GET #proxy' do + let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) } + let(:prometheus_response) { { status: :success, body: response_body } } + let(:json_response_body) { JSON.parse(response_body) } + + let(:response_body) do + "{\"status\":\"success\",\"data\":{\"resultType\":\"scalar\",\"result\":[1553864609.117,\"1\"]}}" + end + + before do + allow(Prometheus::ProxyService).to receive(:new) + .with(environment, 'GET', 'query', anything) + .and_return(prometheus_proxy_service) + + allow(prometheus_proxy_service).to receive(:execute) + .and_return(prometheus_response) + end + + it 'returns prometheus response' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq(json_response_body) + end + + it 'filters params' do + get :proxy, params: environment_params({ extra_param: 'dangerous value' }) + + expect(Prometheus::ProxyService).to have_received(:new) + .with(environment, 'GET', 'query', ActionController::Parameters.new({ 'query' => '1' }).permit!) + end + + context 'Prometheus::ProxyService returns nil' do + before do + allow(prometheus_proxy_service).to receive(:execute) + .and_return(nil) + end + + it 'returns 202 accepted' do + get :proxy, params: environment_params + + expect(json_response['status']).to eq('processing') + expect(json_response['message']).to eq('Not ready yet. Try again later.') + expect(response).to have_gitlab_http_status(:accepted) + end + end + + context 'Prometheus::ProxyService returns status success' do + let(:service_response) { { http_status: 404, status: :success, body: '{"body": "value"}' } } + + before do + allow(prometheus_proxy_service).to receive(:execute) + .and_return(service_response) + end + + it 'returns body' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['body']).to eq('value') + end + end + + context 'Prometheus::ProxyService returns status error' do + before do + allow(prometheus_proxy_service).to receive(:execute) + .and_return(service_response) + end + + context 'with http_status' do + let(:service_response) do + { http_status: :service_unavailable, status: :error, message: 'error message' } + end + + it 'sets the http response status code' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:service_unavailable) + expect(json_response['status']).to eq('error') + expect(json_response['message']).to eq('error message') + end + end + + context 'without http_status' do + let(:service_response) { { status: :error, message: 'error message' } } + + it 'returns message' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['status']).to eq('error') + expect(json_response['message']).to eq('error message') + end + end + end + + context 'with anonymous user' do + before do + sign_out(user) + end + + it 'redirects to signin page' do + get :proxy, params: environment_params + + expect(response).to redirect_to(new_user_session_path) + end + end + + context 'without correct permissions' do + before do + project.team.truncate + end + + it 'returns 404' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + private + + def environment_params(params = {}) + { + id: environment.id, + namespace_id: project.namespace, + project_id: project, + proxy_path: 'query', + query: '1' + }.merge(params) + end +end diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb index 3ad6e067674..ee5cfcd850d 100644 --- a/spec/support/shared_context/policies/project_policy_shared_context.rb +++ b/spec/support/shared_context/policies/project_policy_shared_context.rb @@ -25,6 +25,7 @@ RSpec.shared_context 'ProjectPolicy context' do admin_issue admin_label admin_list read_commit_status read_build read_container_image read_pipeline read_environment read_deployment read_merge_request download_wiki_code read_sentry_issue read_release + read_prometheus ] end -- cgit v1.2.1 From 1427ad4f5ce11d8327d9258eb3ffc62803c1a304 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Tue, 2 Apr 2019 18:38:49 +0200 Subject: Streamline controller specs --- .../environments/prometheus_api_controller_spec.rb | 157 ++++++++++----------- 1 file changed, 74 insertions(+), 83 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index d943d006ae1..b1b68bb42d8 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -14,121 +14,112 @@ describe Projects::Environments::PrometheusApiController do describe 'GET #proxy' do let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) } - let(:prometheus_response) { { status: :success, body: response_body } } - let(:json_response_body) { JSON.parse(response_body) } - let(:response_body) do - "{\"status\":\"success\",\"data\":{\"resultType\":\"scalar\",\"result\":[1553864609.117,\"1\"]}}" - end - - before do - allow(Prometheus::ProxyService).to receive(:new) - .with(environment, 'GET', 'query', anything) - .and_return(prometheus_proxy_service) + context 'with valid requests' do + before do + allow(Prometheus::ProxyService).to receive(:new) + .with(environment, 'GET', 'query', anything) + .and_return(prometheus_proxy_service) - allow(prometheus_proxy_service).to receive(:execute) - .and_return(prometheus_response) - end + allow(prometheus_proxy_service).to receive(:execute) + .and_return(service_result) + end - it 'returns prometheus response' do - get :proxy, params: environment_params + context 'with success result' do + let(:service_result) { { status: :success, body: prometheus_body } } + let(:prometheus_body) { '{"status":"success"}' } + let(:prometheus_json_body) { JSON.parse(prometheus_body) } - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to eq(json_response_body) - end + it 'returns prometheus response' do + get :proxy, params: environment_params - it 'filters params' do - get :proxy, params: environment_params({ extra_param: 'dangerous value' }) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq(prometheus_json_body) + end - expect(Prometheus::ProxyService).to have_received(:new) - .with(environment, 'GET', 'query', ActionController::Parameters.new({ 'query' => '1' }).permit!) - end + it 'filters params' do + get :proxy, params: environment_params({ extra_param: 'dangerous value' }) - context 'Prometheus::ProxyService returns nil' do - before do - allow(prometheus_proxy_service).to receive(:execute) - .and_return(nil) + expect(Prometheus::ProxyService).to have_received(:new) + .with(environment, 'GET', 'query', ActionController::Parameters.new({ 'query' => '1' }).permit!) + end end - it 'returns 202 accepted' do - get :proxy, params: environment_params - - expect(json_response['status']).to eq('processing') - expect(json_response['message']).to eq('Not ready yet. Try again later.') - expect(response).to have_gitlab_http_status(:accepted) - end - end + context 'with nil result' do + let(:service_result) { nil } - context 'Prometheus::ProxyService returns status success' do - let(:service_response) { { http_status: 404, status: :success, body: '{"body": "value"}' } } + it 'returns 202 accepted' do + get :proxy, params: environment_params - before do - allow(prometheus_proxy_service).to receive(:execute) - .and_return(service_response) + expect(json_response['status']).to eq('processing') + expect(json_response['message']).to eq('Not ready yet. Try again later.') + expect(response).to have_gitlab_http_status(:accepted) + end end - it 'returns body' do - get :proxy, params: environment_params + context 'with 404 result' do + let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } } - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response['body']).to eq('value') - end - end + it 'returns body' do + get :proxy, params: environment_params - context 'Prometheus::ProxyService returns status error' do - before do - allow(prometheus_proxy_service).to receive(:execute) - .and_return(service_response) + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['body']).to eq('value') + end end - context 'with http_status' do - let(:service_response) do - { http_status: :service_unavailable, status: :error, message: 'error message' } - end + context 'with error result' do + context 'with http_status' do + let(:service_result) do + { http_status: :service_unavailable, status: :error, message: 'error message' } + end - it 'sets the http response status code' do - get :proxy, params: environment_params + it 'sets the http response status code' do + get :proxy, params: environment_params - expect(response).to have_gitlab_http_status(:service_unavailable) - expect(json_response['status']).to eq('error') - expect(json_response['message']).to eq('error message') + expect(response).to have_gitlab_http_status(:service_unavailable) + expect(json_response['status']).to eq('error') + expect(json_response['message']).to eq('error message') + end end - end - context 'without http_status' do - let(:service_response) { { status: :error, message: 'error message' } } + context 'without http_status' do + let(:service_result) { { status: :error, message: 'error message' } } - it 'returns message' do - get :proxy, params: environment_params + it 'returns bad_request' do + get :proxy, params: environment_params - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['status']).to eq('error') - expect(json_response['message']).to eq('error message') + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['status']).to eq('error') + expect(json_response['message']).to eq('error message') + end end end end - context 'with anonymous user' do - before do - sign_out(user) - end + context 'with inappropriate requests' do + context 'with anonymous user' do + before do + sign_out(user) + end - it 'redirects to signin page' do - get :proxy, params: environment_params + it 'redirects to signin page' do + get :proxy, params: environment_params - expect(response).to redirect_to(new_user_session_path) + expect(response).to redirect_to(new_user_session_path) + end end - end - context 'without correct permissions' do - before do - project.team.truncate - end + context 'without correct permissions' do + before do + project.team.truncate + end - it 'returns 404' do - get :proxy, params: environment_params + it 'returns 404' do + get :proxy, params: environment_params - expect(response).to have_gitlab_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) + end end end end @@ -142,6 +133,6 @@ describe Projects::Environments::PrometheusApiController do project_id: project, proxy_path: 'query', query: '1' - }.merge(params) + }.reverse_merge(params) end end -- cgit v1.2.1 From 4a0c8b4a9c25257d9f0addfbc603f2bac80b63db Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Wed, 3 Apr 2019 08:42:12 +0200 Subject: Make filter params specs more readable --- .../projects/environments/prometheus_api_controller_spec.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index b1b68bb42d8..d9ff420b682 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -37,11 +37,13 @@ describe Projects::Environments::PrometheusApiController do expect(json_response).to eq(prometheus_json_body) end - it 'filters params' do - get :proxy, params: environment_params({ extra_param: 'dangerous value' }) + it 'filters unknown params' do + get :proxy, params: environment_params(unknown_param: 'value') - expect(Prometheus::ProxyService).to have_received(:new) - .with(environment, 'GET', 'query', ActionController::Parameters.new({ 'query' => '1' }).permit!) + params = ActionController::Parameters.new('query' => '1').permit! + expect(Prometheus::ProxyService) + .to have_received(:new) + .with(environment, 'GET', 'query', params) end end @@ -133,6 +135,6 @@ describe Projects::Environments::PrometheusApiController do project_id: project, proxy_path: 'query', query: '1' - }.reverse_merge(params) + }.merge(params) end end -- cgit v1.2.1 From eac3e2302c4155ed1890e39b7124672d79b795b6 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Wed, 3 Apr 2019 13:32:45 +0530 Subject: Add spec for invalid environment id --- .../projects/environments/prometheus_api_controller_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index d9ff420b682..2cabb3f75d2 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -124,6 +124,16 @@ describe Projects::Environments::PrometheusApiController do end end end + + context 'with invalid environment id' do + let(:other_environment) { create(:environment) } + + it 'returns 404' do + get :proxy, params: environment_params(id: other_environment.id) + + expect(response).to have_gitlab_http_status(:not_found) + end + end end private -- cgit v1.2.1 From 16772b91f0b71b47c8e72bfa2d11693aed58e3ba Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Wed, 3 Apr 2019 15:01:44 +0530 Subject: Remove permitting of params - It is now being done in ProxyService class. --- .../projects/environments/prometheus_api_controller_spec.rb | 9 --------- 1 file changed, 9 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index 2cabb3f75d2..f1ac127fc9e 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -36,15 +36,6 @@ describe Projects::Environments::PrometheusApiController do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to eq(prometheus_json_body) end - - it 'filters unknown params' do - get :proxy, params: environment_params(unknown_param: 'value') - - params = ActionController::Parameters.new('query' => '1').permit! - expect(Prometheus::ProxyService) - .to have_received(:new) - .with(environment, 'GET', 'query', params) - end end context 'with nil result' do -- cgit v1.2.1 From 5ee7c419ea97f9322dcaf6da02c85f2ac25f477d Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Wed, 3 Apr 2019 19:30:22 +0530 Subject: Call permit! on params --- .../environments/prometheus_api_controller_spec.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index f1ac127fc9e..ee5202ac798 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -14,11 +14,22 @@ describe Projects::Environments::PrometheusApiController do describe 'GET #proxy' do let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) } + let(:expected_params) do + ActionController::Parameters.new( + "query" => "1", + "id" => "1", + "namespace_id" => "namespace1", + "project_id" => "project1", + "proxy_path" => "query", + "controller" => "projects/environments/prometheus_api", + "action" => "proxy" + ).permit! + end context 'with valid requests' do before do allow(Prometheus::ProxyService).to receive(:new) - .with(environment, 'GET', 'query', anything) + .with(environment, 'GET', 'query', expected_params) .and_return(prometheus_proxy_service) allow(prometheus_proxy_service).to receive(:execute) @@ -33,6 +44,8 @@ describe Projects::Environments::PrometheusApiController do it 'returns prometheus response' do get :proxy, params: environment_params + expect(Prometheus::ProxyService).to have_received(:new) + .with(environment, 'GET', 'query', expected_params) expect(response).to have_gitlab_http_status(:ok) expect(json_response).to eq(prometheus_json_body) end -- cgit v1.2.1 From e89b07327166b2af8557d0f177264da25e4df0d3 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Thu, 4 Apr 2019 13:15:15 +0530 Subject: Do not hardcode project and namespace name in url The names can change in different runs of the spec. --- .../projects/environments/prometheus_api_controller_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index ee5202ac798..f0de211c09c 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -17,9 +17,9 @@ describe Projects::Environments::PrometheusApiController do let(:expected_params) do ActionController::Parameters.new( "query" => "1", - "id" => "1", - "namespace_id" => "namespace1", - "project_id" => "project1", + "id" => environment.id.to_s, + "namespace_id" => project.namespace.name, + "project_id" => project.name, "proxy_path" => "query", "controller" => "projects/environments/prometheus_api", "action" => "proxy" -- cgit v1.2.1 From 20594de8cc3311ddc53b29ffdf60bcf4a580a5b3 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Thu, 4 Apr 2019 19:16:45 +0530 Subject: Use environment_params when defining expected_params --- .../environments/prometheus_api_controller_spec.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index f0de211c09c..5a0b92c2514 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -16,13 +16,11 @@ describe Projects::Environments::PrometheusApiController do let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) } let(:expected_params) do ActionController::Parameters.new( - "query" => "1", - "id" => environment.id.to_s, - "namespace_id" => project.namespace.name, - "project_id" => project.name, - "proxy_path" => "query", - "controller" => "projects/environments/prometheus_api", - "action" => "proxy" + environment_params( + proxy_path: 'query', + controller: 'projects/environments/prometheus_api', + action: 'proxy' + ) ).permit! end @@ -144,9 +142,9 @@ describe Projects::Environments::PrometheusApiController do def environment_params(params = {}) { - id: environment.id, - namespace_id: project.namespace, - project_id: project, + id: environment.id.to_s, + namespace_id: project.namespace.name, + project_id: project.name, proxy_path: 'query', query: '1' }.merge(params) -- cgit v1.2.1 From f2fa7c32992db5a2b1b6acadc6c203c93c139f3b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 4 Apr 2019 13:22:11 -0700 Subject: Fix and expand Gitaly FindCommit caching https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26248 added support for deduplicating FindCommit requests using Gitaly ref name caching. However, not all endpoints were covered, and in one case the Gitaly wrapper wasn't actually surrounding the serialization step. We can safely cache ref names between FindCommit calls for #index and #show endpoints for merge requests and pipelines. This can significantly reduce the number of FindCommit requests. --- spec/controllers/projects/merge_requests_controller_spec.rb | 2 ++ spec/controllers/projects/pipelines_controller_spec.rb | 2 ++ 2 files changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c8fa93a74ee..017162519d8 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -60,6 +60,8 @@ describe Projects::MergeRequestsController do end it "renders merge request page" do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + go(format: :html) expect(response).to be_success diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index b64ae552efc..814100f7d5d 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -97,6 +97,8 @@ describe Projects::PipelinesController do RequestStore.clear! RequestStore.begin! + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + expect { get_pipelines_index_json } .to change { Gitlab::GitalyClient.get_request_count }.by(2) end -- cgit v1.2.1 From f77ff0c7bdc0b3a18406bf256dc5814833c14d23 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Tue, 12 Mar 2019 12:55:43 -0600 Subject: Add support for time windows for the performance dashbooards The performance dashboards will now display the data from a set amount of time windows that are defined on a constants file --- spec/javascripts/monitoring/dashboard_spec.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 454777fa912..ebf3972b00d 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; +import { timeWindows } from '~/monitoring/constants'; import axios from '~/lib/utils/axios_utils'; import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; @@ -131,7 +132,7 @@ describe('Dashboard', () => { setTimeout(() => { const dropdownMenuEnvironments = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item', + '.js-environments-dropdown ul', ); expect(dropdownMenuEnvironments.length).toEqual(0); @@ -176,6 +177,26 @@ describe('Dashboard', () => { done(); }); }); + + it('renders the time window dropdown with a set of options ', done => { + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, hasMetrics: true, showPanels: false }, + }); + const numberOfTimeWindows = Object.keys(timeWindows).length; + + setTimeout(() => { + const timeWindowDropdown = component.$el.querySelector('#time-window-dropdown'); + const timeWindowDropdownEls = component.$el.querySelectorAll( + '#time-window-dropdown .dropdown-item', + ); + + expect(timeWindowDropdown).not.toBeNull(); + expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows); + + done(); + }); + }); }); describe('when the window resizes', () => { -- cgit v1.2.1 From 481d25bf482941c48c335f8895a828544a7df873 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Wed, 20 Mar 2019 13:52:24 -0600 Subject: Add feature flag Also added relevant tests that make use of a stubbed feature flag, prettified files --- spec/javascripts/monitoring/dashboard_spec.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index ebf3972b00d..b152ac82ca4 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -39,11 +39,17 @@ describe('Dashboard', () => { ee: false, }; + gon.features = { + metricsTimeWindow: true, + }; + mock = new MockAdapter(axios); DashboardComponent = Vue.extend(Dashboard); }); afterEach(() => { + gon.features = {}; + mock.restore(); }); @@ -186,9 +192,9 @@ describe('Dashboard', () => { const numberOfTimeWindows = Object.keys(timeWindows).length; setTimeout(() => { - const timeWindowDropdown = component.$el.querySelector('#time-window-dropdown'); + const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); const timeWindowDropdownEls = component.$el.querySelectorAll( - '#time-window-dropdown .dropdown-item', + '.js-time-window-dropdown .dropdown-item', ); expect(timeWindowDropdown).not.toBeNull(); -- cgit v1.2.1 From b75e03a6c95d58a9ce0536da314d782c4895ae43 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Thu, 28 Mar 2019 15:29:36 -0600 Subject: Update translation files --- spec/javascripts/monitoring/dashboard_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index b152ac82ca4..a2d1acbe618 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -138,7 +138,7 @@ describe('Dashboard', () => { setTimeout(() => { const dropdownMenuEnvironments = component.$el.querySelectorAll( - '.js-environments-dropdown ul', + '.js-environments-dropdown .dropdown-item', ); expect(dropdownMenuEnvironments.length).toEqual(0); -- cgit v1.2.1 From 217e9e4db1a81125a06830b9dbf270a15fb9c284 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Mon, 1 Apr 2019 12:38:26 -0600 Subject: Created `getTimeDiff` utility function Updated i18n strings and changed the monitoring service graph data params --- spec/javascripts/monitoring/dashboard_spec.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index a2d1acbe618..6d85e2c6ea2 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -184,7 +184,23 @@ describe('Dashboard', () => { }); }); - it('renders the time window dropdown with a set of options ', done => { + it('renders the time window dropdown with a set of options', done => { + gon.features.metricsTimeWindow = false; + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, hasMetrics: true, showPanels: false }, + }); + + setTimeout(() => { + const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); + + expect(timeWindowDropdown).toBeNull(); + + done(); + }); + }); + + it('does not show the time window dropdown when the feature flag is not set', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, -- cgit v1.2.1 From a6f9f79b73ec1893e00a41ae20c3e17bd33cccb4 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Tue, 2 Apr 2019 14:02:29 -0600 Subject: Changed the feature flag usage In order for the dashboard component not to fail, the feature flag is now required by default --- spec/javascripts/monitoring/dashboard_spec.js | 72 ++++++++++++++++++++------- 1 file changed, 53 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 6d85e2c6ea2..1436bcb466e 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -39,17 +39,11 @@ describe('Dashboard', () => { ee: false, }; - gon.features = { - metricsTimeWindow: true, - }; - mock = new MockAdapter(axios); DashboardComponent = Vue.extend(Dashboard); }); afterEach(() => { - gon.features = {}; - mock.restore(); }); @@ -57,7 +51,7 @@ describe('Dashboard', () => { it('shows a getting started empty state when no metrics are present', () => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData, + propsData: { ...propsData, showTimeWindowDropdown: false }, }); expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); @@ -73,7 +67,7 @@ describe('Dashboard', () => { it('shows up a loading state', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true }, + propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false }, }); Vue.nextTick(() => { @@ -85,7 +79,12 @@ describe('Dashboard', () => { it('hides the legend when showLegend is false', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showLegend: false }, + propsData: { + ...propsData, + hasMetrics: true, + showLegend: false, + showTimeWindowDropdown: false, + }, }); setTimeout(() => { @@ -99,7 +98,12 @@ describe('Dashboard', () => { it('hides the group panels when showPanels is false', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); setTimeout(() => { @@ -113,7 +117,12 @@ describe('Dashboard', () => { it('renders the environments dropdown with a number of environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); component.store.storeEnvironmentsData(environmentData); @@ -131,7 +140,12 @@ describe('Dashboard', () => { it('hides the environments dropdown list when there is no environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); component.store.storeEnvironmentsData([]); @@ -149,7 +163,12 @@ describe('Dashboard', () => { it('renders the environments dropdown with a single is-active element', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); component.store.storeEnvironmentsData(environmentData); @@ -173,6 +192,7 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, environmentsEndpoint: '', + showTimeWindowDropdown: false, }, }); @@ -184,11 +204,15 @@ describe('Dashboard', () => { }); }); - it('renders the time window dropdown with a set of options', done => { - gon.features.metricsTimeWindow = false; + it('does not show the time window dropdown when the feature flag is not set', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); setTimeout(() => { @@ -200,10 +224,15 @@ describe('Dashboard', () => { }); }); - it('does not show the time window dropdown when the feature flag is not set', done => { + it('rewnders the time window dropdown with a set of options', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: true, + }, }); const numberOfTimeWindows = Object.keys(timeWindows).length; @@ -234,7 +263,12 @@ describe('Dashboard', () => { it('sets elWidth to page width when the sidebar is resized', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); expect(component.elWidth).toEqual(0); -- cgit v1.2.1 From 976f1feb28265bfc427a00e7031275f49aa7f877 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Wed, 3 Apr 2019 16:10:39 -0600 Subject: Document getTimeDifferenceMinutes usage Also added a finally clause to the getGraphsDataWithTime promise --- spec/javascripts/monitoring/dashboard_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 1436bcb466e..ce2c6c43c0f 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -224,7 +224,7 @@ describe('Dashboard', () => { }); }); - it('rewnders the time window dropdown with a set of options', done => { + it('renders the time window dropdown with a set of options', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { -- cgit v1.2.1 From 08e2bc390f438dc5cd28d40651e369f85b95cbc1 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 14:41:49 +0300 Subject: Extract remove due date spec to shared example --- .../issues/user_uses_quick_actions_spec.rb | 21 ++---------- ...remove_due_date_quick_action_shared_examples.rb | 40 ++++++++++++++-------- 2 files changed, 27 insertions(+), 34 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 426e205b30b..6a8b5e76cda 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -43,7 +43,7 @@ describe 'Issues > User uses quick actions', :js do describe 'issue-only commands' do let(:user) { create(:user) } let(:project) { create(:project, :public) } - let(:issue) { create(:issue, project: project) } + let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } before do project.add_maintainer(user) @@ -57,6 +57,7 @@ describe 'Issues > User uses quick actions', :js do end it_behaves_like 'confidential quick action' + it_behaves_like 'remove_due_date quick action' describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } @@ -76,24 +77,6 @@ describe 'Issues > User uses quick actions', :js do end end - describe 'removing a due date from note' do - let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } - - it_behaves_like 'remove_due_date action available and due date can be removed' - - context 'when the current user cannot update the due date' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it_behaves_like 'remove_due_date action not available' - end - end - describe 'toggling the WIP prefix from the title from note' do let(:issue) { create(:issue, project: project) } diff --git a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb index 5904164fcfc..dd1676a08e2 100644 --- a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb @@ -1,25 +1,35 @@ # frozen_string_literal: true -shared_examples 'remove_due_date action not available' do - it 'does not remove the due date' do - add_note("/remove_due_date") +shared_examples 'remove_due_date quick action' do + context 'remove_due_date action available and due date can be removed' do + it 'removes the due date accordingly' do + add_note('/remove_due_date') - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content '/remove_due_date' - end -end + expect(page).not_to have_content '/remove_due_date' + expect(page).to have_content 'Commands applied' + + visit project_issue_path(project, issue) -shared_examples 'remove_due_date action available and due date can be removed' do - it 'removes the due date accordingly' do - add_note('/remove_due_date') + page.within '.due_date' do + expect(page).to have_content 'No due date' + end + end + end - expect(page).not_to have_content '/remove_due_date' - expect(page).to have_content 'Commands applied' + context 'remove_due_date action not available' do + let(:guest) { create(:user) } + before do + project.add_guest(guest) + gitlab_sign_out + sign_in(guest) + visit project_issue_path(project, issue) + end - visit project_issue_path(project, issue) + it 'does not remove the due date' do + add_note("/remove_due_date") - page.within '.due_date' do - expect(page).to have_content 'No due date' + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content '/remove_due_date' end end end -- cgit v1.2.1 From 8598fb3411521f813f8776f6f992983bf115668b Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sun, 31 Mar 2019 22:33:46 -0500 Subject: Hide kubernetes cluster warning when a cluster exists --- .../projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb index b52fc719a64..ff2d491539b 100644 --- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb +++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb @@ -13,4 +13,14 @@ describe 'projects/settings/ci_cd/_autodevops_form' do expect(rendered).to have_text('You must add a Kubernetes cluster integration to this project with a domain in order for your deployment strategy to work correctly.') end + + context 'when the project has an available kubernetes cluster' do + let!(:cluster) { create(:cluster, cluster_type: :project_type, projects: [project]) } + + it 'does not show a warning message' do + render + + expect(rendered).not_to have_text('You must add a Kubernetes cluster') + end + end end -- cgit v1.2.1 From b752b579e9c84382002fe47c08282338fc3299d4 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Thu, 21 Mar 2019 09:09:47 +0800 Subject: Adds max_descendants_depth to ObjectHierarchy CE-port of 10546-fix-epic-depth-validation --- spec/lib/gitlab/object_hierarchy_spec.rb | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb index 4700a7ad2e1..e6e9ae3223e 100644 --- a/spec/lib/gitlab/object_hierarchy_spec.rb +++ b/spec/lib/gitlab/object_hierarchy_spec.rb @@ -81,6 +81,24 @@ describe Gitlab::ObjectHierarchy, :postgresql do expect { relation.update_all(share_with_group_lock: false) } .to raise_error(ActiveRecord::ReadOnlyRecord) end + + context 'when with_depth is true' do + let(:relation) do + described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true) + end + + it 'includes depth in the results' do + object_depths = { + parent.id => 1, + child1.id => 2, + child2.id => 3 + } + + relation.each do |object| + expect(object.depth).to eq(object_depths[object.id]) + end + end + end end describe '#descendants' do @@ -91,6 +109,28 @@ describe Gitlab::ObjectHierarchy, :postgresql do end end + describe '#max_descendants_depth' do + subject { described_class.new(base_relation).max_descendants_depth } + + context 'when base relation is empty' do + let(:base_relation) { Group.where(id: nil) } + + it { expect(subject).to be_nil } + end + + context 'when base has no children' do + let(:base_relation) { Group.where(id: child2) } + + it { expect(subject).to eq(1) } + end + + context 'when base has grandchildren' do + let(:base_relation) { Group.where(id: parent) } + + it { expect(subject).to eq(3) } + end + end + describe '#ancestors' do it 'includes only the ancestors' do relation = described_class.new(Group.where(id: child2)).ancestors -- cgit v1.2.1 From 949bcb1f8d9695171d0dc88461f6a16800ac57c9 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Wed, 3 Apr 2019 08:42:23 -0500 Subject: Fenced blockquotes to not change source line pos Replaces blockquote fences with \n, keeping the line numbering intact. --- spec/fixtures/blockquote_fence_after.md | 16 ++++++++++++++++ .../banzai/filter/blockquote_fence_filter_spec.rb | 2 +- spec/services/issues/build_service_spec.rb | 4 +++- spec/services/task_list_toggle_service_spec.rb | 21 +++++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md index 2652a842c0e..555905bf07e 100644 --- a/spec/fixtures/blockquote_fence_after.md +++ b/spec/fixtures/blockquote_fence_after.md @@ -18,10 +18,13 @@ Double `>>>` inside code block: Blockquote outside code block: + > Quote + Code block inside blockquote: + > Quote > > ``` @@ -30,8 +33,10 @@ Code block inside blockquote: > > Quote + Single `>>>` inside code block inside blockquote: + > Quote > > ``` @@ -42,8 +47,10 @@ Single `>>>` inside code block inside blockquote: > > Quote + Double `>>>` inside code block inside blockquote: + > Quote > > ``` @@ -56,6 +63,7 @@ Double `>>>` inside code block inside blockquote: > > Quote + Single `>>>` inside HTML:
      @@ -76,10 +84,13 @@ Double `>>>` inside HTML:
       
       Blockquote outside HTML:
       
      +
       > Quote
       
      +
       HTML inside blockquote:
       
      +
       > Quote
       >
       > 
      @@ -88,8 +99,10 @@ HTML inside blockquote:
       >
       > Quote
       
      +
       Single `>>>` inside HTML inside blockquote:
       
      +
       > Quote
       >
       > 
      @@ -100,8 +113,10 @@ Single `>>>` inside HTML inside blockquote:
       >
       > Quote
       
      +
       Double `>>>` inside HTML inside blockquote:
       
      +
       > Quote
       >
       > 
      @@ -113,3 +128,4 @@ Double `>>>` inside HTML inside blockquote:
       > 
      > > Quote + diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb index b645e49bd43..5b3f679084e 100644 --- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb +++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb @@ -13,6 +13,6 @@ describe Banzai::Filter::BlockquoteFenceFilter do end it 'allows trailing whitespace on blockquote fence lines' do - expect(filter(">>> \ntest\n>>> ")).to eq("> test") + expect(filter(">>> \ntest\n>>> ")).to eq("\n> test\n") end end diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 86e58fe06b9..74f1e83b362 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -58,8 +58,10 @@ describe Issues::BuildService do "> That has a quote\n"\ ">>>\n" note_result = " > This is a string\n"\ + " > \n"\ " > > with a blockquote\n"\ - " > > > That has a quote\n" + " > > > That has a quote\n"\ + " > \n" discussion = create(:diff_note_on_merge_request, note: note_text).to_discussion expect(service.item_for_discussion(discussion)).to include(note_result) end diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb index b1260cf740a..9adaee6481b 100644 --- a/spec/services/task_list_toggle_service_spec.rb +++ b/spec/services/task_list_toggle_service_spec.rb @@ -113,4 +113,25 @@ describe TaskListToggleService do expect(toggler.execute).to be_falsey end + + it 'properly handles a GitLab blockquote' do + markdown = + <<-EOT.strip_heredoc + >>> + gitlab blockquote + >>> + + * [ ] Task 1 + * [x] Task 2 + EOT + + markdown_html = Banzai::Pipeline::FullPipeline.call(markdown, project: nil)[:output].to_html + toggler = described_class.new(markdown, markdown_html, + toggle_as_checked: true, + line_source: '* [ ] Task 1', line_number: 5) + + expect(toggler.execute).to be_truthy + expect(toggler.updated_markdown.lines[4]).to eq "* [x] Task 1\n" + expect(toggler.updated_markdown_html).to include('disabled checked> Task 1') + end end -- cgit v1.2.1 From fa73f4ee196b8c9d28c3b0b035acdd71d71dadb3 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 3 Apr 2019 16:44:51 +0700 Subject: Fix merge requst relationships with pipeline in MWPSService MWPSService currently uses the old pipeline lookup method. It searches related merge requests with pipeline.ref, however, this doesn't work for attached/detached merge request pipelines. --- spec/models/ci/build_spec.rb | 25 +++++++++++++++-- .../add_todo_when_build_fails_service_spec.rb | 32 ++++++++++++++++++++++ .../merge_when_pipeline_succeeds_service_spec.rb | 15 ++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 697fe3fda06..d0d43ee4462 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -3216,7 +3216,7 @@ describe Ci::Build do it 'does not try to create a todo' do project.add_developer(user) - expect(service).not_to receive(:commit_status_merge_requests) + expect(service).not_to receive(:pipeline_merge_requests) subject.drop! end @@ -3252,7 +3252,23 @@ describe Ci::Build do end context 'when build is not configured to be retried' do - subject { create(:ci_build, :running, project: project, user: user) } + subject { create(:ci_build, :running, project: project, user: user, pipeline: pipeline) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + ref: 'feature', + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + let(:merge_request) do + create(:merge_request, :opened, + source_branch: 'feature', + source_project: project, + target_branch: 'master', + target_project: project) + end it 'does not retry build' do expect(described_class).not_to receive(:retry) @@ -3271,7 +3287,10 @@ describe Ci::Build do it 'creates a todo' do project.add_developer(user) - expect(service).to receive(:commit_status_merge_requests) + expect_next_instance_of(TodoService) do |todo_service| + expect(todo_service) + .to receive(:merge_request_build_failed).with(merge_request) + end subject.drop! end diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index af0a214c00f..39a2ef579dd 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -77,6 +77,22 @@ describe MergeRequests::AddTodoWhenBuildFailsService do service.execute(commit_status) end end + + context 'when build belongs to a merge request pipeline' do + let(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, + ref: merge_request.merge_ref_path, + merge_request: merge_request, + merge_requests_as_head_pipeline: [merge_request]) + end + + let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) } + + it 'notifies the todo service' do + expect(todo_service).to receive(:merge_request_build_failed).with(merge_request) + service.execute(commit_status) + end + end end describe '#close' do @@ -106,6 +122,22 @@ describe MergeRequests::AddTodoWhenBuildFailsService do service.close(commit_status) end end + + context 'when build belongs to a merge request pipeline' do + let(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, + ref: merge_request.merge_ref_path, + merge_request: merge_request, + merge_requests_as_head_pipeline: [merge_request]) + end + + let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) } + + it 'notifies the todo service' do + expect(todo_service).to receive(:merge_request_build_retried).with(merge_request) + service.close(commit_status) + end + end end describe '#close_all' do diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 52bbd4e794d..1c709df58b6 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -112,6 +112,21 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do service.trigger(unrelated_pipeline) end end + + context 'when pipeline is merge request pipeline' do + let(:pipeline) do + create(:ci_pipeline, :success, + source: :merge_request_event, + ref: mr_merge_if_green_enabled.merge_ref_path, + merge_request: mr_merge_if_green_enabled, + merge_requests_as_head_pipeline: [mr_merge_if_green_enabled]) + end + + it 'merges the associated merge request' do + expect(MergeWorker).to receive(:perform_async) + service.trigger(pipeline) + end + end end describe "#cancel" do -- cgit v1.2.1 From e0725fa436708209f431fc874d1ee148bbfb50f0 Mon Sep 17 00:00:00 2001 From: Fernando Arias Date: Fri, 5 Apr 2019 05:19:23 +0000 Subject: Dynamic vuln graph dimensions * Fix up graph resize logic if navbar is collapsed Add snapshot --- .../resizable_chart_container_spec.js.snap | 21 +++++++ .../components/resizable_chart_container_spec.js | 64 ++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap create mode 100644 spec/frontend/vue_shared/components/resizable_chart_container_spec.js (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap new file mode 100644 index 00000000000..add0c36a120 --- /dev/null +++ b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Resizable Chart Container renders the component 1`] = ` +
      +
      + + 0 + + + + 0 + +
      +
      +`; diff --git a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js new file mode 100644 index 00000000000..8f533e8ab24 --- /dev/null +++ b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue'; +import $ from 'jquery'; + +jest.mock('~/lib/utils/common_utils', () => ({ + debounceByAnimationFrame(callback) { + return jest.spyOn({ callback }, 'callback'); + }, +})); + +describe('Resizable Chart Container', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(ResizableChartContainer, { + attachToDocument: true, + scopedSlots: { + default: ` +
      + {{width}} + {{height}} +
      + `, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the component', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('updates the slot width and height props', () => { + const width = 1920; + const height = 1080; + + // JSDOM mocks and sets clientWidth/clientHeight to 0 so we set manually + wrapper.vm.$refs.chartWrapper = { clientWidth: width, clientHeight: height }; + + $(document).trigger('content.resize'); + + return Vue.nextTick().then(() => { + const widthNode = wrapper.find('.slot > .width'); + const heightNode = wrapper.find('.slot > .height'); + + expect(parseInt(widthNode.text(), 10)).toEqual(width); + expect(parseInt(heightNode.text(), 10)).toEqual(height); + }); + }); + + it('calls onResize on manual resize', () => { + $(document).trigger('content.resize'); + expect(wrapper.vm.debouncedResize).toHaveBeenCalled(); + }); + + it('calls onResize on page resize', () => { + window.dispatchEvent(new Event('resize')); + expect(wrapper.vm.debouncedResize).toHaveBeenCalled(); + }); +}); -- cgit v1.2.1 From 9f36097db2a5901312d7ea86c5a0df315311b7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20=F0=9F=90=BB?= Date: Fri, 5 Apr 2019 06:44:33 +0000 Subject: fix(gitlab-ci-config): allow strings in the 'include' keyword This fix is a followup to !24098 which introduced a validation of the `include:` keyword of a gitlab-ci configuration file when triggered from /ci/lint API calls. However, there was a test case missing: the case of a single string as value. I have added a test case for that which shows that the code was not validating it correctly. This commit fixes that to allow all `include:` valid inputs. --- spec/lib/gitlab/ci/yaml_processor_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 63a0d54dcfc..8b39c4e4dd0 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -615,6 +615,14 @@ module Gitlab subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) } context "when validating a ci config file with no project context" do + context "when a single string is provided" do + let(:include_content) { "/local.gitlab-ci.yml" } + + it "does not return any error" do + expect { subject }.not_to raise_error + end + end + context "when an array is provided" do let(:include_content) { ["/local.gitlab-ci.yml"] } -- cgit v1.2.1 From 59ac0924da8ffc6ae28eecffda2654cd7ab8f761 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 5 Apr 2019 07:29:53 +0000 Subject: Fix IDE detecting MR from fork branch **Why?** Currently the IDE loads a merge request based on only the `source_branch` name. This means it loads MR's from forks that have the same branch name (not good). - This required updating the BE API to accept `source_project_id` --- spec/finders/merge_requests_finder_spec.rb | 8 +++ .../ide/stores/actions/merge_request_spec.js | 75 ++++++++++++---------- 2 files changed, 48 insertions(+), 35 deletions(-) (limited to 'spec') diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 56136eb84bc..f508b9bdb6f 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -83,6 +83,14 @@ describe MergeRequestsFinder do expect(merge_requests).to contain_exactly(merge_request2) end + it 'filters by source project id' do + params = { source_project_id: merge_request2.source_project_id } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) + end + it 'filters by state' do params = { state: 'locked' } diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js index a5839630657..4dd0c1150eb 100644 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js @@ -2,7 +2,6 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; import actions, { - getMergeRequestsForBranch, getMergeRequestData, getMergeRequestChanges, getMergeRequestVersions, @@ -12,13 +11,17 @@ import service from '~/ide/services'; import { activityBarViews } from '~/ide/constants'; import { resetStore } from '../../helpers'; +const TEST_PROJECT = 'abcproject'; +const TEST_PROJECT_ID = 17; + describe('IDE store merge request actions', () => { let mock; beforeEach(() => { mock = new MockAdapter(axios); - store.state.projects.abcproject = { + store.state.projects[TEST_PROJECT] = { + id: TEST_PROJECT_ID, mergeRequests: {}, }; }); @@ -41,10 +44,11 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequests service method', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { - expect(service.getProjectMergeRequests).toHaveBeenCalledWith('abcproject', { + expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, { source_branch: 'bar', + source_project_id: TEST_PROJECT_ID, order_by: 'created_at', per_page: 1, }); @@ -56,13 +60,11 @@ describe('IDE store merge request actions', () => { it('sets the "Merge Request" Object', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { - expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(1); - expect(Object.keys(store.state.projects.abcproject.mergeRequests)[0]).toEqual('2'); - expect(store.state.projects.abcproject.mergeRequests[2]).toEqual( - jasmine.objectContaining(mrData), - ); + expect(store.state.projects.abcproject.mergeRequests).toEqual({ + '2': jasmine.objectContaining(mrData), + }); done(); }) .catch(done.fail); @@ -70,7 +72,7 @@ describe('IDE store merge request actions', () => { it('sets "Current Merge Request" object to the most recent MR', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { expect(store.state.currentMergeRequestId).toEqual('2'); done(); @@ -87,9 +89,9 @@ describe('IDE store merge request actions', () => { it('does not fail if there are no merge requests for current branch', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'foo' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'foo' }) .then(() => { - expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(0); + expect(store.state.projects[TEST_PROJECT].mergeRequests).toEqual({}); expect(store.state.currentMergeRequestId).toEqual(''); done(); }) @@ -106,7 +108,8 @@ describe('IDE store merge request actions', () => { it('flashes message, if error', done => { const flashSpy = spyOnDependency(actions, 'flash'); - getMergeRequestsForBranch({ commit() {} }, { projectId: 'abcproject', branchId: 'bar' }) + store + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { fail('Expected getMergeRequestsForBranch to throw an error'); }) @@ -132,9 +135,9 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequestData service method', done => { store - .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1, { + expect(service.getProjectMergeRequestData).toHaveBeenCalledWith(TEST_PROJECT, 1, { render_html: true, }); @@ -145,10 +148,12 @@ describe('IDE store merge request actions', () => { it('sets the Merge Request Object', done => { store - .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(store.state.projects.abcproject.mergeRequests['1'].title).toBe('mergerequest'); expect(store.state.currentMergeRequestId).toBe(1); + expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].title).toBe( + 'mergerequest', + ); done(); }) @@ -170,7 +175,7 @@ describe('IDE store merge request actions', () => { dispatch, state: store.state, }, - { projectId: 'abcproject', mergeRequestId: 1 }, + { projectId: TEST_PROJECT, mergeRequestId: 1 }, ) .then(done.fail) .catch(() => { @@ -179,7 +184,7 @@ describe('IDE store merge request actions', () => { action: jasmine.any(Function), actionText: 'Please try again', actionPayload: { - projectId: 'abcproject', + projectId: TEST_PROJECT, mergeRequestId: 1, force: false, }, @@ -193,7 +198,7 @@ describe('IDE store merge request actions', () => { describe('getMergeRequestChanges', () => { beforeEach(() => { - store.state.projects.abcproject.mergeRequests['1'] = { changes: [] }; + store.state.projects[TEST_PROJECT].mergeRequests['1'] = { changes: [] }; }); describe('success', () => { @@ -207,9 +212,9 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequestChanges service method', done => { store - .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith('abcproject', 1); + expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith(TEST_PROJECT, 1); done(); }) @@ -218,9 +223,9 @@ describe('IDE store merge request actions', () => { it('sets the Merge Request Changes Object', done => { store - .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(store.state.projects.abcproject.mergeRequests['1'].changes.title).toBe( + expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].changes.title).toBe( 'mergerequest', ); done(); @@ -243,7 +248,7 @@ describe('IDE store merge request actions', () => { dispatch, state: store.state, }, - { projectId: 'abcproject', mergeRequestId: 1 }, + { projectId: TEST_PROJECT, mergeRequestId: 1 }, ) .then(done.fail) .catch(() => { @@ -252,7 +257,7 @@ describe('IDE store merge request actions', () => { action: jasmine.any(Function), actionText: 'Please try again', actionPayload: { - projectId: 'abcproject', + projectId: TEST_PROJECT, mergeRequestId: 1, force: false, }, @@ -266,7 +271,7 @@ describe('IDE store merge request actions', () => { describe('getMergeRequestVersions', () => { beforeEach(() => { - store.state.projects.abcproject.mergeRequests['1'] = { versions: [] }; + store.state.projects[TEST_PROJECT].mergeRequests['1'] = { versions: [] }; }); describe('success', () => { @@ -279,9 +284,9 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequestVersions service method', done => { store - .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith('abcproject', 1); + expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith(TEST_PROJECT, 1); done(); }) @@ -290,9 +295,9 @@ describe('IDE store merge request actions', () => { it('sets the Merge Request Versions Object', done => { store - .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(store.state.projects.abcproject.mergeRequests['1'].versions.length).toBe(1); + expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].versions.length).toBe(1); done(); }) .catch(done.fail); @@ -313,7 +318,7 @@ describe('IDE store merge request actions', () => { dispatch, state: store.state, }, - { projectId: 'abcproject', mergeRequestId: 1 }, + { projectId: TEST_PROJECT, mergeRequestId: 1 }, ) .then(done.fail) .catch(() => { @@ -322,7 +327,7 @@ describe('IDE store merge request actions', () => { action: jasmine.any(Function), actionText: 'Please try again', actionPayload: { - projectId: 'abcproject', + projectId: TEST_PROJECT, mergeRequestId: 1, force: false, }, @@ -336,7 +341,7 @@ describe('IDE store merge request actions', () => { describe('openMergeRequest', () => { const mr = { - projectId: 'abcproject', + projectId: TEST_PROJECT, targetProjectId: 'defproject', mergeRequestId: 2, }; -- cgit v1.2.1 From ebfe19e8e7690598f86facc0bb18df4052468fc0 Mon Sep 17 00:00:00 2001 From: Brandon Labuschagne Date: Thu, 14 Mar 2019 12:32:07 +0200 Subject: Add limit of 128 characters to users name Truncate existing users names which exceed 128 characters Include test for truncating users names --- spec/migrations/truncate_user_fullname_spec.rb | 21 +++++++++++++++++++++ spec/models/user_spec.rb | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 spec/migrations/truncate_user_fullname_spec.rb (limited to 'spec') diff --git a/spec/migrations/truncate_user_fullname_spec.rb b/spec/migrations/truncate_user_fullname_spec.rb new file mode 100644 index 00000000000..17fd4d9f688 --- /dev/null +++ b/spec/migrations/truncate_user_fullname_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190325080727_truncate_user_fullname.rb') + +describe TruncateUserFullname, :migration do + let(:users) { table(:users) } + + let(:user_short) { create_user(name: 'abc', email: 'test_short@example.com') } + let(:user_long) { create_user(name: 'a' * 200 + 'z', email: 'test_long@example.com') } + + def create_user(params) + users.create!(params.merge(projects_limit: 0)) + end + + it 'truncates user full name to the first 128 characters' do + expect { migrate! }.to change { user_long.reload.name }.to('a' * 128) + end + + it 'does not truncate short names' do + expect { migrate! }.not_to change { user_short.reload.name.length } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b7e36748fa2..a45a2737b13 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -98,6 +98,11 @@ describe User do end describe 'validations' do + describe 'name' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_length_of(:name).is_at_most(128) } + end + describe 'username' do it 'validates presence' do expect(subject).to validate_presence_of(:username) -- cgit v1.2.1 From 7f529353e75852e62203eee3b8106b21dca068d9 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Fri, 5 Apr 2019 08:05:54 +0000 Subject: Add a proxy method to PrometheusClient - Also refactor the get and json_api_get methods so that the get method can be reused by the new proxy method. - The new proxy method makes no changes to the request to the prometheus server and response from the prometheus server. This allows it to be used as a proxy to the Prometheus server, hence the name. --- spec/lib/gitlab/prometheus_client_spec.rb | 79 +++++++++- spec/services/prometheus/proxy_service_spec.rb | 195 +++++++++++++++++++++++++ 2 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 spec/services/prometheus/proxy_service_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index 2517ee71f24..f15ae83a02c 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -60,15 +60,13 @@ describe Gitlab::PrometheusClient do end describe 'failure to reach a provided prometheus url' do - let(:prometheus_url) {"https://prometheus.invalid.example.com"} + let(:prometheus_url) {"https://prometheus.invalid.example.com/api/v1/query?query=1"} - subject { described_class.new(RestClient::Resource.new(prometheus_url)) } - - context 'exceptions are raised' do + shared_examples 'exceptions are raised' do it 'raises a Gitlab::PrometheusClient::Error error when a SocketError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "Can't connect to #{prometheus_url}") expect(req_stub).to have_been_requested end @@ -76,7 +74,7 @@ describe Gitlab::PrometheusClient do it 'raises a Gitlab::PrometheusClient::Error error when a SSLError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "#{prometheus_url} contains invalid SSL data") expect(req_stub).to have_been_requested end @@ -84,11 +82,23 @@ describe Gitlab::PrometheusClient do it 'raises a Gitlab::PrometheusClient::Error error when a RestClient::Exception is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error") expect(req_stub).to have_been_requested end end + + context 'ping' do + subject { described_class.new(RestClient::Resource.new(prometheus_url)).ping } + + it_behaves_like 'exceptions are raised' + end + + context 'proxy' do + subject { described_class.new(RestClient::Resource.new(prometheus_url)).proxy('query', { query: '1' }) } + + it_behaves_like 'exceptions are raised' + end end describe '#query' do @@ -258,4 +268,59 @@ describe Gitlab::PrometheusClient do it { is_expected.to eq(step) } end end + + describe 'proxy' do + context 'get API' do + let(:prometheus_query) { prometheus_cpu_query('env-slug') } + let(:query_url) { prometheus_query_url(prometheus_query) } + + around do |example| + Timecop.freeze { example.run } + end + + context 'when response status code is 200' do + it 'returns response object' do + req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector')) + + response = subject.proxy('query', { query: prometheus_query }) + json_response = JSON.parse(response.body) + + expect(response.code).to eq(200) + expect(json_response).to eq({ + 'status' => 'success', + 'data' => { + 'resultType' => 'vector', + 'result' => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] + } + }) + expect(req_stub).to have_been_requested + end + end + + context 'when response status code is not 200' do + it 'returns response object' do + req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'error' }) + + response = subject.proxy('query', { query: prometheus_query }) + json_response = JSON.parse(response.body) + + expect(req_stub).to have_been_requested + expect(response.code).to eq(400) + expect(json_response).to eq('error' => 'error') + end + end + + context 'when RestClient::Exception is raised' do + before do + stub_prometheus_request_with_exception(query_url, RestClient::Exception) + end + + it 'raises PrometheusClient::Error' do + expect { subject.proxy('query', { query: prometheus_query }) }.to( + raise_error(Gitlab::PrometheusClient::Error, 'Network connection error') + ) + end + end + end + end end diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb new file mode 100644 index 00000000000..4bdb20de4c9 --- /dev/null +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Prometheus::ProxyService do + include ReactiveCachingHelpers + + set(:project) { create(:project) } + set(:environment) { create(:environment, project: project) } + + describe '#initialize' do + let(:params) { ActionController::Parameters.new(query: '1').permit! } + + it 'initializes attributes' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.proxyable).to eq(environment) + expect(result.method).to eq('GET') + expect(result.path).to eq('query') + expect(result.params).to eq('query' => '1') + end + + it 'converts ActionController::Parameters into hash' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.params).to be_an_instance_of(Hash) + end + + context 'with unknown params' do + let(:params) { ActionController::Parameters.new(query: '1', other_param: 'val').permit! } + + it 'filters unknown params' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.params).to eq('query' => '1') + end + end + end + + describe '#execute' do + let(:prometheus_adapter) { instance_double(PrometheusService) } + let(:params) { ActionController::Parameters.new(query: '1').permit! } + + subject { described_class.new(environment, 'GET', 'query', params) } + + context 'when prometheus_adapter is nil' do + before do + allow(environment).to receive(:prometheus_adapter).and_return(nil) + end + + it 'returns error' do + expect(subject.execute).to eq( + status: :error, + message: 'No prometheus server found', + http_status: :service_unavailable + ) + end + end + + context 'when prometheus_adapter cannot query' do + before do + allow(environment).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(false) + end + + it 'returns error' do + expect(subject.execute).to eq( + status: :error, + message: 'No prometheus server found', + http_status: :service_unavailable + ) + end + end + + context 'cannot proxy' do + subject { described_class.new(environment, 'POST', 'garbage', params) } + + it 'returns error' do + expect(subject.execute).to eq( + message: 'Proxy support for this API is not available currently', + status: :error + ) + end + end + + context 'with caching', :use_clean_rails_memory_store_caching do + let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } + + let(:opts) do + [environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' }] + end + + before do + allow(environment).to receive(:prometheus_adapter) + .and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(true) + end + + context 'when value present in cache' do + before do + stub_reactive_cache(subject, return_value, opts) + end + + it 'returns cached value' do + result = subject.execute + + expect(result[:http_status]).to eq(return_value[:http_status]) + expect(result[:body]).to eq(return_value[:body]) + end + end + + context 'when value not present in cache' do + it 'returns nil' do + expect(ReactiveCachingWorker) + .to receive(:perform_async) + .with(subject.class, subject.id, *opts) + + result = subject.execute + + expect(result).to eq(nil) + end + end + end + + context 'call prometheus api' do + let(:prometheus_client) { instance_double(Gitlab::PrometheusClient) } + + before do + synchronous_reactive_cache(subject) + + allow(environment).to receive(:prometheus_adapter) + .and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(true) + allow(prometheus_adapter).to receive(:prometheus_client_wrapper) + .and_return(prometheus_client) + end + + context 'connection to prometheus server succeeds' do + let(:rest_client_response) { instance_double(RestClient::Response) } + let(:prometheus_http_status_code) { 400 } + + let(:response_body) do + '{"status":"error","errorType":"bad_data","error":"parse error at char 1: no expression found in input"}' + end + + before do + allow(prometheus_client).to receive(:proxy).and_return(rest_client_response) + + allow(rest_client_response).to receive(:code) + .and_return(prometheus_http_status_code) + allow(rest_client_response).to receive(:body).and_return(response_body) + end + + it 'returns the http status code and body from prometheus' do + expect(subject.execute).to eq( + http_status: prometheus_http_status_code, + body: response_body, + status: :success + ) + end + end + + context 'connection to prometheus server fails' do + context 'prometheus client raises Gitlab::PrometheusClient::Error' do + before do + allow(prometheus_client).to receive(:proxy) + .and_raise(Gitlab::PrometheusClient::Error, 'Network connection error') + end + + it 'returns error' do + expect(subject.execute).to eq( + status: :error, + message: 'Network connection error', + http_status: :service_unavailable + ) + end + end + end + end + end + + describe '.from_cache' do + it 'initializes an instance of ProxyService class' do + result = described_class.from_cache( + environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' } + ) + + expect(result).to be_an_instance_of(described_class) + expect(result.proxyable).to eq(environment) + expect(result.method).to eq('GET') + expect(result.path).to eq('query') + expect(result.params).to eq('query' => '1') + end + end +end -- cgit v1.2.1 From e0df05cfbc7f03e8fce27c5e93fda17f634721e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 3 Apr 2019 22:51:00 +0200 Subject: Require all templates to use default stages Our templates are free to override the 'stages:'. However, they should follow the convention to always include 'build', 'test', 'deploy' which are defaults when not defined. --- spec/lib/gitlab/ci/templates/templates_spec.rb | 54 ++++++++++---------------- spec/lib/gitlab/ci/yaml_processor_spec.rb | 13 +++++-- 2 files changed, 30 insertions(+), 37 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index 4e3681cd943..b52064b3036 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -3,46 +3,32 @@ require 'spec_helper' describe "CI YML Templates" do - ABSTRACT_TEMPLATES = %w[Serverless].freeze - # These templates depend on the presence of the `project` - # param to enable processing of `include:` within CI config. - PROJECT_DEPENDENT_TEMPLATES = %w[Auto-DevOps DAST].freeze - - def self.concrete_templates - Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template| - ABSTRACT_TEMPLATES.include?(template.name) - end - end + using RSpec::Parameterized::TableSyntax - def self.abstract_templates - Gitlab::Template::GitlabCiYmlTemplate.all.select do |template| - ABSTRACT_TEMPLATES.include?(template.name) - end + subject { Gitlab::Ci::YamlProcessor.new(content) } + + where(:template_name) do + Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name) end - describe 'concrete templates with CI/CD jobs' do - concrete_templates.each do |template| - it "#{template.name} template should be valid" do - # Trigger processing of included files - project = create(:project, :test_repo) if PROJECT_DEPENDENT_TEMPLATES.include?(template.name) + with_them do + let(:content) do + <<~EOS + include: + - template: #{template_name} - expect { Gitlab::Ci::YamlProcessor.new(template.content, project: project) } - .not_to raise_error - end + concrete_build_implemented_by_a_user: + stage: test + script: do something + EOS + end + + it 'is valid' do + expect { subject }.not_to raise_error end - end - describe 'abstract templates without concrete jobs defined' do - abstract_templates.each do |template| - it "#{template.name} template should be valid after being implemented" do - content = template.content + <<~EOS - concrete_build_implemented_by_a_user: - stage: build - script: do something - EOS - - expect { Gitlab::Ci::YamlProcessor.new(content) }.not_to raise_error - end + it 'require default stages to be included' do + expect(subject.stages).to include(*Gitlab::Ci::Config::Entry::Stages.default) end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 63a0d54dcfc..518dd59d4a9 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -618,8 +618,8 @@ module Gitlab context "when an array is provided" do let(:include_content) { ["/local.gitlab-ci.yml"] } - it "does not return any error" do - expect { subject }.not_to raise_error + it "returns a validation error" do + expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /does not have project/) end end @@ -635,11 +635,18 @@ module Gitlab let(:include_content) do [ 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml', - '/templates/.after-script-template.yml', { template: 'Auto-DevOps.gitlab-ci.yml' } ] end + before do + WebMock.stub_request(:get, 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml') + .to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: 'prepare: { script: ls -al }') + end + it "does not return any error" do expect { subject }.not_to raise_error end -- cgit v1.2.1 From 4ec16912b8f4eba9cc70dd21afb65c441f857da0 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 5 Apr 2019 08:43:27 +0000 Subject: Autocorrect with RSpec/ExampleWording cop - rewords examples starting with 'should' - rewords examples starting with 'it' Note: I had to manually fixup "onlies" to "only" --- spec/config/object_store_settings_spec.rb | 2 +- .../dashboard/milestones_controller_spec.rb | 2 +- .../controllers/groups/clusters_controller_spec.rb | 2 +- .../groups/settings/ci_cd_controller_spec.rb | 4 +- spec/controllers/groups_controller_spec.rb | 14 ++-- .../omniauth_callbacks_controller_spec.rb | 2 +- spec/controllers/projects/blob_controller_spec.rb | 2 +- .../projects/ci/lints_controller_spec.rb | 16 ++--- .../projects/commits_controller_spec.rb | 2 +- .../projects/mirrors_controller_spec.rb | 4 +- .../projects/project_members_controller_spec.rb | 2 +- .../projects/services_controller_spec.rb | 6 +- spec/controllers/user_callouts_controller_spec.rb | 8 +-- spec/features/expand_collapse_diffs_spec.rb | 4 +- spec/features/explore/groups_list_spec.rb | 6 +- spec/features/groups/settings/ci_cd_spec.rb | 6 +- spec/features/groups_spec.rb | 2 +- spec/features/help_pages_spec.rb | 6 +- .../filtered_search/dropdown_assignee_spec.rb | 6 +- .../issues/filtered_search/dropdown_author_spec.rb | 6 +- .../issues/filtered_search/dropdown_emoji_spec.rb | 6 +- .../filtered_search/dropdown_milestone_spec.rb | 6 +- spec/features/issues/form_spec.rb | 2 +- spec/features/issues/issue_detail_spec.rb | 2 +- spec/features/labels_hierarchy_spec.rb | 2 +- ...ows_commits_from_memebers_who_can_merge_spec.rb | 2 +- .../merge_request/user_sees_merge_widget_spec.rb | 2 +- .../merge_request/user_sees_versions_spec.rb | 2 +- spec/features/projects/blobs/blob_show_spec.rb | 2 +- .../projects/clusters/applications_spec.rb | 4 +- .../projects/commit/mini_pipeline_graph_spec.rb | 2 +- .../projects/environments/environment_spec.rb | 2 +- .../projects/environments/environments_spec.rb | 12 ++-- .../features/projects/members/invite_group_spec.rb | 2 +- spec/features/projects/pipeline_schedules_spec.rb | 6 +- spec/features/projects/pipelines/pipeline_spec.rb | 10 +-- spec/features/projects/pipelines/pipelines_spec.rb | 14 ++-- .../snippets/user_comments_on_snippet_spec.rb | 4 +- spec/features/raven_js_spec.rb | 4 +- .../snippets/notes_on_personal_snippets_spec.rb | 2 +- spec/features/users/overview_spec.rb | 6 +- spec/finders/milestones_finder_spec.rb | 2 +- spec/helpers/icons_helper_spec.rb | 6 +- spec/helpers/search_helper_spec.rb | 2 +- spec/helpers/version_check_helper_spec.rb | 8 +-- spec/lib/api/helpers/custom_validators_spec.rb | 6 +- spec/lib/banzai/filter/plantuml_filter_spec.rb | 6 +- spec/lib/forever_spec.rb | 4 +- ...late_cluster_kubernetes_namespace_table_spec.rb | 8 +-- .../populate_fork_networks_range_spec.rb | 2 +- .../gitlab/ci/config/external/file/base_spec.rb | 4 +- .../gitlab/ci/config/external/file/local_spec.rb | 18 ++--- .../gitlab/ci/config/external/file/project_spec.rb | 22 +++--- .../gitlab/ci/config/external/file/remote_spec.rb | 32 ++++----- .../ci/config/external/file/template_spec.rb | 12 ++-- .../gitlab/ci/config/external/processor_spec.rb | 22 +++--- spec/lib/gitlab/ci/config_spec.rb | 6 +- spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb | 6 +- spec/lib/gitlab/contributions_calendar_spec.rb | 4 +- .../rename_reserved_paths_migration/v1_spec.rb | 4 +- .../file_collection/merge_request_diff_spec.rb | 2 +- spec/lib/gitlab/git/commit_spec.rb | 14 ++-- spec/lib/gitlab/git/diff_spec.rb | 8 +-- spec/lib/gitlab/git/gitmodules_parser_spec.rb | 2 +- spec/lib/gitlab/git/repository_spec.rb | 22 +++--- .../gitlab/kubernetes/cluster_role_binding_spec.rb | 2 +- spec/lib/gitlab/kubernetes/config_map_spec.rb | 2 +- .../gitlab/kubernetes/helm/base_command_spec.rb | 2 +- .../lib/gitlab/kubernetes/helm/certificate_spec.rb | 6 +- spec/lib/gitlab/kubernetes/helm/pod_spec.rb | 20 +++--- spec/lib/gitlab/kubernetes/role_binding_spec.rb | 2 +- spec/lib/gitlab/kubernetes/service_account_spec.rb | 2 +- .../kubernetes/service_account_token_spec.rb | 2 +- spec/lib/gitlab/search_results_spec.rb | 4 +- .../tracing/rails/action_view_subscriber_spec.rb | 6 +- .../tracing/rails/active_record_subscriber_spec.rb | 6 +- spec/lib/gitlab/tracing_spec.rb | 6 +- spec/lib/gitlab/url_sanitizer_spec.rb | 2 +- ...e_auto_dev_ops_domain_to_cluster_domain_spec.rb | 6 +- spec/models/badge_spec.rb | 2 +- spec/models/badges/project_badge_spec.rb | 2 +- spec/models/ci/build_spec.rb | 4 +- spec/models/ci/runner_spec.rb | 2 +- .../clusters/applications/cert_manager_spec.rb | 8 +-- spec/models/clusters/applications/helm_spec.rb | 4 +- spec/models/clusters/applications/ingress_spec.rb | 6 +- spec/models/clusters/applications/jupyter_spec.rb | 6 +- spec/models/clusters/applications/knative_spec.rb | 22 +++--- .../clusters/applications/prometheus_spec.rb | 16 ++--- spec/models/clusters/applications/runner_spec.rb | 8 +-- spec/models/clusters/cluster_spec.rb | 8 +-- spec/models/clusters/kubernetes_namespace_spec.rb | 6 +- spec/models/clusters/platforms/kubernetes_spec.rb | 4 +- spec/models/concerns/issuable_spec.rb | 4 +- spec/models/concerns/spammable_spec.rb | 4 +- spec/models/deploy_token_spec.rb | 32 ++++----- spec/models/group_spec.rb | 4 +- spec/models/namespace_spec.rb | 8 +-- spec/models/network/graph_spec.rb | 2 +- spec/models/project_auto_devops_spec.rb | 16 ++--- .../project_services/kubernetes_service_spec.rb | 24 +++---- .../pivotaltracker_service_spec.rb | 6 +- spec/models/project_spec.rb | 18 ++--- spec/models/remote_mirror_spec.rb | 4 +- spec/models/repository_spec.rb | 24 +++---- spec/models/service_spec.rb | 4 +- spec/presenters/ci/build_presenter_spec.rb | 12 ++-- spec/requests/api/pipelines_spec.rb | 8 +-- spec/requests/api/project_clusters_spec.rb | 62 ++++++++-------- .../serializers/analytics_stage_serializer_spec.rb | 2 +- .../analytics_summary_serializer_spec.rb | 2 +- spec/serializers/environment_entity_spec.rb | 4 +- spec/serializers/job_entity_spec.rb | 18 ++--- .../merge_request_widget_entity_spec.rb | 2 +- ...reate_or_update_service_account_service_spec.rb | 2 +- spec/services/deploy_tokens/create_service_spec.rb | 10 +-- spec/services/groups/transfer_service_spec.rb | 82 +++++++++++----------- .../merge_when_pipeline_succeeds_service_spec.rb | 2 +- .../merge_requests/refresh_service_spec.rb | 10 +-- .../projects/auto_devops/disable_service_spec.rb | 10 +-- .../services/projects/participants_service_spec.rb | 4 +- .../features/discussion_comments_shared_example.rb | 4 +- spec/support/redis/redis_shared_examples.rb | 2 +- ...t_order_from_user_preference_shared_examples.rb | 2 +- .../shared_examples/helm_generated_script.rb | 2 +- .../cluster_application_helm_cert_examples.rb | 4 +- .../snippet_visibility_shared_examples.rb | 12 ++-- spec/uploaders/records_uploads_spec.rb | 4 +- spec/views/groups/edit.html.haml_spec.rb | 2 +- spec/views/projects/_home_panel.html.haml_spec.rb | 4 +- .../shared/milestones/_issuables.html.haml.rb | 6 +- .../shared/projects/_project.html.haml_spec.rb | 4 +- 132 files changed, 505 insertions(+), 505 deletions(-) (limited to 'spec') diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb index b1ada3c99db..efb620fe6dd 100644 --- a/spec/config/object_store_settings_spec.rb +++ b/spec/config/object_store_settings_spec.rb @@ -3,7 +3,7 @@ require Rails.root.join('config', 'object_store_settings.rb') describe ObjectStoreSettings do describe '.parse' do - it 'should set correct default values' do + it 'sets correct default values' do settings = described_class.parse(nil) expect(settings['enabled']).to be false diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index ab40b4eb178..828de0e7ca5 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -75,7 +75,7 @@ describe Dashboard::MilestonesController do expect(response.body).not_to include(project_milestone.title) end - it 'should show counts of group and project milestones to which the user belongs to' do + it 'shows counts of group and project milestones to which the user belongs to' do get :index expect(response.body).to include("Open\n2") diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index ef23ffaa843..e5180ec5c5c 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -455,7 +455,7 @@ describe Groups::ClustersController do context 'when domain is invalid' do let(:domain) { 'http://not-a-valid-domain' } - it 'should not update cluster attributes' do + it 'does not update cluster attributes' do go cluster.reload diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index 15eb0a442a6..3290ed8b088 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -124,7 +124,7 @@ describe Groups::Settings::CiCdController do end context 'when explicitly enabling auto devops' do - it 'should update group attribute' do + it 'updates group attribute' do expect(group.auto_devops_enabled).to eq(true) end end @@ -132,7 +132,7 @@ describe Groups::Settings::CiCdController do context 'when explicitly disabling auto devops' do let(:auto_devops_param) { '0' } - it 'should update group attribute' do + it 'updates group attribute' do expect(group.auto_devops_enabled).to eq(false) end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 38d7240ea81..2b803e7151f 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -566,11 +566,11 @@ describe GroupsController do } end - it 'should return a notice' do + it 'returns a notice' do expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.") end - it 'should redirect to the new path' do + it 'redirects to the new path' do expect(response).to redirect_to("/#{new_parent_group.path}/#{group.path}") end end @@ -587,11 +587,11 @@ describe GroupsController do } end - it 'should return a notice' do + it 'returns a notice' do expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.") end - it 'should redirect to the new path' do + it 'redirects to the new path' do expect(response).to redirect_to("/#{group.path}") end end @@ -611,11 +611,11 @@ describe GroupsController do } end - it 'should return an alert' do + it 'returns an alert' do expect(flash[:alert]).to eq "Transfer failed: namespace directory cannot be moved" end - it 'should redirect to the current path' do + it 'redirects to the current path' do expect(response).to redirect_to(edit_group_path(group)) end end @@ -633,7 +633,7 @@ describe GroupsController do } end - it 'should be denied' do + it 'is denied' do expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 06c6f49f7cc..b823a8d7463 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -55,7 +55,7 @@ describe OmniauthCallbacksController, type: :controller do allow(@routes).to receive(:generate_extras) { [path, []] } end - it 'it calls through to the failure handler' do + it 'calls through to the failure handler' do request.env['omniauth.error'] = OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch") request.env['omniauth.error.strategy'] = OmniAuth::Strategies::SAML.new(nil) stub_route_as('/users/auth/saml/callback') diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 3801fca09dc..32949e0e7d6 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -285,7 +285,7 @@ describe Projects::BlobController do merge_request.update!(source_project: other_project, target_project: other_project) end - it "it redirect to blob" do + it "redirects to blob" do put :update, params: mr_params expect(response).to redirect_to(blob_after_edit_path) diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb index cfa010c2d1c..0b79484bbfa 100644 --- a/spec/controllers/projects/ci/lints_controller_spec.rb +++ b/spec/controllers/projects/ci/lints_controller_spec.rb @@ -16,15 +16,15 @@ describe Projects::Ci::LintsController do get :show, params: { namespace_id: project.namespace, project_id: project } end - it 'should be success' do + it 'is success' do expect(response).to be_success end - it 'should render show page' do + it 'renders show page' do expect(response).to render_template :show end - it 'should retrieve project' do + it 'retrieves project' do expect(assigns(:project)).to eq(project) end end @@ -36,7 +36,7 @@ describe Projects::Ci::LintsController do get :show, params: { namespace_id: project.namespace, project_id: project } end - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end @@ -74,7 +74,7 @@ describe Projects::Ci::LintsController do post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end - it 'should be success' do + it 'is success' do expect(response).to be_success end @@ -82,7 +82,7 @@ describe Projects::Ci::LintsController do expect(response).to render_template :show end - it 'should retrieve project' do + it 'retrieves project' do expect(assigns(:project)).to eq(project) end end @@ -102,7 +102,7 @@ describe Projects::Ci::LintsController do post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end - it 'should assign errors' do + it 'assigns errors' do expect(assigns[:error]).to eq('jobs:rubocop config contains unknown keys: scriptt') end end @@ -114,7 +114,7 @@ describe Projects::Ci::LintsController do post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 8cb9130b834..9fb455dd974 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -15,7 +15,7 @@ describe Projects::CommitsController do describe "GET commits_root" do context "no ref is provided" do - it 'should redirect to the default branch of the project' do + it 'redirects to the default branch of the project' do get(:commits_root, params: { namespace_id: project.namespace, diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb index 86a12a5e903..f2b73956e8d 100644 --- a/spec/controllers/projects/mirrors_controller_spec.rb +++ b/spec/controllers/projects/mirrors_controller_spec.rb @@ -65,7 +65,7 @@ describe Projects::MirrorsController do expect(flash[:notice]).to match(/successfully updated/) end - it 'should create a RemoteMirror object' do + it 'creates a RemoteMirror object' do expect { do_put(project, remote_mirrors_attributes: remote_mirror_attributes) }.to change(RemoteMirror, :count).by(1) end end @@ -82,7 +82,7 @@ describe Projects::MirrorsController do expect(flash[:alert]).to match(/Only allowed protocols are/) end - it 'should not create a RemoteMirror object' do + it 'does not create a RemoteMirror object' do expect { do_put(project, remote_mirrors_attributes: remote_mirror_attributes) }.not_to change(RemoteMirror, :count) end end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 3cc3fe69fba..33486edcdd1 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::ProjectMembersController do let(:project) { create(:project, :public, :access_requestable) } describe 'GET index' do - it 'should have the project_members address with a 200 status code' do + it 'has the project_members address with a 200 status code' do get :index, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 601a292bf54..d00d5bf579d 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -147,7 +147,7 @@ describe Projects::ServicesController do params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } } end - it 'should not update the service' do + it 'does not update the service' do service.reload expect(service.namespace).not_to eq('updated_namespace') end @@ -172,7 +172,7 @@ describe Projects::ServicesController do context 'with approved services' do let(:service_id) { 'jira' } - it 'should render edit page' do + it 'renders edit page' do expect(response).to be_success end end @@ -180,7 +180,7 @@ describe Projects::ServicesController do context 'with a deprecated service' do let(:service_id) { 'kubernetes' } - it 'should render edit page' do + it 'renders edit page' do expect(response).to be_success end end diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb index c71d75a3e7f..3cbbba934b8 100644 --- a/spec/controllers/user_callouts_controller_spec.rb +++ b/spec/controllers/user_callouts_controller_spec.rb @@ -14,11 +14,11 @@ describe UserCalloutsController do let(:feature_name) { UserCallout.feature_names.keys.first } context 'when callout entry does not exist' do - it 'should create a callout entry with dismissed state' do + it 'creates a callout entry with dismissed state' do expect { subject }.to change { UserCallout.count }.by(1) end - it 'should return success' do + it 'returns success' do subject expect(response).to have_gitlab_http_status(:ok) @@ -28,7 +28,7 @@ describe UserCalloutsController do context 'when callout entry already exists' do let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) } - it 'should return success' do + it 'returns success' do subject expect(response).to have_gitlab_http_status(:ok) @@ -39,7 +39,7 @@ describe UserCalloutsController do context 'with invalid feature name' do let(:feature_name) { 'bogus_feature_name' } - it 'should return bad request' do + it 'returns bad request' do subject expect(response).to have_gitlab_http_status(:bad_request) diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 8d801161148..b2b3382666a 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -34,7 +34,7 @@ describe 'Expand and collapse diffs', :js do define_method(file.split('.').first) { file_container(file) } end - it 'should show the diff content with a highlighted line when linking to line' do + it 'shows the diff content with a highlighted line when linking to line' do expect(large_diff).not_to have_selector('.code') expect(large_diff).to have_selector('.nothing-here-block') @@ -48,7 +48,7 @@ describe 'Expand and collapse diffs', :js do expect(large_diff).to have_selector('.hll') end - it 'should show the diff content when linking to file' do + it 'shows the diff content when linking to file' do expect(large_diff).not_to have_selector('.code') expect(large_diff).to have_selector('.nothing-here-block') diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb index 8ed4051856e..56f6b1f7eaf 100644 --- a/spec/features/explore/groups_list_spec.rb +++ b/spec/features/explore/groups_list_spec.rb @@ -68,17 +68,17 @@ describe 'Explore Groups page', :js do end describe 'landing component' do - it 'should show a landing component' do + it 'shows a landing component' do expect(page).to have_content('Below you will find all the groups that are public.') end - it 'should be dismissable' do + it 'is dismissable' do find('.dismiss-button').click expect(page).not_to have_content('Below you will find all the groups that are public.') end - it 'should persistently not show once dismissed' do + it 'does not show persistently once dismissed' do find('.dismiss-button').click visit explore_groups_path diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb index 0f793dbab6e..5b1a9512c55 100644 --- a/spec/features/groups/settings/ci_cd_spec.rb +++ b/spec/features/groups/settings/ci_cd_spec.rb @@ -43,7 +43,7 @@ describe 'Group CI/CD settings' do end context 'as owner first visiting group settings' do - it 'should see instance enabled badge' do + it 'sees instance enabled badge' do visit group_settings_ci_cd_path(group) page.within '#auto-devops-settings' do @@ -53,7 +53,7 @@ describe 'Group CI/CD settings' do end context 'when Auto DevOps group has been enabled' do - it 'should see group enabled badge' do + it 'sees group enabled badge' do group.update!(auto_devops_enabled: true) visit group_settings_ci_cd_path(group) @@ -65,7 +65,7 @@ describe 'Group CI/CD settings' do end context 'when Auto DevOps group has been disabled' do - it 'should not see a badge' do + it 'does not see a badge' do group.update!(auto_devops_enabled: false) visit group_settings_ci_cd_path(group) diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index c2f32c76422..8e7f78cab81 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -237,7 +237,7 @@ describe 'Group' do let!(:project) { create(:project, namespace: group) } let!(:path) { group_path(group) } - it 'it renders projects and groups on the page' do + it 'renders projects and groups on the page' do visit path wait_for_requests diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index e24b1f4349d..bcd2b90d3bb 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -82,15 +82,15 @@ describe 'Help Pages' do visit help_path end - it 'should display custom help page text' do + it 'displays custom help page text' do expect(page).to have_text "My Custom Text" end - it 'should hide marketing content when enabled' do + it 'hides marketing content when enabled' do expect(page).not_to have_link "Get a support subscription" end - it 'should use a custom support url' do + it 'uses a custom support url' do expect(page).to have_link "See our website for getting help", href: "http://example.com/help" end end diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index e0b1e286dee..75313442b65 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -42,7 +42,7 @@ describe 'Dropdown assignee', :js do expect(page).to have_css(js_dropdown_assignee, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do # We aren't using `input_filtered_search` because we want to see the loading indicator filtered_search.set('assignee:') @@ -51,13 +51,13 @@ describe 'Dropdown assignee', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do input_filtered_search('assignee:', submit: false, extra_space: false) expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading') end - it 'should load all the assignees when opened' do + it 'loads all the assignees when opened' do input_filtered_search('assignee:', submit: false, extra_space: false) expect(dropdown_assignee_size).to eq(4) diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index bedc61b9eed..bc8d9bc8450 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -50,7 +50,7 @@ describe 'Dropdown author', :js do expect(page).to have_css(js_dropdown_author, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do filtered_search.set('author:') @@ -58,13 +58,13 @@ describe 'Dropdown author', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do send_keys_to_filtered_search('author:') expect(page).not_to have_css('#js-dropdown-author .filter-dropdown-loading') end - it 'should load all the authors when opened' do + it 'loads all the authors when opened' do send_keys_to_filtered_search('author:') expect(dropdown_author_size).to eq(4) diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb index f36d4e8f23f..a5c3ab7e7d0 100644 --- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb @@ -69,7 +69,7 @@ describe 'Dropdown emoji', :js do expect(page).to have_css(js_dropdown_emoji, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do filtered_search.set('my-reaction:') @@ -77,13 +77,13 @@ describe 'Dropdown emoji', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do send_keys_to_filtered_search('my-reaction:') expect(page).not_to have_css('#js-dropdown-my-reaction .filter-dropdown-loading') end - it 'should load all the emojis when opened' do + it 'loads all the emojis when opened' do send_keys_to_filtered_search('my-reaction:') expect(dropdown_emoji_size).to eq(4) diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index b330eafe1d1..7584339ccc0 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -49,7 +49,7 @@ describe 'Dropdown milestone', :js do expect(page).to have_css(js_dropdown_milestone, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do filtered_search.set('milestone:') @@ -57,13 +57,13 @@ describe 'Dropdown milestone', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do filtered_search.set('milestone:') expect(find(js_dropdown_milestone)).not_to have_css('.filter-dropdown-loading') end - it 'should load all the milestones when opened' do + it 'loads all the milestones when opened' do filtered_search.set('milestone:') expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6) diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index f2e4c5779df..26c781350e5 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -45,7 +45,7 @@ describe 'New/edit issue', :js do wait_for_requests end - it 'should display selected users even if they are not part of the original API call' do + it 'displays selected users even if they are not part of the original API call' do find('.dropdown-input-field').native.send_keys user2.name page.within '.dropdown-menu-user' do diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 76bc93e9766..791bd003597 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -26,7 +26,7 @@ describe 'Issue Detail', :js do wait_for_requests end - it 'should encode the description to prevent xss issues' do + it 'encodes the description to prevent xss issues' do page.within('.issuable-details .detail-page-description') do expect(page).to have_selector('img', count: 1) expect(find('img')['onerror']).to be_nil diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index 7c31e67a7fa..bac297de4a6 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -145,7 +145,7 @@ describe 'Labels Hierarchy', :js, :nested_groups do visit new_project_issue_path(project_1) end - it 'should be able to assign ancestor group labels' do + it 'is able to assign ancestor group labels' do fill_in 'issue_title', with: 'new created issue' fill_in 'issue_description', with: 'new issue description' diff --git a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb index 0ccab5b2fac..b8c4a78e24f 100644 --- a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb +++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb @@ -76,7 +76,7 @@ describe 'create a merge request, allowing commits from members who can merge to sign_in(member) end - it 'it hides the option from members' do + it 'hides the option from members' do visit edit_project_merge_request_path(target_project, merge_request) expect(page).not_to have_content('Allows commits from members who can merge to the target branch') diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 2609546990d..40ba676ff92 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -302,7 +302,7 @@ describe 'Merge request > User sees merge widget', :js do visit project_merge_request_path(project_only_mwps, merge_request_in_only_mwps_project) end - it 'should be allowed to merge' do + it 'is allowed to merge' do # Wait for the `ci_status` and `merge_check` requests wait_for_requests diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index 5c45e363997..6eae3fd4676 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -230,7 +230,7 @@ describe 'Merge request > User sees versions', :js do wait_for_requests end - it 'should only show diffs from the commit' do + it 'only shows diffs from the commit' do diff_commit_ids = find_all('.diff-file [data-commit-id]').map {|diff| diff['data-commit-id']} expect(diff_commit_ids).not_to be_empty diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index a7aa63018fd..aa2e538cc8e 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -572,7 +572,7 @@ describe 'File blob', :js do visit_blob('files/ruby/test.rb', ref: 'feature') end - it 'should show the realtime pipeline status' do + it 'shows the realtime pipeline status' do page.within('.commit-actions') do expect(page).to have_css('.ci-status-icon') expect(page).to have_css('.ci-status-icon-running') diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index aa1c3902f0f..527508b3519 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -80,7 +80,7 @@ describe 'Clusters Applications', :js do context 'on an abac cluster' do let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project]) } - it 'should show info block and not be installable' do + it 'shows info block and not be installable' do page.within('.js-cluster-application-row-knative') do expect(page).to have_css('.rbac-notice') expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') @@ -91,7 +91,7 @@ describe 'Clusters Applications', :js do context 'on an rbac cluster' do let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } - it 'should not show callout block and be installable' do + it 'does not show callout block and be installable' do page.within('.js-cluster-application-row-knative') do expect(page).not_to have_css('.rbac-notice') expect(page).to have_css('.js-cluster-application-install-button:not([disabled])') diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb index 19f6ebf2c1a..614f11c8392 100644 --- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -43,7 +43,7 @@ describe 'Mini Pipeline Graph in Commit View', :js do visit project_commit_path(project, project.commit.id) end - it 'should not display a mini pipeline graph' do + it 'does not display a mini pipeline graph' do expect(page).not_to have_selector('.mr-widget-pipeline-graph') end end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index fe71cb7661a..da4ef6428d4 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -159,7 +159,7 @@ describe 'Environment' do context 'for project maintainer' do let(:role) { :maintainer } - it 'it shows the terminal button' do + it 'shows the terminal button' do expect(page).to have_terminal_button end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index b2a435e554d..7b7e45312d9 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -30,7 +30,7 @@ describe 'Environments page', :js do end describe 'in available tab page' do - it 'should show one environment' do + it 'shows one environment' do visit_environments(project, scope: 'available') expect(page).to have_css('.environments-container') @@ -44,7 +44,7 @@ describe 'Environments page', :js do create_list(:environment, 4, project: project, state: :available) end - it 'should render second page of pipelines' do + it 'renders second page of pipelines' do visit_environments(project, scope: 'available') find('.js-next-button').click @@ -56,7 +56,7 @@ describe 'Environments page', :js do end describe 'in stopped tab page' do - it 'should show no environments' do + it 'shows no environments' do visit_environments(project, scope: 'stopped') expect(page).to have_css('.environments-container') @@ -72,7 +72,7 @@ describe 'Environments page', :js do allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) end - it 'should show one environment without error' do + it 'shows one environment without error' do visit_environments(project, scope: 'available') expect(page).to have_css('.environments-container') @@ -87,7 +87,7 @@ describe 'Environments page', :js do end describe 'in available tab page' do - it 'should show no environments' do + it 'shows no environments' do visit_environments(project, scope: 'available') expect(page).to have_css('.environments-container') @@ -96,7 +96,7 @@ describe 'Environments page', :js do end describe 'in stopped tab page' do - it 'should show one environment' do + it 'shows one environment' do visit_environments(project, scope: 'stopped') expect(page).to have_css('.environments-container') diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb index b2d2dba55f1..7432c600c1e 100644 --- a/spec/features/projects/members/invite_group_spec.rb +++ b/spec/features/projects/members/invite_group_spec.rb @@ -159,7 +159,7 @@ describe 'Project > Members > Invite group', :js do open_select2 '#link_group_id' end - it 'should infinitely scroll' do + it 'infinitely scrolls' do expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 1) scroll_select2_to_bottom('.select2-drop .select2-results:visible') diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index ee6b67b2188..b1a705f09ce 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -93,14 +93,14 @@ describe 'Pipeline Schedules', :js do expect(page).to have_button('UTC') end - it 'it creates a new scheduled pipeline' do + it 'creates a new scheduled pipeline' do fill_in_schedule_form save_pipeline_schedule expect(page).to have_content('my fancy description') end - it 'it prevents an invalid form from being submitted' do + it 'prevents an invalid form from being submitted' do save_pipeline_schedule expect(page).to have_content('This field is required') @@ -112,7 +112,7 @@ describe 'Pipeline Schedules', :js do edit_pipeline_schedule end - it 'it displays existing properties' do + it 'displays existing properties' do description = find_field('schedule_description').value expect(description).to eq('pipeline schedule') expect(page).to have_button('master') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index b197557039d..cf334e1e4da 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -154,7 +154,7 @@ describe 'Pipeline', :js do end end - it 'should be possible to retry the success job' do + it 'is possible to retry the success job' do find('#ci-badge-build .ci-action-icon-container').click expect(page).not_to have_content('Retry job') @@ -194,13 +194,13 @@ describe 'Pipeline', :js do end end - it 'should be possible to retry the failed build' do + it 'is possible to retry the failed build' do find('#ci-badge-test .ci-action-icon-container').click expect(page).not_to have_content('Retry job') end - it 'should include the failure reason' do + it 'includes the failure reason' do page.within('#ci-badge-test') do build_link = page.find('.js-pipeline-graph-job-link') expect(build_link['data-original-title']).to eq('test - failed - (unknown failure)') @@ -220,7 +220,7 @@ describe 'Pipeline', :js do end end - it 'should be possible to play the manual job' do + it 'is possible to play the manual job' do find('#ci-badge-manual-build .ci-action-icon-container').click expect(page).not_to have_content('Play job') @@ -454,7 +454,7 @@ describe 'Pipeline', :js do expect(page).to have_content('Cancel running') end - it 'should not link to job' do + it 'does not link to job' do expect(page).not_to have_selector('.js-pipeline-graph-job-link') end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 7ca3b3d8edd..cb14db7665d 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -542,19 +542,19 @@ describe 'Pipelines', :js do visit_project_pipelines end - it 'should render a mini pipeline graph' do + it 'renders a mini pipeline graph' do expect(page).to have_selector('.js-mini-pipeline-graph') expect(page).to have_selector('.js-builds-dropdown-button') end context 'when clicking a stage badge' do - it 'should open a dropdown' do + it 'opens a dropdown' do find('.js-builds-dropdown-button').click expect(page).to have_link build.name end - it 'should be possible to cancel pending build' do + it 'is possible to cancel pending build' do find('.js-builds-dropdown-button').click find('.js-ci-action').click wait_for_requests @@ -570,7 +570,7 @@ describe 'Pipelines', :js do name: 'build') end - it 'should display the failure reason' do + it 'displays the failure reason' do find('.js-builds-dropdown-button').click within('.js-builds-dropdown-list') do @@ -587,21 +587,21 @@ describe 'Pipelines', :js do create(:ci_empty_pipeline, project: project) end - it 'should render pagination' do + it 'renders pagination' do visit project_pipelines_path(project) wait_for_requests expect(page).to have_selector('.gl-pagination') end - it 'should render second page of pipelines' do + it 'renders second page of pipelines' do visit project_pipelines_path(project, page: '2') wait_for_requests expect(page).to have_selector('.gl-pagination .page', count: 2) end - it 'should show updated content' do + it 'shows updated content' do visit project_pipelines_path(project) wait_for_requests page.find('.js-next-button .page-link').click diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb index 9c1ef78b0ca..4e1e2f330ec 100644 --- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb +++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb @@ -23,14 +23,14 @@ describe 'Projects > Snippets > User comments on a snippet', :js do expect(page).to have_content('Good snippet!') end - it 'should have autocomplete' do + it 'has autocomplete' do find('#note_note').native.send_keys('') fill_in 'note[note]', with: '@' expect(page).to have_selector('.atwho-view') end - it 'should have zen mode' do + it 'has zen mode' do find('.js-zen-enter').click expect(page).to have_selector('.fullscreen') end diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb index b0923b451ee..9a049764dec 100644 --- a/spec/features/raven_js_spec.rb +++ b/spec/features/raven_js_spec.rb @@ -3,13 +3,13 @@ require 'spec_helper' describe 'RavenJS' do let(:raven_path) { '/raven.chunk.js' } - it 'should not load raven if sentry is disabled' do + it 'does not load raven if sentry is disabled' do visit new_user_session_path expect(has_requested_raven).to eq(false) end - it 'should load raven if sentry is enabled' do + it 'loads raven if sentry is enabled' do stub_application_setting(clientside_sentry_dsn: 'https://key@domain.com/id', clientside_sentry_enabled: true) visit new_user_session_path diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index fc6726985ae..78e0a43ce6d 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -83,7 +83,7 @@ describe 'Comments on personal snippets', :js do expect(find('div#notes')).to have_content('This is awesome!') end - it 'should not have autocomplete' do + it 'does not have autocomplete' do wait_for_requests find('#note_note').native.send_keys('') diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb index 3db9ae7a951..bfa85696e19 100644 --- a/spec/features/users/overview_spec.rb +++ b/spec/features/users/overview_spec.rb @@ -93,7 +93,7 @@ describe 'Overview tab on a user profile', :js do describe 'user has no personal projects' do include_context 'visit overview tab' - it 'it shows an empty project list with an info message' do + it 'shows an empty project list with an info message' do page.within('.projects-block') do expect(page).to have_selector('.loading', visible: false) expect(page).to have_content('You haven\'t created any personal projects.') @@ -113,7 +113,7 @@ describe 'Overview tab on a user profile', :js do include_context 'visit overview tab' - it 'it shows one entry in the list of projects' do + it 'shows one entry in the list of projects' do page.within('.projects-block') do expect(page).to have_selector('.project-row', count: 1) end @@ -139,7 +139,7 @@ describe 'Overview tab on a user profile', :js do include_context 'visit overview tab' - it 'it shows max. ten entries in the list of projects' do + it 'shows max. ten entries in the list of projects' do page.within('.projects-block') do expect(page).to have_selector('.project-row', count: 10) end diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb index ecffbb9e197..34c7b508c56 100644 --- a/spec/finders/milestones_finder_spec.rb +++ b/spec/finders/milestones_finder_spec.rb @@ -9,7 +9,7 @@ describe MilestonesFinder do let!(:milestone_3) { create(:milestone, project: project_1, state: 'active', due_date: Date.tomorrow) } let!(:milestone_4) { create(:milestone, project: project_2, state: 'active') } - it 'it returns milestones for projects' do + it 'returns milestones for projects' do result = described_class.new(project_ids: [project_1.id, project_2.id], state: 'all').execute expect(result).to contain_exactly(milestone_3, milestone_4) diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index 4b40d523287..37e9ddadb8c 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -59,19 +59,19 @@ describe IconsHelper do describe 'non existing icon' do non_existing = 'non_existing_icon_sprite' - it 'should raise in development mode' do + it 'raises in development mode' do allow(Rails.env).to receive(:development?).and_return(true) expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) end - it 'should raise in test mode' do + it 'raises in test mode' do allow(Rails.env).to receive(:test?).and_return(true) expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) end - it 'should not raise in production mode' do + it 'does not raise in production mode' do allow(Rails.env).to receive(:test?).and_return(false) allow(Rails.env).to receive(:development?).and_return(false) diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 9cff0291250..2f59cfda0a0 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -12,7 +12,7 @@ describe SearchHelper do allow(self).to receive(:current_user).and_return(nil) end - it "it returns nil" do + it "returns nil" do expect(search_autocomplete_opts("q")).to be_nil end end diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index bfec7ad4bba..e384e2bf9a0 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe VersionCheckHelper do describe '#version_status_badge' do - it 'should return nil if not dev environment and not enabled' do + it 'returns nil if not dev environment and not enabled' do allow(Rails.env).to receive(:production?) { false } allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false } @@ -16,16 +16,16 @@ describe VersionCheckHelper do allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } end - it 'should return an image tag' do + it 'returns an image tag' do expect(helper.version_status_badge).to start_with(' 'some_value' }) end end @@ -44,7 +44,7 @@ describe API::Helpers::CustomValidators do end context 'invalid parameters' do - it 'should raise a validation error' do + it 'raises a validation error' do expect_validation_error({ 'test' => 'some_other_string' }) end end @@ -67,7 +67,7 @@ describe API::Helpers::CustomValidators do end context 'invalid parameters' do - it 'should raise a validation error' do + it 'raises a validation error' do expect_validation_error({ 'test' => 'some_other_string' }) end end diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb index 8235c411eb7..6f7acfe7072 100644 --- a/spec/lib/banzai/filter/plantuml_filter_spec.rb +++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::PlantumlFilter do include FilterSpecHelper - it 'should replace plantuml pre tag with img tag' do + it 'replaces plantuml pre tag with img tag' do stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") input = '
      Bob -> Sara : Hello
      ' output = '
      ' @@ -12,7 +12,7 @@ describe Banzai::Filter::PlantumlFilter do expect(doc.to_s).to eq output end - it 'should not replace plantuml pre tag with img tag if disabled' do + it 'does not replace plantuml pre tag with img tag if disabled' do stub_application_setting(plantuml_enabled: false) input = '
      Bob -> Sara : Hello
      ' output = '
      Bob -> Sara : Hello
      ' @@ -21,7 +21,7 @@ describe Banzai::Filter::PlantumlFilter do expect(doc.to_s).to eq output end - it 'should not replace plantuml pre tag with img tag if url is invalid' do + it 'does not replace plantuml pre tag with img tag if url is invalid' do stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid") input = '
      Bob -> Sara : Hello
      ' output = '
       PlantUML Error: cannot connect to PlantUML server at "invalid"
      ' diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb index 494c0561975..b9ffe895bf0 100644 --- a/spec/lib/forever_spec.rb +++ b/spec/lib/forever_spec.rb @@ -5,7 +5,7 @@ describe Forever do subject { described_class.date } context 'when using PostgreSQL' do - it 'should return Postgresql future date' do + it 'returns Postgresql future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) expect(subject).to eq(described_class::POSTGRESQL_DATE) @@ -13,7 +13,7 @@ describe Forever do end context 'when using MySQL' do - it 'should return MySQL future date' do + it 'returns MySQL future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(false) expect(subject).to eq(described_class::MYSQL_DATE) diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb index 812e0cc6947..128e118ac17 100644 --- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : end shared_examples 'consistent kubernetes namespace attributes' do - it 'should populate namespace and service account information' do + it 'populates namespace and service account information' do migration.perform clusters_with_namespace.each do |cluster| @@ -41,7 +41,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : context 'when no Clusters::Project has a Clusters::KubernetesNamespace' do let(:cluster_projects) { cluster_projects_table.all } - it 'should create a Clusters::KubernetesNamespace per Clusters::Project' do + it 'creates a Clusters::KubernetesNamespace per Clusters::Project' do expect do migration.perform end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects_table.count) @@ -57,7 +57,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : create_kubernetes_namespace(clusters_table.all) end - it 'should not create any Clusters::KubernetesNamespace' do + it 'does not create any Clusters::KubernetesNamespace' do expect do migration.perform end.not_to change(Clusters::KubernetesNamespace, :count) @@ -78,7 +78,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : end.to change(Clusters::KubernetesNamespace, :count).by(with_no_kubernetes_namespace.count) end - it 'should not modify clusters with Clusters::KubernetesNamespace' do + it 'does not modify clusters with Clusters::KubernetesNamespace' do migration.perform with_kubernetes_namespace.each do |cluster| diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb index 8582af96199..0e73c8c59c9 100644 --- a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb @@ -41,7 +41,7 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch migration.perform(1, 3) end - it 'it creates the fork network' do + it 'creates the fork network' do expect(fork_network1).not_to be_nil expect(fork_network2).not_to be_nil end diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb index fa39b32d7ab..dd536a241bd 100644 --- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::External::File::Base do context 'when a location is present' do let(:location) { 'some-location' } - it 'should return true' do + it 'returns true' do expect(subject).to be_matching end end @@ -34,7 +34,7 @@ describe Gitlab::Ci::Config::External::File::Base do context 'with a location is missing' do let(:location) { nil } - it 'should return false' do + it 'returns false' do expect(subject).not_to be_matching end end diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index dc14b07287e..9451db9522a 100644 --- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'when a local is specified' do let(:params) { { local: 'file' } } - it 'should return true' do + it 'returns true' do expect(local_file).to be_matching end end @@ -23,7 +23,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'with a missing local' do let(:params) { { local: nil } } - it 'should return false' do + it 'returns false' do expect(local_file).not_to be_matching end end @@ -31,7 +31,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'with a missing local key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(local_file).not_to be_matching end end @@ -45,7 +45,7 @@ describe Gitlab::Ci::Config::External::File::Local do allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("image: 'ruby2:2'") end - it 'should return true' do + it 'returns true' do expect(local_file.valid?).to be_truthy end end @@ -53,7 +53,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'when is not a valid local path' do let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } - it 'should return false' do + it 'returns false' do expect(local_file.valid?).to be_falsy end end @@ -61,7 +61,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'when is not a yaml file' do let(:location) { '/config/application.rb' } - it 'should return false' do + it 'returns false' do expect(local_file.valid?).to be_falsy end end @@ -84,7 +84,7 @@ describe Gitlab::Ci::Config::External::File::Local do allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return(local_file_content) end - it 'should return the content of the file' do + it 'returns the content of the file' do expect(local_file.content).to eq(local_file_content) end end @@ -92,7 +92,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'with an invalid file' do let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } - it 'should be nil' do + it 'is nil' do expect(local_file.content).to be_nil end end @@ -101,7 +101,7 @@ describe Gitlab::Ci::Config::External::File::Local do describe '#error_message' do let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } - it 'should return an error message' do + it 'returns an error message' do expect(local_file.error_message).to eq("Local file `#{location}` does not exist!") end end diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index 6e89bb1b30f..4acb4f324d3 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'when a file and project is specified' do let(:params) { { file: 'file.yml', project: 'project' } } - it 'should return true' do + it 'returns true' do expect(project_file).to be_matching end end @@ -27,7 +27,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'with only file is specified' do let(:params) { { file: 'file.yml' } } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_matching end end @@ -35,7 +35,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'with only project is specified' do let(:params) { { project: 'project' } } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_matching end end @@ -43,7 +43,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'with a missing local key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_matching end end @@ -61,14 +61,14 @@ describe Gitlab::Ci::Config::External::File::Project do stub_project_blob(root_ref_sha, '/file.yml') { 'image: ruby:2.1' } end - it 'should return true' do + it 'returns true' do expect(project_file).to be_valid end context 'when user does not have permission to access file' do let(:context_user) { create(:user) } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` not found or access denied!") end @@ -86,7 +86,7 @@ describe Gitlab::Ci::Config::External::File::Project do stub_project_blob(ref_sha, '/file.yml') { 'image: ruby:2.1' } end - it 'should return true' do + it 'returns true' do expect(project_file).to be_valid end end @@ -102,7 +102,7 @@ describe Gitlab::Ci::Config::External::File::Project do stub_project_blob(root_ref_sha, '/file.yml') { '' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!") end @@ -113,7 +113,7 @@ describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, ref: 'I-Do-Not-Exist', file: '/file.yml' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!") end @@ -124,7 +124,7 @@ describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, file: '/invalid-file.yml' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!") end @@ -135,7 +135,7 @@ describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, file: '/invalid-file' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include('Included file `/invalid-file` does not have YAML extension!') end diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index c5b32c29759..d8a61618e77 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when a remote is specified' do let(:params) { { remote: 'http://remote' } } - it 'should return true' do + it 'returns true' do expect(remote_file).to be_matching end end @@ -29,7 +29,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with a missing remote' do let(:params) { { remote: nil } } - it 'should return false' do + it 'returns false' do expect(remote_file).not_to be_matching end end @@ -37,7 +37,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with a missing remote key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(remote_file).not_to be_matching end end @@ -49,7 +49,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_return(body: remote_file_content) end - it 'should return true' do + it 'returns true' do expect(remote_file.valid?).to be_truthy end end @@ -57,7 +57,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with an irregular url' do let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } - it 'should return false' do + it 'returns false' do expect(remote_file.valid?).to be_falsy end end @@ -67,7 +67,7 @@ describe Gitlab::Ci::Config::External::File::Remote do allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error) end - it 'should be falsy' do + it 'is falsy' do expect(remote_file.valid?).to be_falsy end end @@ -75,7 +75,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when is not a yaml file' do let(:location) { 'https://asdasdasdaj48ggerexample.com' } - it 'should be falsy' do + it 'is falsy' do expect(remote_file.valid?).to be_falsy end end @@ -83,7 +83,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with an internal url' do let(:location) { 'http://localhost:8080' } - it 'should be falsy' do + it 'is falsy' do expect(remote_file.valid?).to be_falsy end end @@ -95,7 +95,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_return(body: remote_file_content) end - it 'should return the content of the file' do + it 'returns the content of the file' do expect(remote_file.content).to eql(remote_file_content) end end @@ -105,7 +105,7 @@ describe Gitlab::Ci::Config::External::File::Remote do allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error) end - it 'should be falsy' do + it 'is falsy' do expect(remote_file.content).to be_falsy end end @@ -117,7 +117,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error')) end - it 'should be nil' do + it 'is nil' do expect(remote_file.content).to be_nil end end @@ -125,7 +125,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with an internal url' do let(:location) { 'http://localhost:8080' } - it 'should be nil' do + it 'is nil' do expect(remote_file.content).to be_nil end end @@ -147,7 +147,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_timeout end - it 'should returns error message about a timeout' do + it 'returns error message about a timeout' do expect(subject).to match /could not be fetched because of a timeout error!/ end end @@ -157,7 +157,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error) end - it 'should returns error message about a HTTP error' do + it 'returns error message about a HTTP error' do expect(subject).to match /could not be fetched because of HTTP error!/ end end @@ -167,7 +167,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404) end - it 'should returns error message about a timeout' do + it 'returns error message about a timeout' do expect(subject).to match /could not be fetched because of HTTP code `404` error!/ end end @@ -175,7 +175,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when the URL is blocked' do let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' } - it 'should include details about blocked URL' do + it 'includes details about blocked URL' do expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \ 'is blocked: Requests to localhost are not allowed!' end diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb index 8ecaf4800f8..1609b8fd66b 100644 --- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'when a template is specified' do let(:params) { { template: 'some-template' } } - it 'should return true' do + it 'returns true' do expect(template_file).to be_matching end end @@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with a missing template' do let(:params) { { template: nil } } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_matching end end @@ -32,7 +32,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with a missing template key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_matching end end @@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'when is a valid template name' do let(:template) { 'Auto-DevOps.gitlab-ci.yml' } - it 'should return true' do + it 'returns true' do expect(template_file).to be_valid end end @@ -50,7 +50,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with invalid template name' do let(:template) { 'Template.yml' } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_valid expect(template_file.error_message).to include('Template file `Template.yml` is not a valid location!') end @@ -59,7 +59,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with a non-existing template' do let(:template) { 'I-Do-Not-Have-This-Template.gitlab-ci.yml' } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_valid expect(template_file.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!') end diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 3f6f6d7c5d9..e94bb44f990 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::External::Processor do context 'when no external files defined' do let(:values) { { image: 'ruby:2.2' } } - it 'should return the same values' do + it 'returns the same values' do expect(processor.perform).to eq(values) end end @@ -29,7 +29,7 @@ describe Gitlab::Ci::Config::External::Processor do context 'when an invalid local file is defined' do let(:values) { { include: '/lib/gitlab/ci/templates/non-existent-file.yml', image: 'ruby:2.2' } } - it 'should raise an error' do + it 'raises an error' do expect { processor.perform }.to raise_error( described_class::IncludeError, "Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!" @@ -45,7 +45,7 @@ describe Gitlab::Ci::Config::External::Processor do WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error')) end - it 'should raise an error' do + it 'raises an error' do expect { processor.perform }.to raise_error( described_class::IncludeError, "Remote file `#{remote_file}` could not be fetched because of a socket error!" @@ -78,12 +78,12 @@ describe Gitlab::Ci::Config::External::Processor do WebMock.stub_request(:get, remote_file).to_return(body: external_file_content) end - it 'should append the file to the values' do + it 'appends the file to the values' do output = processor.perform expect(output.keys).to match_array([:image, :before_script, :rspec, :rubocop]) end - it "should remove the 'include' keyword" do + it "removes the 'include' keyword" do expect(processor.perform[:include]).to be_nil end end @@ -105,12 +105,12 @@ describe Gitlab::Ci::Config::External::Processor do .to receive(:fetch_local_content).and_return(local_file_content) end - it 'should append the file to the values' do + it 'appends the file to the values' do output = processor.perform expect(output.keys).to match_array([:image, :before_script]) end - it "should remove the 'include' keyword" do + it "removes the 'include' keyword" do expect(processor.perform[:include]).to be_nil end end @@ -148,11 +148,11 @@ describe Gitlab::Ci::Config::External::Processor do WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) end - it 'should append the files to the values' do + it 'appends the files to the values' do expect(processor.perform.keys).to match_array([:image, :stages, :before_script, :rspec]) end - it "should remove the 'include' keyword" do + it "removes the 'include' keyword" do expect(processor.perform[:include]).to be_nil end end @@ -167,7 +167,7 @@ describe Gitlab::Ci::Config::External::Processor do .to receive(:fetch_local_content).and_return(local_file_content) end - it 'should raise an error' do + it 'raises an error' do expect { processor.perform }.to raise_error( described_class::IncludeError, "Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!" @@ -190,7 +190,7 @@ describe Gitlab::Ci::Config::External::Processor do HEREDOC end - it 'should take precedence' do + it 'takes precedence' do WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) expect(processor.perform[:image]).to eq('ruby:2.2') end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 00b2753c5fc..fd2a29e4ddb 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -225,7 +225,7 @@ describe Gitlab::Ci::Config do end context "when gitlab_ci_yml has valid 'include' defined" do - it 'should return a composed hash' do + it 'returns a composed hash' do before_script_values = [ "apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v", "which ruby", @@ -316,7 +316,7 @@ describe Gitlab::Ci::Config do HEREDOC end - it 'should take precedence' do + it 'takes precedence' do expect(config.to_hash).to eq({ image: 'ruby:2.2' }) end end @@ -341,7 +341,7 @@ describe Gitlab::Ci::Config do HEREDOC end - it 'should merge the variables dictionaries' do + it 'merges the variables dictionaries' do expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } }) end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb index dc13cae961c..c7f4fc98ca3 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do step.perform! end - it 'should break the chain' do + it 'breaks the chain' do expect(step.break?).to be true end @@ -37,11 +37,11 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do step.perform! end - it 'should not break the chain' do + it 'does not break the chain' do expect(step.break?).to be false end - it 'should not skip a pipeline chain' do + it 'does not skip a pipeline chain' do expect(pipeline.reload).not_to be_skipped end end diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index b7924302014..51e5bdc6307 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -150,13 +150,13 @@ describe Gitlab::ContributionsCalendar do end describe '#starting_year' do - it "should be the start of last year" do + it "is the start of last year" do expect(calendar.starting_year).to eq(last_year.year) end end describe '#starting_month' do - it "should be the start of this month" do + it "is the start of this month" do expect(calendar.starting_month).to eq(today.month) end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb index 1d31f96159c..ddd54a669a3 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do describe '#rename_wildcard_paths' do it_behaves_like 'renames child namespaces' - it 'should rename projects' do + it 'renames projects' do rename_projects = double expect(described_class::RenameProjects) .to receive(:new).with(['the-path'], subject) @@ -40,7 +40,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do end describe '#rename_root_paths' do - it 'should rename namespaces' do + it 'renames namespaces' do rename_namespaces = double expect(described_class::RenameNamespaces) .to receive(:new).with(['the-path'], subject) diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb index 256166dbad3..0697594c725 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:diffable) { merge_request.merge_request_diff } end - it 'it uses a different cache key if diff line keys change' do + it 'uses a different cache key if diff line keys change' do mr_diff = described_class.new(merge_request.merge_request_diff, diff_options: nil) key = mr_diff.cache_key diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 4a4ac833e39..507bf222810 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -113,13 +113,13 @@ describe Gitlab::Git::Commit, :seed_helper do context 'Class methods' do shared_examples '.find' do - it "should return first head commit if without params" do + it "returns first head commit if without params" do expect(described_class.last(repository).id).to eq( rugged_repo.head.target.oid ) end - it "should return valid commit" do + it "returns valid commit" do expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_valid_commit end @@ -127,21 +127,21 @@ describe Gitlab::Git::Commit, :seed_helper do expect(described_class.find(repository, SeedRepo::Commit::ID).parent_ids).to be_an(Array) end - it "should return valid commit for tag" do + it "returns valid commit for tag" do expect(described_class.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') end - it "should return nil for non-commit ids" do + it "returns nil for non-commit ids" do blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") expect(described_class.find(repository, blob.id)).to be_nil end - it "should return nil for parent of non-commit object" do + it "returns nil for parent of non-commit object" do blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") expect(described_class.find(repository, "#{blob.id}^")).to be_nil end - it "should return nil for nonexisting ids" do + it "returns nil for nonexisting ids" do expect(described_class.find(repository, "+123_4532530XYZ")).to be_nil end @@ -328,7 +328,7 @@ describe Gitlab::Git::Commit, :seed_helper do end describe '.find_all' do - it 'should return a return a collection of commits' do + it 'returns a return a collection of commits' do commits = described_class.find_all(repository) expect(commits).to all( be_a_kind_of(described_class) ) diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 1d22329b670..9ab669ad488 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -182,7 +182,7 @@ EOT context "without default options" do let(:filtered_options) { described_class.filter_diff_options(options) } - it "should filter invalid options" do + it "filters invalid options" do expect(filtered_options).not_to have_key(:invalid_opt) end end @@ -193,16 +193,16 @@ EOT described_class.filter_diff_options(options, default_options) end - it "should filter invalid options" do + it "filters invalid options" do expect(filtered_options).not_to have_key(:invalid_opt) expect(filtered_options).not_to have_key(:bad_opt) end - it "should merge with default options" do + it "merges with default options" do expect(filtered_options).to have_key(:ignore_whitespace_change) end - it "should override default options" do + it "overrides default options" do expect(filtered_options).to have_key(:max_files) expect(filtered_options[:max_files]).to eq(100) end diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb index 6fd2b33486b..de81dcd227d 100644 --- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb +++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Git::GitmodulesParser do - it 'should parse a .gitmodules file correctly' do + it 'parses a .gitmodules file correctly' do data = <<~GITMODULES [submodule "vendor/libgit2"] path = vendor/libgit2 diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index fc8f590068a..fdb43d1221a 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -450,20 +450,20 @@ describe Gitlab::Git::Repository, :seed_helper do ensure_seeds end - it "should create a new branch" do + it "creates a new branch" do expect(repository.create_branch('new_branch', 'master')).not_to be_nil end - it "should create a new branch with the right name" do + it "creates a new branch with the right name" do expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch') end - it "should fail if we create an existing branch" do + it "fails if we create an existing branch" do repository.create_branch('duplicated_branch', 'master') expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists") end - it "should fail if we create a branch from a non existing ref" do + it "fails if we create a branch from a non existing ref" do expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge") end end @@ -522,7 +522,7 @@ describe Gitlab::Git::Repository, :seed_helper do describe "#refs_hash" do subject { repository.refs_hash } - it "should have as many entries as branches and tags" do + it "has as many entries as branches and tags" do expected_refs = SeedRepo::Repo::BRANCHES + SeedRepo::Repo::TAGS # We flatten in case a commit is pointed at by more than one branch and/or tag expect(subject.values.flatten.size).to eq(expected_refs.size) @@ -613,11 +613,11 @@ describe Gitlab::Git::Repository, :seed_helper do end shared_examples 'search files by content' do - it 'should have 2 items' do + it 'has 2 items' do expect(search_results.size).to eq(2) end - it 'should have the correct matching line' do + it 'has the correct matching line' do expect(search_results).to contain_exactly("search-files-by-content-branch:encoding/CHANGELOG\u00001\u0000search-files-by-content change\n", "search-files-by-content-branch:anotherfile\u00001\u0000search-files-by-content change\n") end @@ -850,7 +850,7 @@ describe Gitlab::Git::Repository, :seed_helper do context "where provides 'after' timestamp" do options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } - it "should returns commits on or after that timestamp" do + it "returns commits on or after that timestamp" do commits = repository.log(options) expect(commits.size).to be > 0 @@ -863,7 +863,7 @@ describe Gitlab::Git::Repository, :seed_helper do context "where provides 'before' timestamp" do options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') } - it "should returns commits on or before that timestamp" do + it "returns commits on or before that timestamp" do commits = repository.log(options) expect(commits.size).to be > 0 @@ -1064,14 +1064,14 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#find_branch' do - it 'should return a Branch for master' do + it 'returns a Branch for master' do branch = repository.find_branch('master') expect(branch).to be_a_kind_of(Gitlab::Git::Branch) expect(branch.name).to eq('master') end - it 'should handle non-existent branch' do + it 'handles non-existent branch' do branch = repository.find_branch('this-is-garbage') expect(branch).to eq(nil) diff --git a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb index 4a669408025..e1106f7496a 100644 --- a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb +++ b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::Kubernetes::ClusterRoleBinding do subject { cluster_role_binding.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/config_map_spec.rb b/spec/lib/gitlab/kubernetes/config_map_spec.rb index fe65d03875f..911d6024804 100644 --- a/spec/lib/gitlab/kubernetes/config_map_spec.rb +++ b/spec/lib/gitlab/kubernetes/config_map_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Kubernetes::ConfigMap do let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: application.files) } subject { config_map.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb index aacae78be43..78a4eb44e38 100644 --- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb @@ -41,7 +41,7 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do describe '#pod_resource' do subject { base_command.pod_resource } - it 'should returns a kubeclient resoure with pod content for application' do + it 'returns a kubeclient resoure with pod content for application' do is_expected.to be_an_instance_of ::Kubeclient::Resource end diff --git a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb index 167bee22fc3..04649353976 100644 --- a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Kubernetes::Helm::Certificate do describe '.generate_root' do subject { described_class.generate_root } - it 'should generate a root CA that expires a long way in the future' do + it 'generates a root CA that expires a long way in the future' do expect(subject.cert.not_after).to be > 999.years.from_now end end @@ -13,14 +13,14 @@ describe Gitlab::Kubernetes::Helm::Certificate do describe '#issue' do subject { described_class.generate_root.issue } - it 'should generate a cert that expires soon' do + it 'generates a cert that expires soon' do expect(subject.cert.not_after).to be < 60.minutes.from_now end context 'passing in INFINITE_EXPIRY' do subject { described_class.generate_root.issue(expires_in: described_class::INFINITE_EXPIRY) } - it 'should generate a cert that expires a long way in the future' do + it 'generates a cert that expires a long way in the future' do expect(subject.cert.not_after).to be > 999.years.from_now end end diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index 95b6b3fd953..06c8d127951 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -10,11 +10,11 @@ describe Gitlab::Kubernetes::Helm::Pod do subject { described_class.new(command, namespace, service_account_name: service_account_name) } context 'with a command' do - it 'should generate a Kubeclient::Resource' do + it 'generates a Kubeclient::Resource' do expect(subject.generate).to be_a_kind_of(Kubeclient::Resource) end - it 'should generate the appropriate metadata' do + it 'generates the appropriate metadata' do metadata = subject.generate.metadata expect(metadata.name).to eq("install-#{app.name}") expect(metadata.namespace).to eq('gitlab-managed-apps') @@ -22,12 +22,12 @@ describe Gitlab::Kubernetes::Helm::Pod do expect(metadata.labels['gitlab.org/application']).to eq(app.name) end - it 'should generate a container spec' do + it 'generates a container spec' do spec = subject.generate.spec expect(spec.containers.count).to eq(1) end - it 'should generate the appropriate specifications for the container' do + it 'generates the appropriate specifications for the container' do container = subject.generate.spec.containers.first expect(container.name).to eq('helm') expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7') @@ -37,30 +37,30 @@ describe Gitlab::Kubernetes::Helm::Pod do expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"]) end - it 'should include a never restart policy' do + it 'includes a never restart policy' do spec = subject.generate.spec expect(spec.restartPolicy).to eq('Never') end - it 'should include volumes for the container' do + it 'includes volumes for the container' do container = subject.generate.spec.containers.first expect(container.volumeMounts.first['name']).to eq('configuration-volume') expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config") end - it 'should include a volume inside the specification' do + it 'includes a volume inside the specification' do spec = subject.generate.spec expect(spec.volumes.first['name']).to eq('configuration-volume') end - it 'should mount configMap specification in the volume' do + it 'mounts configMap specification in the volume' do volume = subject.generate.spec.volumes.first expect(volume.configMap['name']).to eq("values-content-configuration-#{app.name}") expect(volume.configMap['items'].first['key']).to eq(:'values.yaml') expect(volume.configMap['items'].first['path']).to eq(:'values.yaml') end - it 'should have no serviceAccountName' do + it 'has no serviceAccountName' do spec = subject.generate.spec expect(spec.serviceAccountName).to be_nil end @@ -68,7 +68,7 @@ describe Gitlab::Kubernetes::Helm::Pod do context 'with a service_account_name' do let(:service_account_name) { 'sa' } - it 'should use the serviceAccountName provided' do + it 'uses the serviceAccountName provided' do spec = subject.generate.spec expect(spec.serviceAccountName).to eq(service_account_name) end diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb index a1a59533bfb..50acee254cb 100644 --- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb +++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb @@ -42,7 +42,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do ).generate end - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/service_account_spec.rb b/spec/lib/gitlab/kubernetes/service_account_spec.rb index 8da9e932dc3..0d525966d18 100644 --- a/spec/lib/gitlab/kubernetes/service_account_spec.rb +++ b/spec/lib/gitlab/kubernetes/service_account_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::Kubernetes::ServiceAccount do subject { service_account.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb index 0773d3d9aec..0d334bed45f 100644 --- a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb +++ b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::Kubernetes::ServiceAccountToken do subject { service_account_token.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 4b57eecff93..312aa3be490 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -97,7 +97,7 @@ describe Gitlab::SearchResults do results.objects('merge_requests') end - it 'it skips project filter if default project context is used' do + it 'skips project filter if default project context is used' do allow(results).to receive(:default_project_filter).and_return(true) expect(results).not_to receive(:project_ids_relation) @@ -113,7 +113,7 @@ describe Gitlab::SearchResults do results.objects('issues') end - it 'it skips project filter if default project context is used' do + it 'skips project filter if default project context is used' do allow(results).to receive(:default_project_filter).and_return(true) expect(results).not_to receive(:project_ids_relation) diff --git a/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb b/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb index c9d1a06b3e6..0bbaf5968ed 100644 --- a/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb +++ b/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb @@ -7,19 +7,19 @@ describe Gitlab::Tracing::Rails::ActionViewSubscriber do using RSpec::Parameterized::TableSyntax shared_examples 'an actionview notification' do - it 'should notify the tracer when the hash contains null values' do + it 'notifies the tracer when the hash contains null values' do expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception) subject.public_send(notify_method, start, finish, payload) end - it 'should notify the tracer when the payload is missing values' do + it 'notifies the tracer when the payload is missing values' do expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception) subject.public_send(notify_method, start, finish, payload.compact) end - it 'should not throw exceptions when with the default tracer' do + it 'does not throw exceptions when with the default tracer' do expect { subject.public_send(notify_method, start, finish, payload) }.not_to raise_error end end diff --git a/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb b/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb index 3d066843148..7bd0875fa68 100644 --- a/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb +++ b/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb @@ -53,19 +53,19 @@ describe Gitlab::Tracing::Rails::ActiveRecordSubscriber do } end - it 'should notify the tracer when the hash contains null values' do + it 'notifies the tracer when the hash contains null values' do expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception) subject.notify(start, finish, payload) end - it 'should notify the tracer when the payload is missing values' do + it 'notifies the tracer when the payload is missing values' do expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception) subject.notify(start, finish, payload.compact) end - it 'should not throw exceptions when with the default tracer' do + it 'does not throw exceptions when with the default tracer' do expect { subject.notify(start, finish, payload) }.not_to raise_error end end diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb index 566b5050e47..db75ce2a998 100644 --- a/spec/lib/gitlab/tracing_spec.rb +++ b/spec/lib/gitlab/tracing_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Tracing do end with_them do - it 'should return the correct state for .enabled?' do + it 'returns the correct state for .enabled?' do expect(described_class).to receive(:connection_string).and_return(connection_string) expect(described_class.enabled?).to eq(enabled_state) @@ -33,7 +33,7 @@ describe Gitlab::Tracing do end with_them do - it 'should return the correct state for .tracing_url_enabled?' do + it 'returns the correct state for .tracing_url_enabled?' do expect(described_class).to receive(:enabled?).and_return(enabled?) allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template) @@ -56,7 +56,7 @@ describe Gitlab::Tracing do end with_them do - it 'should return the correct state for .tracing_url' do + it 'returns the correct state for .tracing_url' do expect(described_class).to receive(:tracing_url_enabled?).and_return(tracing_url_enabled?) allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template) allow(Gitlab::CorrelationId).to receive(:current_id).and_return(correlation_id) diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 6e98a999766..5861e6955a6 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -161,7 +161,7 @@ describe Gitlab::UrlSanitizer do end context 'when credentials contains special chars' do - it 'should parse the URL without errors' do + it 'parses the URL without errors' do url_sanitizer = described_class.new("https://foo:b?r@github.com/me/project.git") expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb index b1ff3cfd355..349cffea70e 100644 --- a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb +++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb @@ -25,7 +25,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do context 'with ProjectAutoDevOps with no domain' do let(:domain) { nil } - it 'should not update cluster project' do + it 'does not update cluster project' do migrate! expect(clusters_without_domain.count).to eq(clusters_table.count) @@ -35,7 +35,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do context 'with ProjectAutoDevOps with domain' do let(:domain) { 'example-domain.com' } - it 'should update all cluster projects' do + it 'updates all cluster projects' do migrate! expect(clusters_with_domain.count).to eq(clusters_table.count) @@ -49,7 +49,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do setup_cluster_projects_with_domain(quantity: 25, domain: nil) end - it 'should only update specific cluster projects' do + it 'only updates specific cluster projects' do migrate! expect(clusters_with_domain.count).to eq(20) diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb index 314d7d1e9f4..c661f5384ea 100644 --- a/spec/models/badge_spec.rb +++ b/spec/models/badge_spec.rb @@ -61,7 +61,7 @@ describe Badge do end shared_examples 'rendered_links' do - it 'should use the project information to populate the url placeholders' do + it 'uses the project information to populate the url placeholders' do stub_project_commit_info(project) expect(badge.public_send("rendered_#{method}", project)).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever" diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb index e683d110252..d41c5cf2ca1 100644 --- a/spec/models/badges/project_badge_spec.rb +++ b/spec/models/badges/project_badge_spec.rb @@ -14,7 +14,7 @@ describe ProjectBadge do end shared_examples 'rendered_links' do - it 'should use the badge project information to populate the url placeholders' do + it 'uses the badge project information to populate the url placeholders' do stub_project_commit_info(project) expect(badge.public_send("rendered_#{method}")).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever" diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 697fe3fda06..b9ece6a394a 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2726,13 +2726,13 @@ describe Ci::Build do project.deploy_tokens << deploy_token end - it 'should include deploy token variables' do + it 'includes deploy token variables' do is_expected.to include(*deploy_token_variables) end end context 'when gitlab-deploy-token does not exist' do - it 'should not include deploy token variables' do + it 'does not include deploy token variables' do expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index b3ab63925dd..2cb581696a0 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -72,7 +72,7 @@ describe Ci::Runner do expect(instance_runner.errors.full_messages).to include('Runner cannot have projects assigned') end - it 'should fail to save a group assigned to a project runner even if the runner is already saved' do + it 'fails to save a group assigned to a project runner even if the runner is already saved' do group_runner expect { create(:group, runners: [project_runner]) } diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index af7eadfc74c..5cd80edb3a1 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -36,7 +36,7 @@ describe Clusters::Applications::CertManager do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with cert_manager arguments' do + it 'is initialized with cert_manager arguments' do expect(subject.name).to eq('certmanager') expect(subject.chart).to eq('stable/cert-manager') expect(subject.version).to eq('v0.5.2') @@ -52,7 +52,7 @@ describe Clusters::Applications::CertManager do cert_manager.email = cert_email end - it 'should use his/her email to register issuer with certificate provider' do + it 'uses his/her email to register issuer with certificate provider' do expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file)) end end @@ -68,7 +68,7 @@ describe Clusters::Applications::CertManager do context 'application failed to install previously' do let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('v0.5.2') end end @@ -80,7 +80,7 @@ describe Clusters::Applications::CertManager do subject { application.files } - it 'should include cert_manager specific keys in the values.yaml file' do + it 'includes cert_manager specific keys in the values.yaml file' do expect(values).to include('ingressShim') end end diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index f97d126d918..f177d493a2e 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -36,11 +36,11 @@ describe Clusters::Applications::Helm do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InitCommand) } - it 'should be initialized with 1 arguments' do + it 'is initialized with 1 arguments' do expect(subject.name).to eq('helm') end - it 'should have cert files' do + it 'has cert files' do expect(subject.files[:'ca.pem']).to be_present expect(subject.files[:'ca.pem']).to eq(helm.ca_cert) diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 09e60b9a206..113d29b5551 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -73,7 +73,7 @@ describe Clusters::Applications::Ingress do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with ingress arguments' do + it 'is initialized with ingress arguments' do expect(subject.name).to eq('ingress') expect(subject.chart).to eq('stable/nginx-ingress') expect(subject.version).to eq('1.1.2') @@ -92,7 +92,7 @@ describe Clusters::Applications::Ingress do context 'application failed to install previously' do let(:ingress) { create(:clusters_applications_ingress, :errored, version: 'nginx') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('1.1.2') end end @@ -104,7 +104,7 @@ describe Clusters::Applications::Ingress do subject { application.files } - it 'should include ingress valid keys in values' do + it 'includes ingress valid keys in values' do expect(values).to include('image') expect(values).to include('repository') expect(values).to include('stats') diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 5970a1959b5..1a7363b64f9 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -45,7 +45,7 @@ describe Clusters::Applications::Jupyter do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with 4 arguments' do + it 'is initialized with 4 arguments' do expect(subject.name).to eq('jupyter') expect(subject.chart).to eq('jupyter/jupyterhub') expect(subject.version).to eq('0.9-174bbd5') @@ -65,7 +65,7 @@ describe Clusters::Applications::Jupyter do context 'application failed to install previously' do let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('0.9-174bbd5') end end @@ -77,7 +77,7 @@ describe Clusters::Applications::Jupyter do subject { application.files } - it 'should include valid values' do + it 'includes valid values' do expect(values).to include('ingress') expect(values).to include('hub') expect(values).to include('rbac') diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 25493689fbc..5e68f2634da 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -77,17 +77,17 @@ describe Clusters::Applications::Knative do end shared_examples 'a command' do - it 'should be an instance of Helm::InstallCommand' do + it 'is an instance of Helm::InstallCommand' do expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) end - it 'should be initialized with knative arguments' do + it 'is initialized with knative arguments' do expect(subject.name).to eq('knative') expect(subject.chart).to eq('knative/knative') expect(subject.files).to eq(knative.files) end - it 'should not install metrics for prometheus' do + it 'does not install metrics for prometheus' do expect(subject.postinstall).to be_nil end @@ -97,7 +97,7 @@ describe Clusters::Applications::Knative do subject { knative.install_command } - it 'should install metrics' do + it 'installs metrics' do expect(subject.postinstall).not_to be_nil expect(subject.postinstall.length).to be(1) expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") @@ -108,7 +108,7 @@ describe Clusters::Applications::Knative do describe '#install_command' do subject { knative.install_command } - it 'should be initialized with latest version' do + it 'is initialized with latest version' do expect(subject.version).to eq('0.3.0') end @@ -119,7 +119,7 @@ describe Clusters::Applications::Knative do let!(:current_installed_version) { knative.version = '0.1.0' } subject { knative.update_command } - it 'should be initialized with current version' do + it 'is initialized with current version' do expect(subject.version).to eq(current_installed_version) end @@ -132,7 +132,7 @@ describe Clusters::Applications::Knative do subject { application.files } - it 'should include knative specific keys in the values.yaml file' do + it 'includes knative specific keys in the values.yaml file' do expect(values).to include('domain') end end @@ -165,7 +165,7 @@ describe Clusters::Applications::Knative do synchronous_reactive_cache(knative) end - it 'should be able k8s core for pod details' do + it 'is able k8s core for pod details' do expect(knative.service_pod_details(namespace.namespace, cluster.cluster_project.project.name)).not_to be_nil end end @@ -190,7 +190,7 @@ describe Clusters::Applications::Knative do stub_kubeclient_service_pods end - it 'should have an unintialized cache' do + it 'has an unintialized cache' do is_expected.to be_nil end @@ -204,11 +204,11 @@ describe Clusters::Applications::Knative do synchronous_reactive_cache(knative) end - it 'should have cached services' do + it 'has cached services' do is_expected.not_to be_nil end - it 'should match our namespace' do + it 'matches our namespace' do expect(knative.services_for(ns: namespace)).not_to be_nil end end diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 82a502addd4..e8ba9737c23 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -94,7 +94,7 @@ describe Clusters::Applications::Prometheus do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with 3 arguments' do + it 'is initialized with 3 arguments' do expect(subject.name).to eq('prometheus') expect(subject.chart).to eq('stable/prometheus') expect(subject.version).to eq('6.7.3') @@ -113,12 +113,12 @@ describe Clusters::Applications::Prometheus do context 'application failed to install previously' do let(:prometheus) { create(:clusters_applications_prometheus, :errored, version: '2.0.0') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('6.7.3') end end - it 'should not install knative metrics' do + it 'does not install knative metrics' do expect(subject.postinstall).to be_nil end @@ -128,7 +128,7 @@ describe Clusters::Applications::Prometheus do subject { prometheus.install_command } - it 'should install knative metrics' do + it 'installs knative metrics' do expect(subject.postinstall).to include("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") end end @@ -142,7 +142,7 @@ describe Clusters::Applications::Prometheus do expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::InstallCommand) end - it 'should be initialized with 3 arguments' do + it 'is initialized with 3 arguments' do command = prometheus.upgrade_command(values) expect(command.name).to eq('prometheus') @@ -180,7 +180,7 @@ describe Clusters::Applications::Prometheus do subject { application.files } - it 'should include prometheus valid values' do + it 'includes prometheus valid values' do expect(values).to include('alertmanager') expect(values).to include('kubeStateMetrics') expect(values).to include('nodeExporter') @@ -204,7 +204,7 @@ describe Clusters::Applications::Prometheus do expect(subject[:'values.yaml']).to eq({ hello: :world }) end - it 'should include cert files' do + it 'includes cert files' do expect(subject[:'ca.pem']).to be_present expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) @@ -220,7 +220,7 @@ describe Clusters::Applications::Prometheus do application.cluster.application_helm.ca_cert = nil end - it 'should not include cert files' do + it 'does not include cert files' do expect(subject[:'ca.pem']).not_to be_present expect(subject[:'cert.pem']).not_to be_present expect(subject[:'key.pem']).not_to be_present diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 7e2f5835279..399a13f82cb 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -21,7 +21,7 @@ describe Clusters::Applications::Runner do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with 4 arguments' do + it 'is initialized with 4 arguments' do expect(subject.name).to eq('runner') expect(subject.chart).to eq('runner/gitlab-runner') expect(subject.version).to eq('0.3.0') @@ -41,7 +41,7 @@ describe Clusters::Applications::Runner do context 'application failed to install previously' do let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('0.3.0') end end @@ -53,7 +53,7 @@ describe Clusters::Applications::Runner do subject { application.files } - it 'should include runner valid values' do + it 'includes runner valid values' do expect(values).to include('concurrent') expect(values).to include('checkInterval') expect(values).to include('rbac') @@ -131,7 +131,7 @@ describe Clusters::Applications::Runner do allow(application).to receive(:chart_values).and_return(stub_values) end - it 'should overwrite values.yaml' do + it 'overwrites values.yaml' do expect(values).to match(/privileged: '?#{application.privileged}/) end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index fabd2806d9a..894ef3fb956 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -269,7 +269,7 @@ describe Clusters::Cluster do context 'when cluster is not a valid hostname' do let(:cluster) { build(:cluster, domain: 'http://not.a.valid.hostname') } - it 'should add an error on domain' do + it 'adds an error on domain' do expect(subject).not_to be_valid expect(subject.errors[:domain].first).to eq('contains invalid characters (valid characters: [a-z0-9\\-])') end @@ -599,7 +599,7 @@ describe Clusters::Cluster do stub_application_setting(auto_devops_domain: 'global_domain.com') end - it 'should include KUBE_INGRESS_BASE_DOMAIN' do + it 'includes KUBE_INGRESS_BASE_DOMAIN' do expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'global_domain.com') end end @@ -607,7 +607,7 @@ describe Clusters::Cluster do context 'with a cluster domain' do let(:cluster) { create(:cluster, :provided_by_gcp, domain: 'example.com') } - it 'should include KUBE_INGRESS_BASE_DOMAIN' do + it 'includes KUBE_INGRESS_BASE_DOMAIN' do expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com') end end @@ -615,7 +615,7 @@ describe Clusters::Cluster do context 'with no domain' do let(:cluster) { create(:cluster, :provided_by_gcp, :project) } - it 'should return an empty array' do + it 'returns an empty array' do expect(subject.to_hash).to be_empty end end diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index 579f486f99f..b5cba80b806 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do context 'when platform has a namespace assigned' do let(:namespace) { 'platform-namespace' } - it 'should copy the namespace' do + it 'copies the namespace' do subject expect(kubernetes_namespace.namespace).to eq('platform-namespace') @@ -72,7 +72,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do let(:namespace) { nil } let(:project_slug) { "#{project.path}-#{project.id}" } - it 'should fallback to project namespace' do + it 'fallbacks to project namespace' do subject expect(kubernetes_namespace.namespace).to eq(project_slug) @@ -83,7 +83,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do describe '#service_account_name' do let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" } - it 'should set a service account name based on namespace' do + it 'sets a service account name based on namespace' do subject expect(kubernetes_namespace.service_account_name).to eq(service_account_name) diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 14bec17a2bd..0281dd2c303 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -447,7 +447,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching let(:platform) { cluster.platform } context 'when namespace is updated' do - it 'should call ConfigureWorker' do + it 'calls ConfigureWorker' do expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once platform.namespace = 'new-namespace' @@ -456,7 +456,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end context 'when namespace is not updated' do - it 'should not call ConfigureWorker' do + it 'does not call ConfigureWorker' do expect(ClusterConfigureWorker).not_to receive(:perform_async) platform.username = "new-username" diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 259ac6852a8..27ed298ae08 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -659,7 +659,7 @@ describe Issuable do end context 'adding time' do - it 'should update the total time spent' do + it 'updates the total time spent' do spend_time(1800) expect(issue.total_time_spent).to eq(1800) @@ -679,7 +679,7 @@ describe Issuable do spend_time(1800) end - it 'should update the total time spent' do + it 'updates the total time spent' do spend_time(-900) expect(issue.total_time_spent).to eq(900) diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb index 650d49e41a1..67353475251 100644 --- a/spec/models/concerns/spammable_spec.rb +++ b/spec/models/concerns/spammable_spec.rb @@ -12,7 +12,7 @@ describe Spammable do end describe 'ClassMethods' do - it 'should return correct attr_spammable' do + it 'returns correct attr_spammable' do expect(issue.spammable_text).to eq("#{issue.title}\n#{issue.description}") end end @@ -20,7 +20,7 @@ describe Spammable do describe 'InstanceMethods' do let(:issue) { build(:issue, spam: true) } - it 'should be invalid if spam' do + it 'is invalid if spam' do expect(issue.valid?).to be_falsey end diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 05320703e25..2fe82eaa778 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -9,7 +9,7 @@ describe DeployToken do it { is_expected.to have_many(:projects).through(:project_deploy_tokens) } describe '#ensure_token' do - it 'should ensure a token' do + it 'ensures a token' do deploy_token.token = nil deploy_token.save @@ -19,13 +19,13 @@ describe DeployToken do describe '#ensure_at_least_one_scope' do context 'with at least one scope' do - it 'should be valid' do + it 'is valid' do is_expected.to be_valid end end context 'with no scopes' do - it 'should be invalid' do + it 'is invalid' do deploy_token = build(:deploy_token, read_repository: false, read_registry: false) expect(deploy_token).not_to be_valid @@ -36,13 +36,13 @@ describe DeployToken do describe '#scopes' do context 'with all the scopes' do - it 'should return scopes assigned to DeployToken' do + it 'returns scopes assigned to DeployToken' do expect(deploy_token.scopes).to eq([:read_repository, :read_registry]) end end context 'with only one scope' do - it 'should return scopes assigned to DeployToken' do + it 'returns scopes assigned to DeployToken' do deploy_token = create(:deploy_token, read_registry: false) expect(deploy_token.scopes).to eq([:read_repository]) end @@ -50,7 +50,7 @@ describe DeployToken do end describe '#revoke!' do - it 'should update revoke attribute' do + it 'updates revoke attribute' do deploy_token.revoke! expect(deploy_token.revoked?).to be_truthy end @@ -58,20 +58,20 @@ describe DeployToken do describe "#active?" do context "when it has been revoked" do - it 'should return false' do + it 'returns false' do deploy_token.revoke! expect(deploy_token.active?).to be_falsy end end context "when it hasn't been revoked and is not expired" do - it 'should return true' do + it 'returns true' do expect(deploy_token.active?).to be_truthy end end context "when it hasn't been revoked and is expired" do - it 'should return true' do + it 'returns true' do deploy_token.update_attribute(:expires_at, Date.today - 5.days) expect(deploy_token.active?).to be_falsy end @@ -80,7 +80,7 @@ describe DeployToken do context "when it hasn't been revoked and has no expiry" do let(:deploy_token) { create(:deploy_token, expires_at: nil) } - it 'should return true' do + it 'returns true' do expect(deploy_token.active?).to be_truthy end end @@ -126,7 +126,7 @@ describe DeployToken do context 'when using Forever.date' do let(:deploy_token) { create(:deploy_token, expires_at: nil) } - it 'should return nil' do + it 'returns nil' do expect(deploy_token.expires_at).to be_nil end end @@ -135,7 +135,7 @@ describe DeployToken do let(:expires_at) { Date.today + 5.months } let(:deploy_token) { create(:deploy_token, expires_at: expires_at) } - it 'should return the personalized date' do + it 'returns the personalized date' do expect(deploy_token.expires_at).to eq(expires_at) end end @@ -145,7 +145,7 @@ describe DeployToken do context 'when passing nil' do let(:deploy_token) { create(:deploy_token, expires_at: nil) } - it 'should assign Forever.date' do + it 'assigns Forever.date' do expect(deploy_token.read_attribute(:expires_at)).to eq(Forever.date) end end @@ -154,7 +154,7 @@ describe DeployToken do let(:expires_at) { Date.today + 5.months } let(:deploy_token) { create(:deploy_token, expires_at: expires_at) } - it 'should respect the value' do + it 'respects the value' do expect(deploy_token.read_attribute(:expires_at)).to eq(expires_at) end end @@ -166,14 +166,14 @@ describe DeployToken do subject { project.deploy_tokens.gitlab_deploy_token } context 'with a gitlab deploy token associated' do - it 'should return the gitlab deploy token' do + it 'returns the gitlab deploy token' do deploy_token = create(:deploy_token, :gitlab_deploy_token, projects: [project]) is_expected.to eq(deploy_token) end end context 'with no gitlab deploy token associated' do - it 'should return nil' do + it 'returns nil' do is_expected.to be_nil end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index b2ffd5812ab..ad3e3061b9a 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -788,14 +788,14 @@ describe Group do describe '#has_parent?' do context 'when the group has a parent' do - it 'should be truthy' do + it 'is truthy' do group = create(:group, :nested) expect(group.has_parent?).to be_truthy end end context 'when the group has no parent' do - it 'should be falsy' do + it 'is falsy' do group = create(:group, parent: nil) expect(group.has_parent?).to be_falsy end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 62e7dd3231b..387d1221c76 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -740,14 +740,14 @@ describe Namespace do describe '#full_path_was' do context 'when the group has no parent' do - it 'should return the path was' do + it 'returns the path was' do group = create(:group, parent: nil) expect(group.full_path_was).to eq(group.path_was) end end context 'when a parent is assigned to a group with no previous parent' do - it 'should return the path was' do + it 'returns the path was' do group = create(:group, parent: nil) parent = create(:group) @@ -758,7 +758,7 @@ describe Namespace do end context 'when a parent is removed from the group' do - it 'should return the parent full path' do + it 'returns the parent full path' do parent = create(:group) group = create(:group, parent: parent) group.parent = nil @@ -768,7 +768,7 @@ describe Namespace do end context 'when changing parents' do - it 'should return the previous parent full path' do + it 'returns the previous parent full path' do parent = create(:group) group = create(:group, parent: parent) new_parent = create(:group) diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index d1a2bedf542..232172fde76 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -22,7 +22,7 @@ describe Network::Graph do expect(commits).to all( be_kind_of(Network::Commit) ) end - it 'it the commits by commit date (descending)' do + it 'sorts commits by commit date (descending)' do # Remove duplicate timestamps because they make it harder to # assert that the commits are sorted as expected. commits = graph.commits.uniq(&:date) diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 8ad28ce68cc..b81e5610e2c 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -117,7 +117,7 @@ describe ProjectAutoDevops do context 'when the project is public' do let(:project) { create(:project, :repository, :public) } - it 'should not create a gitlab deploy token' do + it 'does not create a gitlab deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } @@ -127,7 +127,7 @@ describe ProjectAutoDevops do context 'when the project is internal' do let(:project) { create(:project, :repository, :internal) } - it 'should create a gitlab deploy token' do + it 'creates a gitlab deploy token' do expect do auto_devops.save end.to change { DeployToken.count }.by(1) @@ -137,7 +137,7 @@ describe ProjectAutoDevops do context 'when the project is private' do let(:project) { create(:project, :repository, :private) } - it 'should create a gitlab deploy token' do + it 'creates a gitlab deploy token' do expect do auto_devops.save end.to change { DeployToken.count }.by(1) @@ -148,7 +148,7 @@ describe ProjectAutoDevops do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'should create a deploy token' do + it 'creates a deploy token' do expect do auto_devops.save end.to change { DeployToken.count }.by(1) @@ -159,7 +159,7 @@ describe ProjectAutoDevops do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, enabled: nil, project: project) } - it 'should create a deploy token' do + it 'creates a deploy token' do allow(Gitlab::CurrentSettings).to receive(:auto_devops_enabled?).and_return(true) expect do @@ -172,7 +172,7 @@ describe ProjectAutoDevops do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) } - it 'should not create a deploy token' do + it 'does not create a deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } @@ -184,7 +184,7 @@ describe ProjectAutoDevops do let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'should not create a deploy token' do + it 'does not create a deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } @@ -196,7 +196,7 @@ describe ProjectAutoDevops do let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :expired, projects: [project]) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'should not create a deploy token' do + it 'does not create a deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 7bf093b71e7..3a381cb405d 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -70,11 +70,11 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do kubernetes_service.properties['namespace'] = "foo" end - it 'should not update attributes' do + it 'does not update attributes' do expect(kubernetes_service.save).to be_falsy end - it 'should include an error with a deprecation message' do + it 'includes an error with a deprecation message' do kubernetes_service.valid? expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been deprecated/) end @@ -83,7 +83,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'with a non-deprecated service' do let(:kubernetes_service) { create(:kubernetes_service) } - it 'should update attributes' do + it 'updates attributes' do kubernetes_service.properties['namespace'] = 'foo' expect(kubernetes_service.save).to be_truthy end @@ -98,15 +98,15 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do kubernetes_service.save end - it 'should deactive the service' do + it 'deactivates the service' do expect(kubernetes_service.active?).to be_falsy end - it 'should not include a deprecation message as error' do + it 'does not include a deprecation message as error' do expect(kubernetes_service.errors.messages.count).to eq(0) end - it 'should update attributes' do + it 'updates attributes' do expect(kubernetes_service.properties['namespace']).to eq("foo") end end @@ -118,7 +118,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do kubernetes_service.properties['namespace'] = 'foo' end - it 'should update attributes' do + it 'updates attributes' do expect(kubernetes_service.save).to be_truthy expect(kubernetes_service.properties['namespace']).to eq('foo') end @@ -392,13 +392,13 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do let(:kubernetes_service) { create(:kubernetes_service) } context 'with an active kubernetes service' do - it 'should return false' do + it 'returns false' do expect(kubernetes_service.deprecated?).to be_falsy end end context 'with a inactive kubernetes service' do - it 'should return true' do + it 'returns true' do kubernetes_service.update_attribute(:active, false) expect(kubernetes_service.deprecated?).to be_truthy end @@ -408,18 +408,18 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do describe "#deprecation_message" do let(:kubernetes_service) { create(:kubernetes_service) } - it 'should indicate the service is deprecated' do + it 'indicates the service is deprecated' do expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been deprecated/) end context 'if the services is active' do - it 'should return a message' do + it 'returns a message' do expect(kubernetes_service.deprecation_message).to match(/Your Kubernetes cluster information on this page is still editable/) end end context 'if the service is not active' do - it 'should return a message' do + it 'returns a message' do kubernetes_service.update_attribute(:active, false) expect(kubernetes_service.deprecation_message).to match(/Fields on this page are now uneditable/) end diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index de2c8790405..773b8b7890f 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -56,7 +56,7 @@ describe PivotaltrackerService do WebMock.stub_request(:post, url) end - it 'should post correct message' do + it 'posts correct message' do service.execute(push_data) expect(WebMock).to have_requested(:post, url).with( body: { @@ -81,14 +81,14 @@ describe PivotaltrackerService do end end - it 'should post message if branch is in the list' do + it 'posts message if branch is in the list' do service.execute(push_data(branch: 'master')) service.execute(push_data(branch: 'v10')) expect(WebMock).to have_requested(:post, url).twice end - it 'should not post message if branch is not in the list' do + it 'does not post message if branch is not in the list' do service.execute(push_data(branch: 'mas')) service.execute(push_data(branch: 'v11')) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 33e514cd7b9..5eb31430ccd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -415,7 +415,7 @@ describe Project do project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) end - it 'should return .external pipelines' do + it 'returns .external pipelines' do expect(project.all_pipelines).to all(have_attributes(source: 'external')) expect(project.all_pipelines.size).to eq(1) end @@ -439,7 +439,7 @@ describe Project do project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) end - it 'should return .external pipelines' do + it 'returns .external pipelines' do expect(project.ci_pipelines).to all(have_attributes(source: 'external')) expect(project.ci_pipelines.size).to eq(1) end @@ -1910,7 +1910,7 @@ describe Project do tags: %w[latest rc1]) end - it 'should have image tags' do + it 'has image tags' do expect(project).to have_container_registry_tags end end @@ -1921,7 +1921,7 @@ describe Project do tags: %w[latest rc1 pre1]) end - it 'should have image tags' do + it 'has image tags' do expect(project).to have_container_registry_tags end end @@ -1931,7 +1931,7 @@ describe Project do stub_container_registry_tags(repository: :any, tags: []) end - it 'should not have image tags' do + it 'does not have image tags' do expect(project).not_to have_container_registry_tags end end @@ -1942,16 +1942,16 @@ describe Project do stub_container_registry_config(enabled: false) end - it 'should not have image tags' do + it 'does not have image tags' do expect(project).not_to have_container_registry_tags end - it 'should not check root repository tags' do + it 'does not check root repository tags' do expect(project).not_to receive(:full_path) expect(project).not_to have_container_registry_tags end - it 'should iterate through container repositories' do + it 'iterates through container repositories' do expect(project).to receive(:container_repositories) expect(project).not_to have_container_registry_tags end @@ -2638,7 +2638,7 @@ describe Project do let!(:cluster) { kubernetes_namespace.cluster } let(:project) { kubernetes_namespace.project } - it 'should return token from kubernetes namespace' do + it 'returns token from kubernetes namespace' do expect(project.deployment_variables).to include( { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } ) diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 0478094034a..f743dfed31f 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -7,14 +7,14 @@ describe RemoteMirror, :mailer do describe 'URL validation' do context 'with a valid URL' do - it 'should be valid' do + it 'is valid' do remote_mirror = build(:remote_mirror) expect(remote_mirror).to be_valid end end context 'with an invalid URL' do - it 'should not be valid' do + it 'is not valid' do remote_mirror = build(:remote_mirror, url: 'ftp://invalid.invalid') expect(remote_mirror).not_to be_valid diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 2578208659a..3f5d285bc2c 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -50,7 +50,7 @@ describe Repository do it { is_expected.not_to include('fix') } describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.branch_names_contains(sample_commit.id) end @@ -225,7 +225,7 @@ describe Repository do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore') end @@ -249,7 +249,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id end @@ -390,7 +390,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') } end end @@ -726,7 +726,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.search_files_by_content('feature', 'master') end @@ -775,7 +775,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error { broken_repository.search_files_by_name('files', 'master') } end end @@ -817,7 +817,7 @@ describe Repository do let(:broken_repository) { create(:project, :broken_storage).repository } describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.fetch_ref(broken_repository, source_ref: '1', target_ref: '2') end @@ -1018,7 +1018,7 @@ describe Repository do repository.add_branch(project.creator, ref, 'master') end - it 'should be true' do + it 'is true' do is_expected.to eq(true) end end @@ -1028,7 +1028,7 @@ describe Repository do repository.add_tag(project.creator, ref, 'master') end - it 'should be false' do + it 'is false' do is_expected.to eq(false) end end @@ -1152,7 +1152,7 @@ describe Repository do end context 'with broken storage', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error { broken_repository.exists? } end end @@ -2249,11 +2249,11 @@ describe Repository do let(:commit) { repository.commit } let(:ancestor) { commit.parents.first } - it 'it is an ancestor' do + it 'is an ancestor' do expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true) end - it 'it is not an ancestor' do + it 'is not an ancestor' do expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false) end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 2f025038bab..64db32781fe 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -291,7 +291,7 @@ describe Service do describe "#deprecated?" do let(:project) { create(:project, :repository) } - it 'should return false by default' do + it 'returns false by default' do service = create(:service, project: project) expect(service.deprecated?).to be_falsy end @@ -300,7 +300,7 @@ describe Service do describe "#deprecation_message" do let(:project) { create(:project, :repository) } - it 'should be empty by default' do + it 'is empty by default' do service = create(:service, project: project) expect(service.deprecation_message).to be_nil end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 676835b3880..e202f7a9b5f 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -180,7 +180,7 @@ describe Ci::BuildPresenter do context 'When build has failed and retried' do let(:build) { create(:ci_build, :script_failure, :retried, pipeline: pipeline) } - it 'should include the reason of failure and the retried title' do + it 'includes the reason of failure and the retried title' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - failed - (script failure) (retried)") @@ -190,7 +190,7 @@ describe Ci::BuildPresenter do context 'When build has failed and is allowed to' do let(:build) { create(:ci_build, :script_failure, :allowed_to_fail, pipeline: pipeline) } - it 'should include the reason of failure' do + it 'includes the reason of failure' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - failed - (script failure) (allowed to fail)") @@ -200,7 +200,7 @@ describe Ci::BuildPresenter do context 'For any other build (no retried)' do let(:build) { create(:ci_build, :success, pipeline: pipeline) } - it 'should include build name and status' do + it 'includes build name and status' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - passed") @@ -210,7 +210,7 @@ describe Ci::BuildPresenter do context 'For any other build (retried)' do let(:build) { create(:ci_build, :success, :retried, pipeline: pipeline) } - it 'should include build name and status' do + it 'includes build name and status' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - passed (retried)") @@ -269,7 +269,7 @@ describe Ci::BuildPresenter do context 'when is a script or missing dependency failure' do let(:failure_reasons) { %w(script_failure missing_dependency_failure archived_failure) } - it 'should return false' do + it 'returns false' do failure_reasons.each do |failure_reason| build.update_attribute(:failure_reason, failure_reason) expect(presenter.recoverable?).to be_falsy @@ -280,7 +280,7 @@ describe Ci::BuildPresenter do context 'when is any other failure type' do let(:failure_reasons) { %w(unknown_failure api_failure stuck_or_timeout_failure runner_system_failure) } - it 'should return true' do + it 'returns true' do failure_reasons.each do |failure_reason| build.update_attribute(:failure_reason, failure_reason) expect(presenter.recoverable?).to be_truthy diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index c26d31c5e0d..9fed07cae82 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -435,7 +435,7 @@ describe API::Pipelines do end context 'unauthorized user' do - it 'should not return a project pipeline' do + it 'does not return a project pipeline' do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) expect(response).to have_gitlab_http_status(404) @@ -481,7 +481,7 @@ describe API::Pipelines do context 'unauthorized user' do context 'when user is not member' do - it 'should return a 404' do + it 'returns a 404' do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) expect(response).to have_gitlab_http_status(404) @@ -496,7 +496,7 @@ describe API::Pipelines do project.add_developer(developer) end - it 'should return a 403' do + it 'returns a 403' do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", developer) expect(response).to have_gitlab_http_status(403) @@ -526,7 +526,7 @@ describe API::Pipelines do end context 'unauthorized user' do - it 'should not return a project pipeline' do + it 'does not return a project pipeline' do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member) expect(response).to have_gitlab_http_status(404) diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 81442125a1c..94e6ca2c07c 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -22,7 +22,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do get api("/projects/#{project.id}/clusters", non_member) expect(response).to have_gitlab_http_status(404) @@ -34,15 +34,15 @@ describe API::ProjectClusters do get api("/projects/#{project.id}/clusters", current_user) end - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end - it 'should include pagination headers' do + it 'includes pagination headers' do expect(response).to include_pagination_headers end - it 'should only include authorized clusters' do + it 'onlies include authorized clusters' do cluster_ids = json_response.map { |cluster| cluster['id'] } expect(cluster_ids).to match_array(clusters.pluck(:id)) @@ -67,7 +67,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do get api("/projects/#{project.id}/clusters/#{cluster_id}", non_member) expect(response).to have_gitlab_http_status(404) @@ -132,7 +132,7 @@ describe API::ProjectClusters do projects: [project]) end - it 'should not include GCP provider info' do + it 'does not include GCP provider info' do expect(json_response['provider_gcp']).not_to be_present end end @@ -194,7 +194,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do post api("/projects/#{project.id}/clusters/user", non_member), params: cluster_params expect(response).to have_gitlab_http_status(404) @@ -207,11 +207,11 @@ describe API::ProjectClusters do end context 'with valid params' do - it 'should respond with 201' do + it 'responds with 201' do expect(response).to have_gitlab_http_status(201) end - it 'should create a new Cluster::Cluster' do + it 'creates a new Cluster::Cluster' do cluster_result = Clusters::Cluster.find(json_response["id"]) platform_kubernetes = cluster_result.platform @@ -246,7 +246,7 @@ describe API::ProjectClusters do context 'when user sets authorization type as ABAC' do let(:authorization_type) { 'abac' } - it 'should create an ABAC cluster' do + it 'creates an ABAC cluster' do cluster_result = Clusters::Cluster.find(json_response['id']) expect(cluster_result.platform.abac?).to be_truthy @@ -256,15 +256,15 @@ describe API::ProjectClusters do context 'with invalid params' do let(:namespace) { 'invalid_namespace' } - it 'should respond with 400' do + it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end - it 'should not create a new Clusters::Cluster' do + it 'does not create a new Clusters::Cluster' do expect(project.reload.clusters).to be_empty end - it 'should return validation errors' do + it 'returns validation errors' do expect(json_response['message']['platform_kubernetes.namespace'].first).to be_present end end @@ -278,11 +278,11 @@ describe API::ProjectClusters do post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params end - it 'should respond with 403' do + it 'responds with 403' do expect(response).to have_gitlab_http_status(403) end - it 'should return an appropriate message' do + it 'returns an appropriate message' do expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters') end end @@ -314,7 +314,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do put api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: update_params expect(response).to have_gitlab_http_status(404) @@ -329,11 +329,11 @@ describe API::ProjectClusters do end context 'with valid params' do - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end - it 'should update cluster attributes' do + it 'updates cluster attributes' do expect(cluster.domain).to eq('new-domain.com') expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') end @@ -342,17 +342,17 @@ describe API::ProjectClusters do context 'with invalid params' do let(:namespace) { 'invalid_namespace' } - it 'should respond with 400' do + it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end - it 'should not update cluster attributes' do + it 'does not update cluster attributes' do expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') expect(cluster.kubernetes_namespace.namespace).not_to eq('invalid_namespace') end - it 'should return validation errors' do + it 'returns validation errors' do expect(json_response['message']['platform_kubernetes.namespace'].first).to match('can contain only lowercase letters') end end @@ -366,11 +366,11 @@ describe API::ProjectClusters do } end - it 'should respond with 400' do + it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end - it 'should return validation error' do + it 'returns validation error' do expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster') end end @@ -378,7 +378,7 @@ describe API::ProjectClusters do context 'when user tries to change namespace' do let(:namespace) { 'new-namespace' } - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end end @@ -407,11 +407,11 @@ describe API::ProjectClusters do } end - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end - it 'should update platform kubernetes attributes' do + it 'updates platform kubernetes attributes' do platform_kubernetes = cluster.platform_kubernetes expect(cluster.name).to eq('new-name') @@ -424,7 +424,7 @@ describe API::ProjectClusters do context 'with a cluster that does not belong to user' do let(:cluster) { create(:cluster, :project, :provided_by_user) } - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end @@ -440,7 +440,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do delete api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: cluster_params expect(response).to have_gitlab_http_status(404) @@ -452,18 +452,18 @@ describe API::ProjectClusters do delete api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: cluster_params end - it 'should respond with 204' do + it 'responds with 204' do expect(response).to have_gitlab_http_status(204) end - it 'should delete the cluster' do + it 'deletes the cluster' do expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy end context 'with a cluster that does not belong to user' do let(:cluster) { create(:cluster, :project, :provided_by_user) } - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb index be6aa7c65c3..dbfb3eace83 100644 --- a/spec/serializers/analytics_stage_serializer_spec.rb +++ b/spec/serializers/analytics_stage_serializer_spec.rb @@ -14,7 +14,7 @@ describe AnalyticsStageSerializer do allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({}) end - it 'it generates payload for single object' do + it 'generates payload for single object' do expect(subject).to be_kind_of Hash end diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb index 236c244b402..8fa0574bfd6 100644 --- a/spec/serializers/analytics_summary_serializer_spec.rb +++ b/spec/serializers/analytics_summary_serializer_spec.rb @@ -18,7 +18,7 @@ describe AnalyticsSummarySerializer do .to receive(:value).and_return(1.12) end - it 'it generates payload for single object' do + it 'generates payload for single object' do expect(subject).to be_kind_of Hash end diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb index 791b64dc356..c2312734042 100644 --- a/spec/serializers/environment_entity_spec.rb +++ b/spec/serializers/environment_entity_spec.rb @@ -54,7 +54,7 @@ describe EnvironmentEntity do projects: [project]) end - it 'should include cluster_type' do + it 'includes cluster_type' do expect(subject).to include(:cluster_type) expect(subject[:cluster_type]).to eq('project_type') end @@ -65,7 +65,7 @@ describe EnvironmentEntity do create(:kubernetes_service, project: project) end - it 'should not include cluster_type' do + it 'does not include cluster_type' do expect(subject).not_to include(:cluster_type) end end diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb index 851b41a7f7e..8de61d4d466 100644 --- a/spec/serializers/job_entity_spec.rb +++ b/spec/serializers/job_entity_spec.rb @@ -154,15 +154,15 @@ describe JobEntity do expect(subject[:status][:label]).to eq('failed') end - it 'should indicate the failure reason on tooltip' do + it 'indicates the failure reason on tooltip' do expect(subject[:status][:tooltip]).to eq('failed - (API failure)') end - it 'should include a callout message with a verbose output' do + it 'includes a callout message with a verbose output' do expect(subject[:callout_message]).to eq('There has been an API failure, please try again') end - it 'should state that it is not recoverable' do + it 'states that it is not recoverable' do expect(subject[:recoverable]).to be_truthy end end @@ -178,15 +178,15 @@ describe JobEntity do expect(subject[:status][:label]).to eq('failed (allowed to fail)') end - it 'should indicate the failure reason on tooltip' do + it 'indicates the failure reason on tooltip' do expect(subject[:status][:tooltip]).to eq('failed - (API failure) (allowed to fail)') end - it 'should include a callout message with a verbose output' do + it 'includes a callout message with a verbose output' do expect(subject[:callout_message]).to eq('There has been an API failure, please try again') end - it 'should state that it is not recoverable' do + it 'states that it is not recoverable' do expect(subject[:recoverable]).to be_truthy end end @@ -194,7 +194,7 @@ describe JobEntity do context 'when the job failed with a script failure' do let(:job) { create(:ci_build, :failed, :script_failure) } - it 'should not include callout message or recoverable keys' do + it 'does not include callout message or recoverable keys' do expect(subject).not_to include('callout_message') expect(subject).not_to include('recoverable') end @@ -203,7 +203,7 @@ describe JobEntity do context 'when job failed and is recoverable' do let(:job) { create(:ci_build, :api_failure) } - it 'should state it is recoverable' do + it 'states it is recoverable' do expect(subject[:recoverable]).to be_truthy end end @@ -211,7 +211,7 @@ describe JobEntity do context 'when job passed' do let(:job) { create(:ci_build, :success) } - it 'should not include callout message or recoverable keys' do + it 'does not include callout message or recoverable keys' do expect(subject).not_to include('callout_message') expect(subject).not_to include('recoverable') end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 0e99ef38d2f..b89898f26f7 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -287,7 +287,7 @@ describe MergeRequestWidgetEntity do resource.commits.find { |c| c.short_id == short_id } end - it 'should not include merge commits' do + it 'does not include merge commits' do commits_in_widget = subject[:commits_without_merge_commits] expect(commits_in_widget.length).to be < resource.commits.length diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb index 11a65d0c300..382b9043566 100644 --- a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb @@ -89,7 +89,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do it_behaves_like 'creates service account and token' - it 'should create a cluster role binding with cluster-admin access' do + it 'creates a cluster role binding with cluster-admin access' do subject expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings").with( diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb index 3a2bbf1ecd1..1bd0356a73b 100644 --- a/spec/services/deploy_tokens/create_service_spec.rb +++ b/spec/services/deploy_tokens/create_service_spec.rb @@ -9,11 +9,11 @@ describe DeployTokens::CreateService do subject { described_class.new(project, user, deploy_token_params).execute } context 'when the deploy token is valid' do - it 'should create a new DeployToken' do + it 'creates a new DeployToken' do expect { subject }.to change { DeployToken.count }.by(1) end - it 'should create a new ProjectDeployToken' do + it 'creates a new ProjectDeployToken' do expect { subject }.to change { ProjectDeployToken.count }.by(1) end @@ -25,7 +25,7 @@ describe DeployTokens::CreateService do context 'when expires at date is not passed' do let(:deploy_token_params) { attributes_for(:deploy_token, expires_at: '') } - it 'should set Forever.date' do + it 'sets Forever.date' do expect(subject.read_attribute(:expires_at)).to eq(Forever.date) end end @@ -33,11 +33,11 @@ describe DeployTokens::CreateService do context 'when the deploy token is invalid' do let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) } - it 'should not create a new DeployToken' do + it 'does not create a new DeployToken' do expect { subject }.not_to change { DeployToken.count } end - it 'should not create a new ProjectDeployToken' do + it 'does not create a new ProjectDeployToken' do expect { subject }.not_to change { ProjectDeployToken.count } end end diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index 79d504b9b45..0bc67dbb4a1 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -12,11 +12,11 @@ describe Groups::TransferService, :postgresql do allow(Group).to receive(:supports_nested_objects?).and_return(false) end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: Database is not supported.') end @@ -30,11 +30,11 @@ describe Groups::TransferService, :postgresql do create(:group_member, :owner, group: new_parent_group, user: user) end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: namespace directory cannot be moved') end @@ -50,7 +50,7 @@ describe Groups::TransferService, :postgresql do context 'when the group is already a root group' do let(:group) { create(:group, :public) } - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(nil) expect(transfer_service.error).to eq('Transfer failed: Group is already a root group.') end @@ -59,11 +59,11 @@ describe Groups::TransferService, :postgresql do context 'when the user does not have the right policies' do let!(:group_member) { create(:group_member, :guest, group: group, user: user) } - it "should return false" do + it "returns false" do expect(transfer_service.execute(nil)).to be_falsy end - it "should add an error on group" do + it "adds an error on group" do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq("Transfer failed: You don't have enough permissions.") end @@ -76,11 +76,11 @@ describe Groups::TransferService, :postgresql do create(:group, path: 'not-unique') end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(nil)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(nil) expect(transfer_service.error).to eq('Transfer failed: The parent group already has a subgroup with the same path.') end @@ -96,17 +96,17 @@ describe Groups::TransferService, :postgresql do group.reload end - it 'should update group attributes' do + it 'updates group attributes' do expect(group.parent).to be_nil end - it 'should update group children path' do + it 'updates group children path' do group.children.each do |subgroup| expect(subgroup.full_path).to eq("#{group.path}/#{subgroup.path}") end end - it 'should update group projects path' do + it 'updates group projects path' do group.projects.each do |project| expect(project.full_path).to eq("#{group.path}/#{project.path}") end @@ -122,11 +122,11 @@ describe Groups::TransferService, :postgresql do context 'when the new parent group is the same as the previous parent group' do let(:group) { create(:group, :public, :nested, parent: new_parent_group) } - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: Group is already associated to the parent group.') end @@ -135,11 +135,11 @@ describe Groups::TransferService, :postgresql do context 'when the user does not have the right policies' do let!(:group_member) { create(:group_member, :guest, group: group, user: user) } - it "should return false" do + it "returns false" do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it "should add an error on group" do + it "adds an error on group" do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq("Transfer failed: You don't have enough permissions.") end @@ -152,11 +152,11 @@ describe Groups::TransferService, :postgresql do create(:group, path: "not-unique", parent: new_parent_group) end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: The parent group already has a subgroup with the same path.') end @@ -171,11 +171,11 @@ describe Groups::TransferService, :postgresql do group.update_attribute(:path, 'foo') end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: Validation failed: Group URL has already been taken') end @@ -191,7 +191,7 @@ describe Groups::TransferService, :postgresql do let(:new_parent_group) { create(:group, :public) } let(:group) { create(:group, :private, :nested) } - it 'should not update the visibility for the group' do + it 'does not update the visibility for the group' do group.reload expect(group.private?).to be_truthy expect(group.visibility_level).not_to eq(new_parent_group.visibility_level) @@ -202,27 +202,27 @@ describe Groups::TransferService, :postgresql do let(:new_parent_group) { create(:group, :private) } let(:group) { create(:group, :public, :nested) } - it 'should update visibility level based on the parent group' do + it 'updates visibility level based on the parent group' do group.reload expect(group.private?).to be_truthy expect(group.visibility_level).to eq(new_parent_group.visibility_level) end end - it 'should update visibility for the group based on the parent group' do + it 'updates visibility for the group based on the parent group' do expect(group.visibility_level).to eq(new_parent_group.visibility_level) end - it 'should update parent group to the new parent ' do + it 'updates parent group to the new parent' do expect(group.parent).to eq(new_parent_group) end - it 'should return the group as children of the new parent' do + it 'returns the group as children of the new parent' do expect(new_parent_group.children.count).to eq(1) expect(new_parent_group.children.first).to eq(group) end - it 'should create a redirect for the group' do + it 'creates a redirect for the group' do expect(group.redirect_routes.count).to eq(1) end end @@ -236,21 +236,21 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update subgroups path' do + it 'updates subgroups path' do new_parent_path = new_parent_group.path group.children.each do |subgroup| expect(subgroup.full_path).to eq("#{new_parent_path}/#{group.path}/#{subgroup.path}") end end - it 'should create redirects for the subgroups' do + it 'creates redirects for the subgroups' do expect(group.redirect_routes.count).to eq(1) expect(subgroup1.redirect_routes.count).to eq(1) expect(subgroup2.redirect_routes.count).to eq(1) end context 'when the new parent has a higher visibility than the children' do - it 'should not update the children visibility' do + it 'does not update the children visibility' do expect(subgroup1.private?).to be_truthy expect(subgroup2.internal?).to be_truthy end @@ -261,7 +261,7 @@ describe Groups::TransferService, :postgresql do let!(:subgroup2) { create(:group, :public, parent: group) } let(:new_parent_group) { create(:group, :private) } - it 'should update children visibility to match the new parent' do + it 'updates children visibility to match the new parent' do group.children.each do |subgroup| expect(subgroup.private?).to be_truthy end @@ -279,21 +279,21 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update projects path' do + it 'updates projects path' do new_parent_path = new_parent_group.path group.projects.each do |project| expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}") end end - it 'should create permanent redirects for the projects' do + it 'creates permanent redirects for the projects' do expect(group.redirect_routes.count).to eq(1) expect(project1.redirect_routes.count).to eq(1) expect(project2.redirect_routes.count).to eq(1) end context 'when the new parent has a higher visibility than the projects' do - it 'should not update projects visibility' do + it 'does not update projects visibility' do expect(project1.private?).to be_truthy expect(project2.internal?).to be_truthy end @@ -304,7 +304,7 @@ describe Groups::TransferService, :postgresql do let!(:project2) { create(:project, :repository, :public, namespace: group) } let(:new_parent_group) { create(:group, :private) } - it 'should update projects visibility to match the new parent' do + it 'updates projects visibility to match the new parent' do group.projects.each do |project| expect(project.private?).to be_truthy end @@ -324,21 +324,21 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update subgroups path' do + it 'updates subgroups path' do new_parent_path = new_parent_group.path group.children.each do |subgroup| expect(subgroup.full_path).to eq("#{new_parent_path}/#{group.path}/#{subgroup.path}") end end - it 'should update projects path' do + it 'updates projects path' do new_parent_path = new_parent_group.path group.projects.each do |project| expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}") end end - it 'should create redirect for the subgroups and projects' do + it 'creates redirect for the subgroups and projects' do expect(group.redirect_routes.count).to eq(1) expect(subgroup1.redirect_routes.count).to eq(1) expect(subgroup2.redirect_routes.count).to eq(1) @@ -360,7 +360,7 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update subgroups path' do + it 'updates subgroups path' do new_base_path = "#{new_parent_group.path}/#{group.path}" group.children.each do |children| expect(children.full_path).to eq("#{new_base_path}/#{children.path}") @@ -372,7 +372,7 @@ describe Groups::TransferService, :postgresql do end end - it 'should update projects path' do + it 'updates projects path' do new_parent_path = "#{new_parent_group.path}/#{group.path}" subgroup1.projects.each do |project| project_full_path = "#{new_parent_path}/#{project.namespace.path}/#{project.name}" @@ -380,7 +380,7 @@ describe Groups::TransferService, :postgresql do end end - it 'should create redirect for the subgroups and projects' do + it 'creates redirect for the subgroups and projects' do expect(group.redirect_routes.count).to eq(1) expect(project1.redirect_routes.count).to eq(1) expect(subgroup1.redirect_routes.count).to eq(1) @@ -402,7 +402,7 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should restore group and projects visibility' do + it 'restores group and projects visibility' do subgroup1.reload project1.reload expect(subgroup1.public?).to be_truthy diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 52bbd4e794d..df29cec30eb 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -95,7 +95,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do sha: '1234abcdef', status: 'success') end - it 'it does not merge request' do + it 'does not merge request' do expect(MergeWorker).not_to receive(:perform_async) service.trigger(old_pipeline) end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 5cf3577f01f..bd10523bc94 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -454,35 +454,35 @@ describe MergeRequests::RefreshService do end let(:force_push_commit) { @project.commit('feature').id } - it 'should reload a new diff for a push to the forked project' do + it 'reloads a new diff for a push to the forked project' do expect do service.new(@fork_project, @user).execute(@oldrev, first_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should reload a new diff for a force push to the source branch' do + it 'reloads a new diff for a force push to the source branch' do expect do service.new(@fork_project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should reload a new diff for a force push to the target branch' do + it 'reloads a new diff for a force push to the target branch' do expect do service.new(@project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should reload a new diff for a push to the target project that contains a commit in the MR' do + it 'reloads a new diff for a push to the target project that contains a commit in the MR' do expect do service.new(@project, @user).execute(@oldrev, first_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should not increase the diff count for a new push to target branch' do + it 'does not increase the diff count for a new push to target branch' do new_commit = @project.repository.create_file(@user, 'new-file.txt', 'A new file', message: 'This is a test', branch_name: 'master') diff --git a/spec/services/projects/auto_devops/disable_service_spec.rb b/spec/services/projects/auto_devops/disable_service_spec.rb index 76977d7a1a7..fb1ab3f9949 100644 --- a/spec/services/projects/auto_devops/disable_service_spec.rb +++ b/spec/services/projects/auto_devops/disable_service_spec.rb @@ -46,7 +46,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do create(:ci_pipeline, :failed, :auto_devops_source, project: project) end - it 'should disable Auto DevOps for project' do + it 'disables Auto DevOps for project' do subject expect(auto_devops.enabled).to eq(false) @@ -58,7 +58,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do create_list(:ci_pipeline, 2, :failed, :auto_devops_source, project: project) end - it 'should explicitly disable Auto DevOps for project' do + it 'explicitly disables Auto DevOps for project' do subject expect(auto_devops.reload.enabled).to eq(false) @@ -70,7 +70,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do create(:ci_pipeline, :success, :auto_devops_source, project: project) end - it 'should not disable Auto DevOps for project' do + it 'does not disable Auto DevOps for project' do subject expect(auto_devops.reload.enabled).to be_nil @@ -85,14 +85,14 @@ describe Projects::AutoDevops::DisableService, '#execute' do create(:ci_pipeline, :failed, :auto_devops_source, project: project) end - it 'should disable Auto DevOps for project' do + it 'disables Auto DevOps for project' do subject auto_devops = project.reload.auto_devops expect(auto_devops.enabled).to eq(false) end - it 'should create a ProjectAutoDevops record' do + it 'creates a ProjectAutoDevops record' do expect { subject }.to change { ProjectAutoDevops.count }.from(0).to(1) end end diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index 4b6d0c51363..8455b9bc3cf 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -41,12 +41,12 @@ describe Projects::ParticipantsService do group.add_owner(user) end - it 'should return an url for the avatar' do + it 'returns an url for the avatar' do expect(service.groups.size).to eq 1 expect(service.groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") end - it 'should return an url for the avatar with relative url' do + it 'returns an url for the avatar with relative url' do stub_config_setting(relative_url_root: '/gitlab') stub_config_setting(url: Settings.send(:build_gitlab_url)) diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 42a086d58d2..5b79c40f27b 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -224,7 +224,7 @@ shared_examples 'discussion comments' do |resource_name| find(toggle_selector).click end - it 'should have "Start discussion" selected' do + it 'has "Start discussion" selected' do find("#{menu_selector} li", match: :first) items = all("#{menu_selector} li") @@ -267,7 +267,7 @@ shared_examples 'discussion comments' do |resource_name| end end - it 'should have "Comment" selected when opening the menu' do + it 'has "Comment" selected when opening the menu' do find(toggle_selector).click find("#{menu_selector} li", match: :first) diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index a8b00004fe7..6aa59960092 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -90,7 +90,7 @@ RSpec.shared_examples "redis_shared_examples" do subject { described_class._raw_config } let(:config_file_name) { '/var/empty/doesnotexist' } - it 'should be frozen' do + it 'is frozen' do expect(subject).to be_frozen end diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb index 98ab04c5636..eb051166a69 100644 --- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb +++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb @@ -4,7 +4,7 @@ shared_examples 'set sort order from user preference' do # however any other field present in user_preferences table can be used for testing. context 'when database is in read-only mode' do - it 'it does not update user preference' do + it 'does not update user preference' do allow(Gitlab::Database).to receive(:read_only?).and_return(true) expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param }) diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb index ba9b7d3bdcf..01bee603274 100644 --- a/spec/support/shared_examples/helm_generated_script.rb +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -6,7 +6,7 @@ shared_examples 'helm commands' do EOS end - it 'should return appropriate command' do + it 'returns appropriate command' do expect(subject.generate_script.strip).to eq((helm_setup + commands).strip) end end diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb index d87b3181e80..033b65bdc84 100644 --- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb @@ -9,12 +9,12 @@ shared_examples 'cluster application helm specs' do |application_name| application.cluster.application_helm.ca_cert = nil end - it 'should not include cert files when there is no ca_cert entry' do + it 'does not include cert files when there is no ca_cert entry' do expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem') end end - it 'should include cert files when there is a ca_cert entry' do + it 'includes cert files when there is a ca_cert entry' do expect(subject).to include(:'ca.pem', :'cert.pem', :'key.pem') expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb index 4f662db2120..833c31a57cb 100644 --- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb +++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb @@ -220,11 +220,11 @@ RSpec.shared_examples 'snippet visibility' do end context "For #{params[:project_type]} project and #{params[:user_type]} users" do - it 'should agree with the read_project_snippet policy' do + it 'agrees with the read_project_snippet policy' do expect(can?(user, :read_project_snippet, snippet)).to eq(outcome) end - it 'should return proper outcome' do + it 'returns proper outcome' do results = described_class.new(user, project: project).execute expect(results.include?(snippet)).to eq(outcome) @@ -232,7 +232,7 @@ RSpec.shared_examples 'snippet visibility' do end context "Without a given project and #{params[:user_type]} users" do - it 'should return proper outcome' do + it 'returns proper outcome' do results = described_class.new(user).execute expect(results.include?(snippet)).to eq(outcome) end @@ -283,16 +283,16 @@ RSpec.shared_examples 'snippet visibility' do let!(:snippet) { create(:personal_snippet, visibility_level: snippet_visibility, author: author) } context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do - it 'should agree with read_personal_snippet policy' do + it 'agrees with read_personal_snippet policy' do expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome) end - it 'should return proper outcome' do + it 'returns proper outcome' do results = described_class.new(user).execute expect(results.include?(snippet)).to eq(outcome) end - it 'should return personal snippets when the user cannot read cross project' do + it 'returns personal snippets when the user cannot read cross project' do allow(Ability).to receive(:allowed?).and_call_original allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb index ab98976ec27..42352f9b9f8 100644 --- a/spec/uploaders/records_uploads_spec.rb +++ b/spec/uploaders/records_uploads_spec.rb @@ -71,7 +71,7 @@ describe RecordsUploads do expect { uploader.store!(upload_fixture('rails_sample.jpg')) }.not_to change { Upload.count } end - it 'it destroys Upload records at the same path before recording' do + it 'destroys Upload records at the same path before recording' do existing = Upload.create!( path: File.join('uploads', 'rails_sample.jpg'), size: 512.kilobytes, @@ -88,7 +88,7 @@ describe RecordsUploads do end describe '#destroy_upload callback' do - it 'it destroys Upload records at the same path after removal' do + it 'destroys Upload records at the same path after removal' do uploader.store!(upload_fixture('rails_sample.jpg')) expect { uploader.remove! }.to change { Upload.count }.from(1).to(0) diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb index 38cfb84f0d5..29e15960fb8 100644 --- a/spec/views/groups/edit.html.haml_spec.rb +++ b/spec/views/groups/edit.html.haml_spec.rb @@ -12,7 +12,7 @@ describe 'groups/edit.html.haml' do end shared_examples_for '"Share with group lock" setting' do |checkbox_options| - it 'should have the correct label, help text, and checkbox options' do + it 'has the correct label, help text, and checkbox options' do assign(:group, test_group) allow(view).to receive(:can?).with(test_user, :admin_group, test_group).and_return(true) allow(view).to receive(:can_change_group_visibility_level?).and_return(false) diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb index 908ecb898e4..12925a5ab07 100644 --- a/spec/views/projects/_home_panel.html.haml_spec.rb +++ b/spec/views/projects/_home_panel.html.haml_spec.rb @@ -45,7 +45,7 @@ describe 'projects/_home_panel' do context 'badges' do shared_examples 'show badges' do - it 'should render the all badges' do + it 'renders the all badges' do render expect(rendered).to have_selector('.project-badges a') @@ -70,7 +70,7 @@ describe 'projects/_home_panel' do context 'has no badges' do let(:project) { create(:project) } - it 'should not render any badge' do + it 'does not render any badge' do render expect(rendered).not_to have_selector('.project-badges') diff --git a/spec/views/shared/milestones/_issuables.html.haml.rb b/spec/views/shared/milestones/_issuables.html.haml.rb index 4769d569548..cbbb984935f 100644 --- a/spec/views/shared/milestones/_issuables.html.haml.rb +++ b/spec/views/shared/milestones/_issuables.html.haml.rb @@ -11,12 +11,12 @@ describe 'shared/milestones/_issuables.html.haml' do stub_template 'shared/milestones/_issuable.html.haml' => '' end - it 'should show the issuables count if show_counter is true' do + it 'shows the issuables count if show_counter is true' do render 'shared/milestones/issuables', show_counter: true expect(rendered).to have_content('100') end - it 'should not show the issuables count if show_counter is false' do + it 'does not show the issuables count if show_counter is false' do render 'shared/milestones/issuables', show_counter: false expect(rendered).not_to have_content('100') end @@ -24,7 +24,7 @@ describe 'shared/milestones/_issuables.html.haml' do describe 'a high issuables count' do let(:issuables_size) { 1000 } - it 'should show a delimited number if show_counter is true' do + it 'shows a delimited number if show_counter is true' do render 'shared/milestones/issuables', show_counter: true expect(rendered).to have_content('1,000') end diff --git a/spec/views/shared/projects/_project.html.haml_spec.rb b/spec/views/shared/projects/_project.html.haml_spec.rb index 3b14045e61f..dc223861037 100644 --- a/spec/views/shared/projects/_project.html.haml_spec.rb +++ b/spec/views/shared/projects/_project.html.haml_spec.rb @@ -8,13 +8,13 @@ describe 'shared/projects/_project.html.haml' do allow(view).to receive(:can?) { true } end - it 'should render creator avatar if project has a creator' do + it 'renders creator avatar if project has a creator' do render 'shared/projects/project', use_creator_avatar: true, project: project expect(rendered).to have_selector('img.avatar') end - it 'should render a generic avatar if project does not have a creator' do + it 'renders a generic avatar if project does not have a creator' do project.creator = nil render 'shared/projects/project', use_creator_avatar: true, project: project -- cgit v1.2.1 From 239aa514382d8573ee151cf24ef390cbcd54fbcf Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Fri, 5 Apr 2019 09:14:13 +0000 Subject: Restore multiple series support Corrects an issue whereby queries which return multiple series results were not rendering as multiple data series on the metrics dashboard --- spec/frontend/helpers/monitor_helper_spec.js | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 spec/frontend/helpers/monitor_helper_spec.js (limited to 'spec') diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js new file mode 100644 index 00000000000..2e8bff298c4 --- /dev/null +++ b/spec/frontend/helpers/monitor_helper_spec.js @@ -0,0 +1,45 @@ +import * as monitorHelper from '~/helpers/monitor_helper'; + +describe('monitor helper', () => { + const defaultConfig = { default: true, name: 'default name' }; + const name = 'data name'; + const series = [[1, 1], [2, 2], [3, 3]]; + const data = ({ metric = { default_name: name }, values = series } = {}) => [{ metric, values }]; + + describe('makeDataSeries', () => { + const expectedDataSeries = [ + { + ...defaultConfig, + data: series, + }, + ]; + + it('converts query results to data series', () => { + expect(monitorHelper.makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual( + expectedDataSeries, + ); + }); + + it('returns an empty array if no query results exist', () => { + expect(monitorHelper.makeDataSeries([], defaultConfig)).toEqual([]); + }); + + it('handles multi-series query results', () => { + const expectedData = { ...expectedDataSeries[0], name: 'default name: data name' }; + + expect(monitorHelper.makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([ + expectedData, + expectedData, + ]); + }); + + it('excludes NaN values', () => { + expect( + monitorHelper.makeDataSeries( + data({ metric: {}, values: [[1, 1], [2, NaN]] }), + defaultConfig, + ), + ).toEqual([{ ...expectedDataSeries[0], data: [[1, 1]] }]); + }); + }); +}); -- cgit v1.2.1 From 64e9cf00a7d889913ae7327695394cac29643f0b Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Fri, 5 Apr 2019 12:08:05 +0200 Subject: Rename environment variable EE to IS_GITLAB_EE --- spec/javascripts/test_bundle.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 235a17d13b0..87ef0885d8c 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -69,7 +69,7 @@ window.gl = window.gl || {}; window.gl.TEST_HOST = TEST_HOST; window.gon = window.gon || {}; window.gon.test_env = true; -window.gon.ee = process.env.EE; +window.gon.ee = process.env.IS_GITLAB_EE; gon.relative_url_root = ''; let hasUnhandledPromiseRejections = false; @@ -124,7 +124,7 @@ const axiosDefaultAdapter = getDefaultAdapter(); // render all of our tests const testContexts = [require.context('spec', true, /_spec$/)]; -if (process.env.EE) { +if (process.env.IS_GITLAB_EE) { testContexts.push(require.context('ee_spec', true, /_spec$/)); } @@ -213,7 +213,7 @@ if (process.env.BABEL_ENV === 'coverage') { describe('Uncovered files', function() { const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)]; - if (process.env.EE) { + if (process.env.IS_GITLAB_EE) { sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/)); } -- cgit v1.2.1 From 2fdda74458bb7b58a6a18b72170a572f72afdd95 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 5 Apr 2019 10:51:42 +0000 Subject: Fall back to project repository type by default This makes sure that we always have a repository type when trying to parse a repository from a path. This is needed because sometimes we want to perform access checks as if the project already existed, for example when creating a project on push. Before this we were only doing that when accessing git over http, this makes sure it also works correctly when accessing git over SSH --- spec/lib/gitlab/repo_path_spec.rb | 6 ++++-- spec/requests/api/internal_spec.rb | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index 4c7ca4e2b57..8fbda929064 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -44,8 +44,10 @@ describe ::Gitlab::RepoPath do end end - it "returns nil for non existent paths" do - expect(described_class.parse("path/non-existent.git")).to eq(nil) + it "returns the default type for non existent paths" do + _project, type, _redirected = described_class.parse("path/non-existent.git") + + expect(type).to eq(Gitlab::GlRepository.default_type) end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 6640ce2b07e..0919540e4ba 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -644,6 +644,22 @@ describe API::Internal do expect(response).to have_gitlab_http_status(404) expect(json_response["status"]).to be_falsey end + + it 'returns a 200 response when using a project path that does not exist' do + post( + api("/internal/allowed"), + params: { + key_id: key.id, + project: 'project/does-not-exist.git', + action: 'git-upload-pack', + secret_token: secret_token, + protocol: 'ssh' + } + ) + + expect(response).to have_gitlab_http_status(404) + expect(json_response["status"]).to be_falsey + end end context 'user does not exist' do -- cgit v1.2.1 From d9d7237d2ebf101ca35ed8ba2740e7c7093437ea Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Fri, 5 Apr 2019 11:45:47 +0000 Subject: Move Contribution Analytics related spec in spec/features/groups/group_page_with_external_authorization_service_spec to EE --- .../admin/application_settings_controller_spec.rb | 22 ++++ spec/controllers/boards/issues_controller_spec.rb | 26 ++++ .../concerns/project_unauthorized_spec.rb | 51 ++++++++ .../dashboard/groups_controller_spec.rb | 50 +++++--- .../dashboard/labels_controller_spec.rb | 6 +- .../dashboard/milestones_controller_spec.rb | 6 + .../dashboard/projects_controller_spec.rb | 24 +++- .../controllers/dashboard/todos_controller_spec.rb | 6 + spec/controllers/groups/avatars_controller_spec.rb | 10 ++ spec/controllers/groups/boards_controller_spec.rb | 8 ++ .../controllers/groups/children_controller_spec.rb | 12 ++ .../groups/group_members_controller_spec.rb | 86 +++++++++++++ spec/controllers/groups/labels_controller_spec.rb | 6 + .../groups/milestones_controller_spec.rb | 6 + .../groups/settings/ci_cd_controller_spec.rb | 15 +++ .../groups/variables_controller_spec.rb | 34 +++++ spec/controllers/groups_controller_spec.rb | 96 ++++++++++++++ .../controllers/projects/boards_controller_spec.rb | 4 + .../controllers/projects/issues_controller_spec.rb | 11 ++ spec/controllers/projects_controller_spec.rb | 32 +++++ spec/controllers/search_controller_spec.rb | 39 ++++++ spec/controllers/users_controller_spec.rb | 12 ++ ...ard_with_external_authorization_service_spec.rb | 38 ++++++ ...age_with_external_authorization_service_spec.rb | 58 +++++++++ .../classification_label_on_project_pages_spec.rb | 22 ++++ spec/features/projects/forks/fork_list_spec.rb | 35 +++++ ...ues_with_external_authorization_enabled_spec.rb | 128 +++++++++++++++++++ ...external_authorization_service_settings_spec.rb | 21 +++ spec/features/users/show_spec.rb | 22 ++++ spec/finders/issues_finder_spec.rb | 7 + spec/finders/labels_finder_spec.rb | 7 + spec/finders/merge_requests_finder_spec.rb | 7 + spec/finders/snippets_finder_spec.rb | 32 +++++ spec/finders/todos_finder_spec.rb | 7 + spec/fixtures/passphrase_x509_certificate.crt | 27 ++++ spec/fixtures/passphrase_x509_certificate_pk.key | 54 ++++++++ spec/fixtures/x509_certificate.crt | 27 ++++ spec/fixtures/x509_certificate_pk.key | 51 ++++++++ .../gitlab/external_authorization/access_spec.rb | 142 +++++++++++++++++++++ .../gitlab/external_authorization/cache_spec.rb | 48 +++++++ .../gitlab/external_authorization/client_spec.rb | 97 ++++++++++++++ .../gitlab/external_authorization/logger_spec.rb | 45 +++++++ .../gitlab/external_authorization/response_spec.rb | 52 ++++++++ spec/lib/gitlab/external_authorization_spec.rb | 54 ++++++++ .../gitlab/import_export/safe_model_attributes.yml | 1 + spec/models/application_setting_spec.rb | 48 ++++++- spec/models/concerns/protected_ref_access_spec.rb | 12 ++ spec/models/issue_spec.rb | 45 +++++++ spec/models/project_spec.rb | 20 +++ spec/policies/base_policy_spec.rb | 23 ++++ spec/policies/issue_policy_spec.rb | 19 +++ spec/policies/merge_request_policy_spec.rb | 19 +++ spec/policies/project_policy_spec.rb | 53 ++++++++ spec/requests/api/projects_spec.rb | 49 +++++++ spec/requests/api/settings_spec.rb | 33 +++++ spec/serializers/group_child_entity_spec.rb | 19 +++ .../application_settings/update_service_spec.rb | 35 +++++ spec/services/notification_service_spec.rb | 41 ++++++ spec/services/projects/create_service_spec.rb | 37 ++++++ spec/services/projects/update_service_spec.rb | 41 ++++++ .../external_authorization_service_helpers.rb | 33 +++++ ...ternal_authorization_service_shared_examples.rb | 40 ++++++ .../finder_with_external_authorization_enabled.rb | 30 +++++ .../x509_certificate_credentials_validator_spec.rb | 87 +++++++++++++ 64 files changed, 2206 insertions(+), 22 deletions(-) create mode 100644 spec/controllers/concerns/project_unauthorized_spec.rb create mode 100644 spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb create mode 100644 spec/features/groups/group_page_with_external_authorization_service_spec.rb create mode 100644 spec/features/projects/classification_label_on_project_pages_spec.rb create mode 100644 spec/features/projects/forks/fork_list_spec.rb create mode 100644 spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb create mode 100644 spec/features/projects/settings/external_authorization_service_settings_spec.rb create mode 100644 spec/fixtures/passphrase_x509_certificate.crt create mode 100644 spec/fixtures/passphrase_x509_certificate_pk.key create mode 100644 spec/fixtures/x509_certificate.crt create mode 100644 spec/fixtures/x509_certificate_pk.key create mode 100644 spec/lib/gitlab/external_authorization/access_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/cache_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/client_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/logger_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/response_spec.rb create mode 100644 spec/lib/gitlab/external_authorization_spec.rb create mode 100644 spec/support/external_authorization_service_helpers.rb create mode 100644 spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb create mode 100644 spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb create mode 100644 spec/validators/x509_certificate_credentials_validator_spec.rb (limited to 'spec') diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 9af472df74e..60b725f4cac 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -85,6 +85,28 @@ describe Admin::ApplicationSettingsController do expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) end + + context 'external policy classification settings' do + let(:settings) do + { + external_authorization_service_enabled: true, + external_authorization_service_url: 'https://custom.service/', + external_authorization_service_default_label: 'default', + external_authorization_service_timeout: 3, + external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'), + external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'), + external_auth_client_key_pass: "5iveL!fe" + } + end + + it 'updates settings when the feature is available' do + put :update, params: { application_setting: settings } + + settings.each do |attribute, value| + expect(ApplicationSetting.current.public_send(attribute)).to eq(value) + end + end + end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 5eb05f01b8d..309cac47928 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Boards::IssuesController do + include ExternalAuthorizationServiceHelpers + let(:project) { create(:project, :private) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } @@ -136,6 +138,30 @@ describe Boards::IssuesController do end end + context 'with external authorization' do + before do + sign_in(user) + enable_external_authorization_service_check + end + + it 'returns a 403 for group boards' do + group = create(:group) + group_board = create(:board, group: group) + + list_issues(user: user, board: group_board) + + expect(response).to have_gitlab_http_status(403) + end + + it 'is successful for project boards' do + project_board = create(:board, project: project) + + list_issues(user: user, board: project_board) + + expect(response).to have_gitlab_http_status(200) + end + end + def list_issues(user:, board:, list: nil) sign_in(user) diff --git a/spec/controllers/concerns/project_unauthorized_spec.rb b/spec/controllers/concerns/project_unauthorized_spec.rb new file mode 100644 index 00000000000..90b59b027cf --- /dev/null +++ b/spec/controllers/concerns/project_unauthorized_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe ProjectUnauthorized do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } + + before do + sign_in user + end + + render_views + + describe '#project_unauthorized_proc' do + controller(::Projects::ApplicationController) do + def show + head :ok + end + end + + let(:project) { create(:project) } + + before do + project.add_developer(user) + end + + it 'renders a 200 when the service allows access to the project' do + external_service_allow_access(user, project) + + get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param } + + expect(response).to have_gitlab_http_status(200) + end + + it 'renders a 403 when the service denies access to the project' do + external_service_deny_access(user, project) + + get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param } + + expect(response).to have_gitlab_http_status(403) + expect(response.body).to match("External authorization denied access to this project") + end + + it 'renders a 404 when the user cannot see the project at all' do + other_project = create(:project, :private) + + get :show, params: { namespace_id: other_project.namespace.to_param, id: other_project.to_param } + + expect(response).to have_gitlab_http_status(404) + end + end +end diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb index c8d99f79277..775b3ca40b2 100644 --- a/spec/controllers/dashboard/groups_controller_spec.rb +++ b/spec/controllers/dashboard/groups_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Dashboard::GroupsController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } before do @@ -11,33 +13,43 @@ describe Dashboard::GroupsController do expect(described_class).to include(GroupTree) end - it 'only includes projects the user is a member of' do - member_of_group = create(:group) - member_of_group.add_developer(user) - create(:group, :public) + describe '#index' do + it 'only includes projects the user is a member of' do + member_of_group = create(:group) + member_of_group.add_developer(user) + create(:group, :public) - get :index + get :index - expect(assigns(:groups)).to contain_exactly(member_of_group) - end + expect(assigns(:groups)).to contain_exactly(member_of_group) + end - context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do - let!(:top_level_result) { create(:group, name: 'chef-top') } - let!(:top_level_a) { create(:group, name: 'top-a') } - let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) } - let!(:other_group) { create(:group, name: 'other') } + context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do + let!(:top_level_result) { create(:group, name: 'chef-top') } + let!(:top_level_a) { create(:group, name: 'top-a') } + let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) } + let!(:other_group) { create(:group, name: 'other') } - before do - top_level_result.add_maintainer(user) - top_level_a.add_maintainer(user) + before do + top_level_result.add_maintainer(user) + top_level_a.add_maintainer(user) + end + + it 'renders only groups the user is a member of when searching hierarchy correctly' do + get :index, params: { filter: 'chef' }, format: :json + + expect(response).to have_gitlab_http_status(200) + all_groups = [top_level_result, top_level_a, sub_level_result_a] + expect(assigns(:groups)).to contain_exactly(*all_groups) + end end - it 'renders only groups the user is a member of when searching hierarchy correctly' do - get :index, params: { filter: 'chef' }, format: :json + it 'works when the external authorization service is enabled' do + enable_external_authorization_service_check + + get :index expect(response).to have_gitlab_http_status(200) - all_groups = [top_level_result, top_level_a, sub_level_result_a] - expect(assigns(:groups)).to contain_exactly(*all_groups) end end end diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb index a3bfb2f3a87..01de896f9f4 100644 --- a/spec/controllers/dashboard/labels_controller_spec.rb +++ b/spec/controllers/dashboard/labels_controller_spec.rb @@ -13,13 +13,17 @@ describe Dashboard::LabelsController do describe "#index" do let!(:unrelated_label) { create(:label, project: create(:project, :public)) } + subject { get :index, format: :json } + it 'returns global labels for projects the user has a relationship with' do - get :index, format: :json + subject expect(json_response).to be_kind_of(Array) expect(json_response.size).to eq(1) expect(json_response[0]["id"]).to be_nil expect(json_response[0]["title"]).to eq(label.title) end + + it_behaves_like 'disabled when using an external authorization service' end end diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 828de0e7ca5..1614739db05 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -81,5 +81,11 @@ describe Dashboard::MilestonesController do expect(response.body).to include("Open\n2") expect(response.body).to include("Closed\n0") end + + context 'external authorization' do + subject { get :index } + + it_behaves_like 'disabled when using an external authorization service' + end end end diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb index 649441f4917..c17cb49e460 100644 --- a/spec/controllers/dashboard/projects_controller_spec.rb +++ b/spec/controllers/dashboard/projects_controller_spec.rb @@ -1,7 +1,29 @@ require 'spec_helper' describe Dashboard::ProjectsController do - it_behaves_like 'authenticates sessionless user', :index, :atom + include ExternalAuthorizationServiceHelpers + + describe '#index' do + context 'user not logged in' do + it_behaves_like 'authenticates sessionless user', :index, :atom + end + + context 'user logged in' do + before do + sign_in create(:user) + end + + context 'external authorization' do + it 'works when the external authorization service is enabled' do + enable_external_authorization_service_check + + get :index + + expect(response).to have_gitlab_http_status(200) + end + end + end + end context 'json requests' do render_views diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index d88beaff0e1..abbf0b52306 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -105,6 +105,12 @@ describe Dashboard::TodosController do end end end + + context 'external authorization' do + subject { get :index } + + it_behaves_like 'disabled when using an external authorization service' + end end describe 'PATCH #restore' do diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb index 772d1d0c1dd..6ececa6f372 100644 --- a/spec/controllers/groups/avatars_controller_spec.rb +++ b/spec/controllers/groups/avatars_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::AvatarsController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } @@ -15,4 +17,12 @@ describe Groups::AvatarsController do expect(@group.avatar.present?).to be_falsey expect(@group).to be_valid end + + it 'works when external authorization service is enabled' do + enable_external_authorization_service_check + + delete :destroy, params: { group_id: group } + + expect(response).to have_gitlab_http_status(302) + end end diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index 27ee37b3817..0ca5ce51750 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -82,6 +82,10 @@ describe Groups::BoardsController do end end + it_behaves_like 'disabled when using an external authorization service' do + subject { list_boards } + end + def list_boards(format: :html) get :index, params: { group_id: group }, format: format end @@ -160,6 +164,10 @@ describe Groups::BoardsController do end end + it_behaves_like 'disabled when using an external authorization service' do + subject { read_board board: board } + end + def read_board(board:, format: :html) get :show, params: { group_id: group, diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb index e1b97013408..4085c8f95a9 100644 --- a/spec/controllers/groups/children_controller_spec.rb +++ b/spec/controllers/groups/children_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::ChildrenController do + include ExternalAuthorizationServiceHelpers + let(:group) { create(:group, :public) } let(:user) { create(:user) } let!(:group_member) { create(:group_member, group: group, user: user) } @@ -317,5 +319,15 @@ describe Groups::ChildrenController do end end end + + context 'external authorization' do + it 'works when external authorization service is enabled' do + enable_external_authorization_service_check + + get :index, params: { group_id: group }, format: :json + + expect(response).to have_gitlab_http_status(200) + end + end end end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 3a801fabafc..96a58d6d87c 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -1,8 +1,11 @@ require 'spec_helper' describe Groups::GroupMembersController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } + let(:membership) { create(:group_member, group: group) } describe 'GET index' do it 'renders index with 200 status code' do @@ -263,4 +266,87 @@ describe Groups::GroupMembersController do end end end + + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + group.add_owner(user) + sign_in(user) + end + + describe 'GET #index' do + it 'is successful' do + get :index, params: { group_id: group } + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'POST #create' do + it 'is successful' do + post :create, params: { group_id: group, users: user, access_level: Gitlab::Access::GUEST } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'PUT #update' do + it 'is successful' do + put :update, + params: { + group_member: { access_level: Gitlab::Access::GUEST }, + group_id: group, + id: membership + }, + format: :js + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'DELETE #destroy' do + it 'is successful' do + delete :destroy, params: { group_id: group, id: membership } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #destroy' do + it 'is successful' do + sign_in(create(:user)) + + post :request_access, params: { group_id: group } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #approve_request_access' do + it 'is successful' do + access_request = create(:group_member, :access_request, group: group) + post :approve_access_request, params: { group_id: group, id: access_request } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'DELETE #leave' do + it 'is successful' do + group.add_owner(create(:user)) + + delete :leave, params: { group_id: group } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #resend_invite' do + it 'is successful' do + post :resend_invite, params: { group_id: group, id: membership } + + expect(response).to have_gitlab_http_status(302) + end + end + end end diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index fa664a29066..9af47114838 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -37,6 +37,12 @@ describe Groups::LabelsController do expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) end end + + context 'external authorization' do + subject { get :index, params: { group_id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end end describe 'POST #toggle_subscription' do diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 043cf28514b..d70946cbc8f 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -80,6 +80,12 @@ describe Groups::MilestonesController do expect(response.content_type).to eq 'application/json' end end + + context 'external authorization' do + subject { get :index, params: { group_id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end end describe '#show' do diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index 3290ed8b088..b998f64ef72 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::Settings::CiCdController do + include ExternalAuthorizationServiceHelpers + let(:group) { create(:group) } let(:user) { create(:user) } @@ -33,6 +35,19 @@ describe Groups::Settings::CiCdController do expect(response).to have_gitlab_http_status(404) end end + + context 'external authorization' do + before do + enable_external_authorization_service_check + group.add_owner(user) + end + + it 'renders show with 200 status code' do + get :show, params: { group_id: group } + + expect(response).to have_gitlab_http_status(200) + end + end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb index 29ec3588316..40f05167350 100644 --- a/spec/controllers/groups/variables_controller_spec.rb +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::VariablesController do + include ExternalAuthorizationServiceHelpers + let(:group) { create(:group) } let(:user) { create(:user) } @@ -34,4 +36,36 @@ describe Groups::VariablesController do include_examples 'PATCH #update updates variables' end + + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + end + + describe 'GET #show' do + let!(:variable) { create(:ci_group_variable, group: group) } + + it 'is successful' do + get :show, params: { group_id: group }, format: :json + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'PATCH #update' do + let!(:variable) { create(:ci_group_variable, group: group) } + let(:owner) { group } + + it 'is successful' do + patch :update, + params: { + group_id: group, + variables_attributes: [{ id: variable.id, key: 'hello' }] + }, + format: :json + + expect(response).to have_gitlab_http_status(200) + end + end + end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 2b803e7151f..0adcba4f0b8 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe GroupsController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:admin) { create(:admin) } let(:group) { create(:group, :public) } @@ -658,4 +660,98 @@ describe GroupsController do end end end + + describe 'external authorization' do + before do + group.add_owner(user) + sign_in(user) + end + + context 'with external authorization service enabled' do + before do + enable_external_authorization_service_check + end + + describe 'GET #show' do + it 'is successful' do + get :show, params: { id: group.to_param } + + expect(response).to have_gitlab_http_status(200) + end + + it 'does not allow other formats' do + get :show, params: { id: group.to_param }, format: :atom + + expect(response).to have_gitlab_http_status(403) + end + end + + describe 'GET #edit' do + it 'is successful' do + get :edit, params: { id: group.to_param } + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'GET #new' do + it 'is successful' do + get :new + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'GET #index' do + it 'is successful' do + get :index + + # Redirects to the dashboard + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #create' do + it 'creates a group' do + expect do + post :create, params: { group: { name: 'a name', path: 'a-name' } } + end.to change { Group.count }.by(1) + end + end + + describe 'PUT #update' do + it 'updates a group' do + expect do + put :update, params: { id: group.to_param, group: { name: 'world' } } + end.to change { group.reload.name } + end + end + + describe 'DELETE #destroy' do + it 'deletes the group' do + delete :destroy, params: { id: group.to_param } + + expect(response).to have_gitlab_http_status(302) + end + end + end + + describe 'GET #activity' do + subject { get :activity, params: { id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end + + describe 'GET #issues' do + subject { get :issues, params: { id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end + + describe 'GET #merge_requests' do + subject { get :merge_requests, params: { id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end + end end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 1eeded06459..b1203fd00b0 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -98,6 +98,10 @@ describe Projects::BoardsController do end end + it_behaves_like 'unauthorized when external service denies access' do + subject { list_boards } + end + def list_boards(format: :html) get :index, params: { namespace_id: project.namespace, diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index c34d7c13d57..bfa23af76d5 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -127,6 +127,17 @@ describe Projects::IssuesController do expect(assigns(:issues).size).to eq(2) end end + + context 'external authorization' do + before do + sign_in user + project.add_developer(user) + end + + it_behaves_like 'unauthorized when external service denies access' do + subject { get :index, params: { namespace_id: project.namespace, project_id: project } } + end + end end describe 'GET #new' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 56d38b9475e..04cc315b175 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1,6 +1,7 @@ require('spec_helper') describe ProjectsController do + include ExternalAuthorizationServiceHelpers include ProjectForksHelper let(:project) { create(:project) } @@ -407,6 +408,37 @@ describe ProjectsController do it_behaves_like 'updating a project' end + + context 'as maintainer' do + before do + project.add_maintainer(user) + sign_in(user) + end + + it_behaves_like 'unauthorized when external service denies access' do + subject do + put :update, + params: { + namespace_id: project.namespace, + id: project, + project: { description: 'Hello world' } + } + project.reload + end + + it 'updates when the service allows access' do + external_service_allow_access(user, project) + + expect { subject }.to change(project, :description) + end + + it 'does not update when the service rejects access' do + external_service_deny_access(user, project) + + expect { subject }.not_to change(project, :description) + end + end + end end describe '#transfer' do diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 02a0cfe0272..752d6ae55cc 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe SearchController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } before do @@ -76,4 +78,41 @@ describe SearchController do expect(assigns[:search_objects].count).to eq(0) end end + + context 'with external authorization service enabled' do + let(:project) { create(:project, namespace: user.namespace) } + let(:note) { create(:note_on_issue, project: project) } + + before do + enable_external_authorization_service_check + end + + describe 'GET #show' do + it 'renders a 403 when no project is given' do + get :show, params: { scope: 'notes', search: note.note } + + expect(response).to have_gitlab_http_status(403) + end + + it 'renders a 200 when a project was set' do + get :show, params: { project_id: project.id, scope: 'notes', search: note.note } + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'GET #autocomplete' do + it 'renders a 403 when no project is given' do + get :autocomplete, params: { term: 'hello' } + + expect(response).to have_gitlab_http_status(403) + end + + it 'renders a 200 when a project was set' do + get :autocomplete, params: { project_id: project.id, term: 'hello' } + + expect(response).to have_gitlab_http_status(200) + end + end + end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 4f6a6881193..42d28c53d34 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -223,6 +223,12 @@ describe UsersController do end end + context 'external authorization' do + subject { get :calendar_activities, params: { username: user.username } } + + it_behaves_like 'disabled when using an external authorization service' + end + def create_push_event push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) EventCreateService.new.push(project, public_user, push_data) @@ -286,6 +292,12 @@ describe UsersController do expect(JSON.parse(response.body)).to have_key('html') end end + + context 'external authorization' do + subject { get :snippets, params: { username: user.username } } + + it_behaves_like 'disabled when using an external authorization service' + end end describe 'GET #exists' do diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb new file mode 100644 index 00000000000..4098dd02141 --- /dev/null +++ b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'The group dashboard' do + include ExternalAuthorizationServiceHelpers + + let(:user) { create(:user) } + + before do + sign_in user + end + + describe 'The top navigation' do + it 'has all the expected links' do + visit dashboard_groups_path + + within('.navbar') do + expect(page).to have_button('Projects') + expect(page).to have_button('Groups') + expect(page).to have_link('Activity') + expect(page).to have_link('Milestones') + expect(page).to have_link('Snippets') + end + end + + it 'hides some links when an external authorization service is enabled' do + enable_external_authorization_service_check + visit dashboard_groups_path + + within('.navbar') do + expect(page).to have_button('Projects') + expect(page).to have_button('Groups') + expect(page).not_to have_link('Activity') + expect(page).not_to have_link('Milestones') + expect(page).to have_link('Snippets') + end + end + end +end diff --git a/spec/features/groups/group_page_with_external_authorization_service_spec.rb b/spec/features/groups/group_page_with_external_authorization_service_spec.rb new file mode 100644 index 00000000000..c05c3f4f3d6 --- /dev/null +++ b/spec/features/groups/group_page_with_external_authorization_service_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'The group page' do + include ExternalAuthorizationServiceHelpers + + let(:user) { create(:user) } + let(:group) { create(:group) } + + before do + sign_in user + group.add_owner(user) + end + + def expect_all_sidebar_links + within('.nav-sidebar') do + expect(page).to have_link('Overview') + expect(page).to have_link('Details') + expect(page).to have_link('Activity') + expect(page).to have_link('Issues') + expect(page).to have_link('Merge Requests') + expect(page).to have_link('Members') + end + end + + describe 'The sidebar' do + it 'has all the expected links' do + visit group_path(group) + + expect_all_sidebar_links + end + + it 'shows all project features when policy control is enabled' do + stub_application_setting(external_authorization_service_enabled: true) + + visit group_path(group) + + expect_all_sidebar_links + end + + it 'hides some links when an external authorization service configured with an url' do + enable_external_authorization_service_check + visit group_path(group) + + within('.nav-sidebar') do + expect(page).to have_link('Overview') + expect(page).to have_link('Details') + expect(page).not_to have_link('Activity') + expect(page).not_to have_link('Contribution Analytics') + + expect(page).not_to have_link('Issues') + expect(page).not_to have_link('Merge Requests') + expect(page).to have_link('Members') + end + end + end +end diff --git a/spec/features/projects/classification_label_on_project_pages_spec.rb b/spec/features/projects/classification_label_on_project_pages_spec.rb new file mode 100644 index 00000000000..92f8aa8eb8d --- /dev/null +++ b/spec/features/projects/classification_label_on_project_pages_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Classification label on project pages' do + let(:project) do + create(:project, external_authorization_classification_label: 'authorized label') + end + let(:user) { create(:user) } + + before do + stub_application_setting(external_authorization_service_enabled: true) + project.add_maintainer(user) + sign_in(user) + end + + it 'shows the classification label on the project page' do + visit project_path(project) + + expect(page).to have_content('authorized label') + end +end diff --git a/spec/features/projects/forks/fork_list_spec.rb b/spec/features/projects/forks/fork_list_spec.rb new file mode 100644 index 00000000000..2c41c61a660 --- /dev/null +++ b/spec/features/projects/forks/fork_list_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'listing forks of a project' do + include ProjectForksHelper + include ExternalAuthorizationServiceHelpers + + let(:source) { create(:project, :public, :repository) } + let!(:fork) { fork_project(source, nil, repository: true) } + let(:user) { create(:user) } + + before do + source.add_maintainer(user) + sign_in(user) + end + + it 'shows the forked project in the list with commit as description' do + visit project_forks_path(source) + + page.within('li.project-row') do + expect(page).to have_content(fork.full_name) + expect(page).to have_css('a.commit-row-message') + end + end + + it 'does not show the commit message when an external authorization service is used' do + enable_external_authorization_service_check + + visit project_forks_path(source) + + page.within('li.project-row') do + expect(page).to have_content(fork.full_name) + expect(page).not_to have_css('a.commit-row-message') + end + end +end diff --git a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb new file mode 100644 index 00000000000..a8612d77a5e --- /dev/null +++ b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +describe 'viewing an issue with cross project references' do + include ExternalAuthorizationServiceHelpers + include Gitlab::Routing.url_helpers + + let(:user) { create(:user) } + let(:other_project) do + create(:project, :public, + external_authorization_classification_label: 'other_label') + end + let(:other_issue) do + create(:issue, :closed, + title: 'I am in another project', + project: other_project) + end + let(:other_confidential_issue) do + create(:issue, :confidential, :closed, + title: 'I am in another project and confidential', + project: other_project) + end + let(:other_merge_request) do + create(:merge_request, :closed, + title: 'I am a merge request in another project', + source_project: other_project) + end + let(:description_referencing_other_issue) do + "Referencing: #{other_issue.to_reference(project)}, "\ + "a confidential issue #{confidential_issue.to_reference}, "\ + "a cross project confidential issue #{other_confidential_issue.to_reference(project)}, and "\ + "a cross project merge request #{other_merge_request.to_reference(project)}" + end + let(:project) { create(:project) } + let(:issue) do + create(:issue, + project: project, + description: description_referencing_other_issue ) + end + let(:confidential_issue) do + create(:issue, :confidential, :closed, + title: "I am in the same project and confidential", + project: project) + end + + before do + project.add_developer(user) + sign_in(user) + end + + it 'shows all information related to the cross project reference' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_issue.to_reference(project)} (#{other_issue.state})") + expect(page).to have_xpath("//a[@title='#{other_issue.title}']") + end + + it 'shows a link to the confidential issue in the same project' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})") + expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']") + end + + it 'does not show the link to a cross project confidential issue when the user does not have access' do + visit project_issue_path(project, issue) + + expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})") + expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + it 'shows the link to a cross project confidential issue when the user has access' do + other_project.add_developer(user) + + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})") + expect(page).to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + context 'when an external authorization service is enabled' do + before do + enable_external_authorization_service_check + end + + it 'only hits the external service for the project the user is viewing' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'default_label', any_args).at_least(1).and_return(true) + expect(::Gitlab::ExternalAuthorization) + .not_to receive(:access_allowed?).with(user, 'other_label', any_args) + + visit project_issue_path(project, issue) + end + + it 'shows only the link to the cross project references' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_issue.to_reference(project)}") + expect(page).to have_link("#{other_merge_request.to_reference(project)}") + expect(page).not_to have_content("#{other_issue.to_reference(project)} (#{other_issue.state})") + expect(page).not_to have_xpath("//a[@title='#{other_issue.title}']") + expect(page).not_to have_content("#{other_merge_request.to_reference(project)} (#{other_merge_request.state})") + expect(page).not_to have_xpath("//a[@title='#{other_merge_request.title}']") + end + + it 'does not link a cross project confidential issue if the user does not have access' do + visit project_issue_path(project, issue) + + expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)}") + expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + it 'links a cross project confidential issue without exposing information when the user has access' do + other_project.add_developer(user) + + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_confidential_issue.to_reference(project)}") + expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + it 'shows a link to the confidential issue in the same project' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})") + expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']") + end + end +end diff --git a/spec/features/projects/settings/external_authorization_service_settings_spec.rb b/spec/features/projects/settings/external_authorization_service_settings_spec.rb new file mode 100644 index 00000000000..31b2892cf6f --- /dev/null +++ b/spec/features/projects/settings/external_authorization_service_settings_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Projects > Settings > External Authorization Classification Label setting' do + let(:user) { create(:user) } + let(:project) { create(:project_empty_repo) } + + before do + project.add_maintainer(user) + sign_in(user) + end + + it 'shows the field to set a classification label' do + stub_application_setting(external_authorization_service_enabled: true) + + visit edit_project_path(project) + + expect(page).to have_selector('#project_external_authorization_classification_label') + end +end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 86379164cf0..351750c0179 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'User page' do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } context 'with public profile' do @@ -86,4 +88,24 @@ describe 'User page' do end end end + + context 'most recent activity' do + it 'shows the most recent activity' do + visit(user_path(user)) + + expect(page).to have_content('Most Recent Activity') + end + + context 'when external authorization is enabled' do + before do + enable_external_authorization_service_check + end + + it 'hides the most recent activity' do + visit(user_path(user)) + + expect(page).not_to have_content('Most Recent Activity') + end + end + end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index fe53fabe54c..4133987a07e 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -559,6 +559,13 @@ describe IssuesFinder do expect(issues.count).to eq 0 end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:issue, project: project) } + let(:project_params) { { project_id: project.id } } + end + end end describe '#row_count', :request_store do diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index 3f060ba0553..98b4933fef6 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -226,5 +226,12 @@ describe LabelsFinder do expect(finder.execute).to eq [project_label_1] end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:label, project: project) } + let(:project_params) { { project_id: project.id } } + end + end end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index f508b9bdb6f..9d4b9af3ec3 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -253,6 +253,13 @@ describe MergeRequestsFinder do expect(finder.row_count).to eq(1) end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:merge_request, source_project: project) } + let(:project_params) { { project_id: project.id } } + end + end end context 'when projects require different access levels for merge requests' do diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 93287f3e9b8..d367f9015c7 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe SnippetsFinder do + include ExternalAuthorizationServiceHelpers include Gitlab::Allowable describe '#initialize' do @@ -164,4 +165,35 @@ describe SnippetsFinder do end it_behaves_like 'snippet visibility' + + context 'external authorization' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let!(:snippet) { create(:project_snippet, :public, project: project) } + + before do + project.add_maintainer(user) + end + + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:project_snippet, project: project) } + let(:project_params) { { project: project } } + end + + it 'includes the result if the external service allows access' do + external_service_allow_access(user, project) + + results = described_class.new(user, project: project).execute + + expect(results).to contain_exactly(snippet) + end + + it 'does not include any results if the external service denies access' do + external_service_deny_access(user, project) + + results = described_class.new(user, project: project).execute + + expect(results).to be_empty + end + end end diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index d4ed41d54f0..22318a9946a 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -47,6 +47,13 @@ describe TodosFinder do end end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:todo, project: project, user: user) } + let(:project_params) { { project_id: project.id } } + end + end end describe '#sort' do diff --git a/spec/fixtures/passphrase_x509_certificate.crt b/spec/fixtures/passphrase_x509_certificate.crt new file mode 100644 index 00000000000..6973163b79e --- /dev/null +++ b/spec/fixtures/passphrase_x509_certificate.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEpTCCAo0CAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5 +MB4XDTE4MDMyMzE0MDIwOFoXDTE5MDMyMzE0MDIwOFowHTEbMBkGA1UEAwwSZ2l0 +bGFiLXBhc3NwaHJhc2VkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +zpsWHOewP/khfDsLUWxaRCinrBzVJm2C01bVahKVR3g/JD4vEH901Wod9Pvbh/9e +PEfE+YZmgSUUopbL3JUheMnyW416F43HKE/fPW4+QeuIEceuhCXg20eOXmvnWWNM +0hXZh4hq69rwvMPREC/LkZy/QkTDKhJNLNAqAQu2AJ3C7Yga8hFQYEhx1hpfGtwD +z/Nf3efat9WN/d6yW9hfJ98NCmImTm5l9Pc0YPNWCAf96vsqsNHBrTkFy6CQwkhH +K1ynVYuqnHYxSc4FPCT5SAleD9gR/xFBAHb7pPy4yGxMSEmiWaMjjZCVPsghj1jM +Ej77MTDL3U9LeDfiILhvZ+EeQxqPiFwwG2eaIn3ZEs2Ujvw7Z2VpG9VMcPTnB4jK +ot6qPM1YXnkGWQ6iT0DTPS3h7zg1xIJXI5N2sI6GXuKrXXwZ1wPqzFLKPv+xBjp8 +P6dih+EImfReFi9zIO1LqGMY+XmRcqodsb6jzsmBimJkqBtatJM7FuUUUN56wiaj +q9+BWbm+ZdQ2lvqndMljjUjTh6pNERfGAJgkNuLn3X9hXVE0TSpmn0nOgaL5izP3 +7FWUt0PTyGgK2zq9SEhZmK2TKckLkKMk/ZBBBVM/nrnjs72IlbsqdcVoTnApytZr +xVYTj1hV7QlAfaU3w/M534qXDiy8+HfX5ksWQMtSklECAwEAATANBgkqhkiG9w0B +AQUFAAOCAgEAMMhzSRq9PqCpui74nwjhmn8Dm2ky7A+MmoXNtk70cS/HWrjzaacb +B/rxsAUp7f0pj4QMMM0ETMFpbNs8+NPd2FRY0PfWE4yyDpvZO2Oj1HZKLHX72Gjn +K5KB9DYlVsXhGPfuFWXpxGWF2Az9hDWnj58M3DOAps+6tHuAtudQUuwf5ENQZWwE +ySpr7yoHm1ykgl0Tsb9ZHi9qLrWRRMNYXRT+gvwP1bba8j9jOtjO/xYiIskwMPLM +W8SFmQxbg0Cvi8Q89PB6zoTNOhPQyoyeSlw9meeZJHAMK2zxeglEm8C4EQ+I9Y6/ +yylM5/Sc55TjWAvRFgbsq+OozgMvffk/Q2fzcGF44J9DEQ7nrhmJxJ+X4enLknR5 +Hw4+WhdYA+bwjx3YZBNTh9/YMgNPYwQhf5gtcZGTd6X4j6qZfJ6CXBmhkC1Cbfyl +yM7B7i4JAqPWMeDP50pXCgyKlwgw1JuFW+xkbkYQAj7wtggQ6z1Vjb5W8R8kYn9q +LXClVtThEeSV5KkVwNX21aFcUs8qeQ+zsgKqpEyM5oILQQ1gDSxLTtrr2KuN+WJN +wM0acwD45X7gA/aZYpCGkIgHIBq0zIDP1s6IqeebFJjW8lWofhRxOEWomWdRweJG +N7qQ1WCTQxAPGAkDI8QPjaspvnAhFKmpBG/mR5IXLFKDbttu7WNdYDo= +-----END CERTIFICATE----- diff --git a/spec/fixtures/passphrase_x509_certificate_pk.key b/spec/fixtures/passphrase_x509_certificate_pk.key new file mode 100644 index 00000000000..f9760dfe70e --- /dev/null +++ b/spec/fixtures/passphrase_x509_certificate_pk.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,79CCB506B0FD42A6F1BAE6D72E1CB20C + +EuZQOfgaO6LVCNytTHNJmbiq1rbum9xg6ohfBTVt7Cw4+8yLezWva/3sJQtnEk2P +M2yEQYWIiCX+clPkRiRL8WLjRfLTNcYS6QxxuJdpOrowPrBYr4Aig8jBUUBI4VQf +w1ZEUQd0mxQGnyzkKpsudFOntCtZbvbrBsIAQUNLcrKEFk3XW/BqE1Q/ja6WfWqX +b6EKg6DoXi92V90O6sLDfpmTKZq3ThvVDFuWeJ2K/GVp2cs+MkBIBJ8XX+NT1nWg +g+Ok+yaSI/N9ILX4XDgXunJGwcooI8PhHSjkDWRusi8vbo7RFqIKiSF+h6tIwktF +Uss3JESKgXZCQ7upCnHSzK/aWFtwHtXxqOi7esqEZd+1sB0LY+XMnbaxweCMx2Kj +czktKYvoXUs69Whln+yyXULtl5XhJ8lbvlbIG2FbZ9y+/hHOyBqZyeUyCnXDzv8/ +0U0iZwreP3XPVMsy578pIdcdL27q+r05j4yjrJfbX3T9xp2u3F9uVubCa4euEBwV +yrFdsxJLKON8pFeDS49m5gHNsHmeZ0sUeTPZVGNXdabVetkOA0eAAGK4zAoqG79L +hEN7cDenz+E4XHp8gMzwwMiVyU4FuAb6SXkfSodctmSTWVbzNBja0FBek3UXy+pn +9qq7cIpe7NY5gzcbyoy9lSkyYVkAm8j6BIYtY1ZUAmtCklC2ADWARTjd7dI7aEbO +QbXxNIq2+O/zMOXfougSPoDP8SLyLuE1p6SwfWV7Dwf119hn+mjWlGzAZDxxHhsR +yYUQCUe0NIKzuUp3WYIx8xIb7/WFwit/JaFaxurjBnhkkEviBn+TgXiuFBO3tv/d +URpZ39rH0mrDsR61pCiIcoNVkQkynHcAFPd5VtaeSJPvZP280uOCPPS31cr6/0LB +1JX3lZoWWCuA+JQjxtZDaDTcvEUbfOQ2rexQQo4uylNkBF9F5WOdQBkKG/AfqBq8 +S/TdubYzvpcKhFAlXsI67JdbxGlU4HCsxOLwWzSUYclN4W3l7s7KZ5zxt+MU03Uf +vara9uuZHiKUjZohjXeqcXTc+UyC8VH1dF19M3Cj9RNrwl2xEDUMtIiALBjbGp1E +pu2nPj9NhWf9Vw5MtSszutesxXba2nPmvvGvvZ7N3h/k4NsKL7JdENF7XqkI0D2K +jpO1t6d3cazS1VpMWLZS45kWaM3Y07tVR3V+4Iv9Vo1e9H2u/Z5U4YeJ44sgMsct +dBOAhHdUAI5+P+ocLXiCKo+EcS0cKvz+CC4ux0vvcF3JrTqZJN1U/JxRka2EyJ1B +2Xtu3DF36XpBJcs+MJHjJ+kUn6DHYoYxZa+bB8LX6+FQ+G7ue+Dx/RsGlP7if1nq +DAaM6kZg7/FbFzOZyl5xhwAJMxfgNNU7nSbk9lrvQ4mdwgFjvgGu3jlER4+TcleE +4svXInxp1zK6ES44tI9fXkhPaFkafxAL7eUSyjjEwMC06h+FtqK3mmoKLo5NrGJE +zVl69r2WdoSQEylVN1Kbp+U4YbfncInLJqBq2q5w9ASL/8Rhe8b52q6PuVX/bjoz +0pkSu+At4jVbAhRpER5NGlzG884IaqqvBvMYR5zFJeRroIijyUyH0KslK37/sXRk +ty0yKrkm31De9gDa3+XlgAVDAgbEQmGVwVVcV0IYYJbjIf36lUdGh4+3krwxolr/ +vZct5Z7QxfJlBtdOstjz5U9o05yOhjoNrPZJXuKMmWOQjSwr7rRSdqmAABF9IrBf +Pa/ChF1y5j3gJESAFMyiea3kvLq1EbZRaKoybsQE2ctBQ8EQjzUz+OOxVO6GJ4W9 +XHyfcviFrpsVcJEpXQlEtGtKdfKLp48cytob1Fu1JOYPDCrafUQINCZP4H3Nt892 +zZiTmdwux7pbgf4KbONImN5XkpvdCGjQHSkYMmm5ETRK8s7Fmvt2aBPtlyXxJDOq +iJUqwDV5HZXOnQVE/v/yESKgo2Cb8BWqPZ4/8Ubgu/OADYyv/dtjQel8QQ2FMhO4 +2tnwWbBBJk8VpR/vjFHkGSnj+JJfW/vUVQ+06D3wHYhNp7mh4M+37AngwzGCp7k+ +9aFwb2FBGghArB03E4lIO/959T0cX95WZ6tZtLLEsf3+ug7PPOSswCqsoPsXzFJH +MgXVGKFXccNSsWol7VvrX/uja7LC1OE+pZNXxCRzSs4aljJBpvQ6Mty0lk2yBC0R +MdujMoZH9PG9U6stwFd+P17tlGrQdRD3H2uimn82Ck+j2l0z0pzN0JB2WBYEyK0O +1MC36wLICWjgIPLPOxDEEBeZPbc24DCcYfs/F/hSCHv/XTJzVVILCX11ShGPSXlI +FL9qyq6jTNh/pVz6NiN/WhUPBFfOSzLRDyU0MRsSHM8b/HPpf3NOI3Ywmmj65c2k +2kle1F2M5ZTL+XvLS61qLJ/8AgXWvDHP3xWuKGG/pM40CRTUkRW6NAokMr2/pEFw +IHTE2+84dOKnUIEczzMY3aqzNmYDCmhOY0jD/Ieb4hy9tN+1lbQ/msYMIJ1w7CFR +38yB/UbDD90NcuDhjrMbzVUv1At2rW7GM9lSbxGOlYDmtMNEL63md1pQ724v4gSE +mzoFcMkqdh+hjFvv11o4H32lF3mPYcXuL+po76tqxGOiUrLKe/ZqkT5XAclYV/7H +k3Me++PCh4ZqXBRPvR8Xr90NETtiFCkBQXLdhNWXrRe2v0EbSX+cYAWk68FQKCHa +HKTz9T7wAvB6QWBXFhH9iCP8rnQLCEhLEhdrt+4v2KFkIVzBgOlMoHsZsMp0sBeq +c5ZVbJdiKik3P/8ZQTn4jmOnQXCEyWx+LU4acks8Aho4lqq9yKq2DZpwbIRED47E +r7R/NUevhqqzEHZ2SGD6EDqRN+bHJEi64vq0ryaEielusYXZqlnFXDHJcfLCmR5X +3bj5pCwQF4ScTukrGQB/c4henG4vlF4CaD0CIIK3W6tH+AoDohYJts6YK49LGxmK +yXiyKNak8zHYBBoRvd2avRHyGuR5yC9KrN8cbC/kZqMDvAyM65pIK+U7exJwYJhv +ezCcbiH3bK3anpiRpdeNOot2ba/Y+/ks+DRC+xs4QDIhrmSEBCsLv1JbcWjtHSaG +lm+1DSVduUk/kN+fBnlfif+TQV9AP3/wb8ekk8jjKXsL7H1tJKHsLLIIvrgrpxjw +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/x509_certificate.crt b/spec/fixtures/x509_certificate.crt new file mode 100644 index 00000000000..8a84890b928 --- /dev/null +++ b/spec/fixtures/x509_certificate.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEnDCCAoQCAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5 +MB4XDTE4MDMxOTE1MjYzMloXDTE5MDMxOTE1MjYzMlowFDESMBAGA1UEAwwJbG9j +YWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+tcM7iphsLlR +ccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzAn/eVU4jyVWkaBym6MHa8CiDOro9H +OXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/2FAgFWzrB2HnYSShiN8tBeeDI5cJ +ii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UTK37k2kbDQZ41rv1ng2w0AUZt0LRA +NWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ ++1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNkI+cyv0Gle6tk+CkOfE1m0CvNWlNg +b8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5xMXpdUCsh22CZZHe/4SeFE64amkf +1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q/nLdY8haMC6KOtpbAWvKX/Jqq0z1 +nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVrVef0pb2mfdtzjzUrYCP0PtnQExPB +rocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8RSvAoEUs9VbPiUfN7WAyU1K1rTYH +KV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRlq07Q5LDz33h9KXw1LZT8MWRinVJf +RePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEAATANBgkqhkiG9w0BAQUFAAOCAgEA +Skp0tbvVsg3RG2pX0GP25j0ix+f78zG0+BJ6LiKGMoCIBtGKitfUjBg83ru/ILpa +fpgrQpNQVUnGQ9tmpnqV605ZBBRUC1CRDsvUnyN6p7+yQAq6Fl+2ZKONHpPk+Bl4 +CIewgdkHjTwTpvIM/1DFVCz4R1FxNjY3uqOVcNDczMYEk2Pn2GZNNN35hUHHxWh4 +89ZvI+XKuRFZq3cDPA60PySeJJpCRScWGgnkdEX1gTtWH3WUlq9llxIvRexyNyzZ +Yqvcfx5UT75/Pp+JPh9lpUCcKLHeUiadjkiLxu3IcrYa4gYx4lA8jgm7adNEahd0 +oMAHoO9DU6XMo7o6tnQH3xQv9RAbQanjuyJR9N7mwmc59bQ6mW+pxCk843GwT73F +slseJ1nE1fQQQD7mn/KGjmeWtxY2ElUjTay9ff9/AgJeQYRW+oH0cSdo8WCpc2+G ++LZtLWfBgFLHseRlmarSe2pP8KmbaTd3q7Bu0GekVQOxYcNX59Pj4muQZDVLh8aX +mSQ+Ifts/ljT649MISHn2AZMR4+BUx63tFcatQhbAGGH5LeFdbaGcaVdsUVyZ9a2 +HBmFWNsgEPtcC+WmNzCXbv7jQsLAJXufKG5MnurJgNf/n5uKCmpGsEJDT/KF1k/3 +x9YnqM7zTyV6un+LS3HjEJvwQmqPWe+vFAeXWGCoWxE= +-----END CERTIFICATE----- diff --git a/spec/fixtures/x509_certificate_pk.key b/spec/fixtures/x509_certificate_pk.key new file mode 100644 index 00000000000..c02a3cf6189 --- /dev/null +++ b/spec/fixtures/x509_certificate_pk.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEA+tcM7iphsLlRccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzA +n/eVU4jyVWkaBym6MHa8CiDOro9HOXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/ +2FAgFWzrB2HnYSShiN8tBeeDI5cJii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UT +K37k2kbDQZ41rv1ng2w0AUZt0LRANWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK +88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ+1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNk +I+cyv0Gle6tk+CkOfE1m0CvNWlNgb8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5 +xMXpdUCsh22CZZHe/4SeFE64amkf1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q +/nLdY8haMC6KOtpbAWvKX/Jqq0z1nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVr +Vef0pb2mfdtzjzUrYCP0PtnQExPBrocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8 +RSvAoEUs9VbPiUfN7WAyU1K1rTYHKV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRl +q07Q5LDz33h9KXw1LZT8MWRinVJfRePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEA +AQKCAgBf1urJ1Meeji/gGETVx9qBWLbDjn9QTayZSyyEd78155tDShIPDLmxQRHW +MGIReo/5FGSkOgS+DWBZRZ77oGOGrtuMnjkheXhDr8dZvw5b1PBv5ntqWrLnfMYP +/Ag7xZMyiJLbPqmMX5j1gsFt8zPzUoVMnnl9DYryV0Edrs/utHgfJCM+6yzleUQB +PkGkqo1yWVVFZ3Nt2nDt9dNsdlC594+dYQ1m2JuArNvYNiw3dpHT98GnhRc1aLh4 +U+q22FiFn3BKGQat43JdlaLa6KO5f8MIQRYWuI8tss2DGPlhRv9AnUcVsLBjAuIH +bmUVrBosxCYUQ6giatjd2sZPfdC+VIDCbIWRthxkXJ9I/Ap8R98xx/7qIcPFc+XA +hcK1xOM7zIq2xgAOFeeh8O8Wq9cH8NmUhMCgzIE0WT32Zo0JAW6l0kZc82Y/Yofz +U+TJKo0NOFZe687HOhanOHbbQSG29XOqxMYTABZ7Ixf+4RZPD5+yQgZWP1BhLluy +PxZhsLl67xvbfB2i9VVorMN7PbFx5hbni3C7/p63Z0rG5q4/uJBbX3Uuh6KdhIo+ +Zh9UC6u29adIthdxz+ZV5wBccTOgaeHB9wRL9Hbp6ZxyqesQB4RTsFtPNXxZ7K43 +fmJgHZvHhF5gSbeB8JAeBf0cy3pytJM49ZxplifeGVzUJP2gAQKCAQEA/1T9quz5 +sOD03FxV//oRWD1kqfunq3v56sIBG4ZMVZKUqc6wLjTmeklLYKq85AWX8gnCHi0g +nmG/xDh/rt1/IngMWP98WVuD67hFbrj87g7A7YGIiwZ2gi6hqhqmALN+5JjCSTPp +XOiPvNnXP0XM4gIHBXV8diHq5rF9NsSh4vx3OExr8KQqVzWoDcnnWNfnDlrFB8cq +ViII+UqdovXp59hAVOsc+pYAe+8JeQDX17H3U/NMkUw4gU2aWUCvUVjxi9oBG/CW +ncIdYuW8zne4qXbX7YLC0QUUIDVOWzhLauAUBduTqRTldJo0KAxu887tf+uStXs8 +RACLGIaBQw7BXQKCAQEA+38NFnpflKquU92xRtmqWAVaW7rm865ZO6EIaS4JII/N +/Ebu1YZrAhT0ruGJQaolYj8w79BEZRF2CYDPZxKFv/ye0O7rWCAGtCdWQ0BXcrIU +7SdlsdfTNXO1R3WbwCyVxyjg6YF7FjbTaaOAoTiosTjDs2ZOgkbdh/sMeWkSN5HB +aQz4c8rqq0kkYucLqp4nWYSWSJn88bL8ctwEwW77MheJiSpo1ohNRP3ExHnbCbYw +RIj7ATSz74ebpd9NMauB5clvMMh4jRG0EQyt7KCoOyfPRFc3fddvTr03LlgFfX/n +qoxd2nejgAS3NnG1XMxdcUa7cPannt46Sef1uZo3gQKCAQB454zquCYQDKXGBu8u +NAKsjv2wxBqESENyV4VgvDo/NxawRdAFQUV12GkaEB87ti5aDSbfVS0h8lV1G+/S +JM5DyybFqcz/Hyebofk20d/q9g+DJ5g5hMjvIhepTc8Xe+d1ZaRyN2Oke/c8TMbx +DiNTTfR3MEfMRIlPzfHl0jx6GGR3wzBFleb6vsyiIt4qoqmlkXPFGBlDCgDH0v5M +ITgucacczuw8+HSoOut4Yd7TI1FjbkzubHJBQDb7VnbuBTjzqTpnOYiIkVeK8hBy +kBxgGodqz0Vi5o2+Jp/A8Co+JHc2wt/r65ovmali4WhUiMLLlQg2aXGDHeK/rUle +MIl9AoIBAQCPKCYSCnyHypRK5uG3W8VsLzfdCUnXogHnQGXiQTMu1szA8ruWzdnx +qG4TcgxIVYrMHv5DNAEKquLOzATDPjbmLu1ULvvGAQzv1Yhz5ZchkZ7507g+gIUY +YxHoaFjNDlP/txQ3tt2SqoizFD/vBap4nsA/SVgdLiuB8PSL07Rr70rx+lEe0H2+ +HHda2Pu6FiZ9/Uvybb0e8+xhkT4fwYW5YM6IRpzAqXuabv1nfZmiMJPPH04JxK88 +BKwjwjVVtbPOUlg5o5ODcXVXUylZjaXVbna8Bw1uU4hngKt9dNtDMeB0I0x1RC7M +e2Ky2g0LksUJ6uJdjfmiJAt38FLeYJuBAoIBAC2oqaqr86Dug5v8xHpgFoC5u7z7 +BRhaiHpVrUr+wnaNJEXfAEmyKf4xF5xDJqldnYG3c9ETG/7bLcg1dcrMPzXx94Si +MI3ykwiPeI/sVWYmUlq4U8zCIC7MY6sWzWt3oCBNoCN/EeYx9e7+eLNBB+fADAXq +v9RMGlUIy7beX0uac8Bs771dsxIb/RrYw58wz+jrwGlzuDmcPWiu+ARu7hnBqCAV +AITlCV/tsEk7u08oBuv47+rVGCh1Qb19pNswyTtTZARAGErJO0Q+39BNuu0M2TIn +G3M8eNmGHC+mNsZTVgKRuyk9Ye0s4Bo0KcqSndiPFGHjcrF7/t+RqEOXr/E= +-----END RSA PRIVATE KEY----- diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb new file mode 100644 index 00000000000..5dc2521b310 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/access_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do + subject(:access) { described_class.new(build(:user), 'dummy_label') } + + describe '#loaded?' do + it 'is `true` when it was loaded recently' do + Timecop.freeze do + allow(access).to receive(:loaded_at).and_return(5.minutes.ago) + + expect(access).to be_loaded + end + end + + it 'is `false` when there is no loading time' do + expect(access).not_to be_loaded + end + + it 'is `false` when there the result was loaded a long time ago' do + Timecop.freeze do + allow(access).to receive(:loaded_at).and_return(2.weeks.ago) + + expect(access).not_to be_loaded + end + end + end + + describe 'load!' do + let(:fake_client) { double('ExternalAuthorization::Client') } + let(:fake_response) do + double( + 'Response', + 'successful?' => true, + 'valid?' => true, + 'reason' => nil + ) + end + + before do + allow(access).to receive(:load_from_cache) + allow(fake_client).to receive(:request_access).and_return(fake_response) + allow(Gitlab::ExternalAuthorization::Client).to receive(:new) { fake_client } + end + + context 'when loading from the webservice' do + it 'loads from the webservice it the cache was empty' do + expect(access).to receive(:load_from_cache) + expect(access).to receive(:load_from_service).and_call_original + + access.load! + + expect(access).to be_loaded + end + + it 'assigns the accessibility, reason and loaded_at' do + allow(fake_response).to receive(:successful?).and_return(false) + allow(fake_response).to receive(:reason).and_return('Inaccessible label') + + access.load! + + expect(access.reason).to eq('Inaccessible label') + expect(access).not_to have_access + expect(access.loaded_at).not_to be_nil + end + + it 'returns itself' do + expect(access.load!).to eq(access) + end + + it 'stores the result in redis' do + Timecop.freeze do + fake_cache = double + expect(fake_cache).to receive(:store).with(true, nil, Time.now) + expect(access).to receive(:cache).and_return(fake_cache) + + access.load! + end + end + + context 'when the request fails' do + before do + allow(fake_client).to receive(:request_access) do + raise ::Gitlab::ExternalAuthorization::RequestFailed.new('Service unavailable') + end + end + + it 'is loaded' do + access.load! + + expect(access).to be_loaded + end + + it 'assigns the correct accessibility, reason and loaded_at' do + access.load! + + expect(access.reason).to eq('Service unavailable') + expect(access).not_to have_access + expect(access.loaded_at).not_to be_nil + end + + it 'does not store the result in redis' do + fake_cache = double + expect(fake_cache).not_to receive(:store) + allow(access).to receive(:cache).and_return(fake_cache) + + access.load! + end + end + end + + context 'When loading from cache' do + let(:fake_cache) { double('ExternalAuthorization::Cache') } + + before do + allow(access).to receive(:cache).and_return(fake_cache) + end + + it 'does not load from the webservice' do + Timecop.freeze do + expect(fake_cache).to receive(:load).and_return([true, nil, Time.now]) + + expect(access).to receive(:load_from_cache).and_call_original + expect(access).not_to receive(:load_from_service) + + access.load! + end + end + + it 'loads from the webservice when the cached result was too old' do + Timecop.freeze do + expect(fake_cache).to receive(:load).and_return([true, nil, 2.days.ago]) + + expect(access).to receive(:load_from_cache).and_call_original + expect(access).to receive(:load_from_service).and_call_original + allow(fake_cache).to receive(:store) + + access.load! + end + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb new file mode 100644 index 00000000000..58e7d626707 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/cache_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do + let(:user) { build_stubbed(:user) } + let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" } + + subject(:cache) { described_class.new(user, 'dummy_label') } + + def read_from_redis(key) + Gitlab::Redis::Cache.with do |redis| + redis.hget(cache_key, key) + end + end + + def set_in_redis(key, value) + Gitlab::Redis::Cache.with do |redis| + redis.hmset(cache_key, key, value) + end + end + + describe '#load' do + it 'reads stored info from redis' do + Timecop.freeze do + set_in_redis(:access, false) + set_in_redis(:reason, 'Access denied for now') + set_in_redis(:refreshed_at, Time.now) + + access, reason, refreshed_at = cache.load + + expect(access).to eq(false) + expect(reason).to eq('Access denied for now') + expect(refreshed_at).to be_within(1.second).of(Time.now) + end + end + end + + describe '#store' do + it 'sets the values in redis' do + Timecop.freeze do + cache.store(true, 'the reason', Time.now) + + expect(read_from_redis(:access)).to eq('true') + expect(read_from_redis(:reason)).to eq('the reason') + expect(read_from_redis(:refreshed_at)).to eq(Time.now.to_s) + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb new file mode 100644 index 00000000000..fa18c1e56e8 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/client_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Client do + let(:user) { build(:user, email: 'dummy_user@example.com') } + let(:dummy_url) { 'https://dummy.net/' } + subject(:client) { described_class.new(user, 'dummy_label') } + + before do + stub_application_setting(external_authorization_service_url: dummy_url) + end + + describe '#request_access' do + it 'performs requests to the configured endpoint' do + expect(Excon).to receive(:post).with(dummy_url, any_args) + + client.request_access + end + + it 'adds the correct params for the user to the body of the request' do + expected_body = { + user_identifier: 'dummy_user@example.com', + project_classification_label: 'dummy_label' + }.to_json + expect(Excon).to receive(:post) + .with(dummy_url, hash_including(body: expected_body)) + + client.request_access + end + + it 'respects the the timeout' do + stub_application_setting( + external_authorization_service_timeout: 3 + ) + + expect(Excon).to receive(:post).with(dummy_url, + hash_including( + connect_timeout: 3, + read_timeout: 3, + write_timeout: 3 + )) + + client.request_access + end + + it 'adds the mutual tls params when they are present' do + stub_application_setting( + external_auth_client_cert: 'the certificate data', + external_auth_client_key: 'the key data', + external_auth_client_key_pass: 'open sesame' + ) + expected_params = { + client_cert_data: 'the certificate data', + client_key_data: 'the key data', + client_key_pass: 'open sesame' + } + + expect(Excon).to receive(:post).with(dummy_url, hash_including(expected_params)) + + client.request_access + end + + it 'returns an expected response' do + expect(Excon).to receive(:post) + + expect(client.request_access) + .to be_kind_of(::Gitlab::ExternalAuthorization::Response) + end + + it 'wraps exceptions if the request fails' do + expect(Excon).to receive(:post) { raise Excon::Error.new('the request broke') } + + expect { client.request_access } + .to raise_error(::Gitlab::ExternalAuthorization::RequestFailed) + end + + describe 'for ldap users' do + let(:user) do + create(:omniauth_user, + email: 'dummy_user@example.com', + extern_uid: 'external id', + provider: 'ldapprovider') + end + + it 'includes the ldap dn for ldap users' do + expected_body = { + user_identifier: 'dummy_user@example.com', + project_classification_label: 'dummy_label', + user_ldap_dn: 'external id' + }.to_json + expect(Excon).to receive(:post) + .with(dummy_url, hash_including(body: expected_body)) + + client.request_access + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb new file mode 100644 index 00000000000..81f1b2390e6 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/logger_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Logger do + let(:request_time) { Time.parse('2018-03-26 20:22:15') } + + def fake_access(has_access, user, load_type = :request) + access = double('access') + allow(access).to receive_messages(user: user, + has_access?: has_access, + loaded_at: request_time, + label: 'dummy_label', + load_type: load_type) + + access + end + + describe '.log_access' do + it 'logs a nice message for an access request' do + expected_message = "GRANTED admin@example.com access to 'dummy_label' (the/project/path)" + fake_access = fake_access(true, build(:user, email: 'admin@example.com')) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, 'the/project/path') + end + + it 'does not trip without a project path' do + expected_message = "DENIED admin@example.com access to 'dummy_label'" + fake_access = fake_access(false, build(:user, email: 'admin@example.com')) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, nil) + end + + it 'adds the load time for cached accesses' do + expected_message = "DENIED admin@example.com access to 'dummy_label' - cache #{request_time}" + fake_access = fake_access(false, build(:user, email: 'admin@example.com'), :cache) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, nil) + end + end +end diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb new file mode 100644 index 00000000000..43211043eca --- /dev/null +++ b/spec/lib/gitlab/external_authorization/response_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Response do + let(:excon_response) { double } + subject(:response) { described_class.new(excon_response) } + + describe '#valid?' do + it 'is valid for 200, 401, and 403 responses' do + [200, 401, 403].each do |status| + allow(excon_response).to receive(:status).and_return(status) + + expect(response).to be_valid + end + end + + it "is invalid for other statuses" do + expect(excon_response).to receive(:status).and_return(500) + + expect(response).not_to be_valid + end + end + + describe '#reason' do + it 'returns a reason if it was included in the response body' do + expect(excon_response).to receive(:body).and_return({ reason: 'Not authorized' }.to_json) + + expect(response.reason).to eq('Not authorized') + end + + it 'returns nil when there was no body' do + expect(excon_response).to receive(:body).and_return('') + + expect(response.reason).to eq(nil) + end + end + + describe '#successful?' do + it 'is `true` if the status is 200' do + allow(excon_response).to receive(:status).and_return(200) + + expect(response).to be_successful + end + + it 'is `false` if the status is 401 or 403' do + [401, 403].each do |status| + allow(excon_response).to receive(:status).and_return(status) + + expect(response).not_to be_successful + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb new file mode 100644 index 00000000000..7394fbfe0ce --- /dev/null +++ b/spec/lib/gitlab/external_authorization_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization, :request_store do + include ExternalAuthorizationServiceHelpers + + let(:user) { build(:user) } + let(:label) { 'dummy_label' } + + describe '#access_allowed?' do + it 'is always true when the feature is disabled' do + # Not using `stub_application_setting` because the method is prepended in + # `EE::ApplicationSetting` which breaks when using `any_instance` + # https://gitlab.com/gitlab-org/gitlab-ce/issues/33587 + expect(::Gitlab::CurrentSettings.current_application_settings) + .to receive(:external_authorization_service_enabled) { false } + + expect(described_class).not_to receive(:access_for_user_to_label) + + expect(described_class.access_allowed?(user, label)).to be_truthy + end + end + + describe '#rejection_reason' do + it 'is always nil when the feature is disabled' do + expect(::Gitlab::CurrentSettings.current_application_settings) + .to receive(:external_authorization_service_enabled) { false } + + expect(described_class).not_to receive(:access_for_user_to_label) + + expect(described_class.rejection_reason(user, label)).to be_nil + end + end + + describe '#access_for_user_to_label' do + it 'only loads the access once per request' do + enable_external_authorization_service_check + + expect(::Gitlab::ExternalAuthorization::Access) + .to receive(:new).with(user, label).once.and_call_original + + 2.times { described_class.access_for_user_to_label(user, label, nil) } + end + + it 'logs the access request once per request' do + expect(::Gitlab::ExternalAuthorization::Logger) + .to receive(:log_access) + .with(an_instance_of(::Gitlab::ExternalAuthorization::Access), + 'the/project/path') + .once + + 2.times { described_class.access_for_user_to_label(user, label, 'the/project/path') } + end + end +end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index d0ed588f05f..30bb58ac990 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -496,6 +496,7 @@ Project: - merge_requests_ff_only_enabled - merge_requests_rebase_enabled - jobs_cache_index +- external_authorization_classification_label - pages_https_only Author: - name diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index c81572d739e..c7d7dbac736 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ApplicationSetting do - let(:setting) { described_class.create_from_defaults } + subject(:setting) { described_class.create_from_defaults } it { include(CacheableAttributes) } it { include(ApplicationSettingImplementation) } @@ -284,6 +284,52 @@ describe ApplicationSetting do expect(subject).to be_valid end end + + describe 'when external authorization service is enabled' do + before do + setting.external_authorization_service_enabled = true + end + + it { is_expected.not_to allow_value('not a URL').for(:external_authorization_service_url) } + it { is_expected.to allow_value('https://example.com').for(:external_authorization_service_url) } + it { is_expected.to allow_value('').for(:external_authorization_service_url) } + it { is_expected.not_to allow_value(nil).for(:external_authorization_service_default_label) } + it { is_expected.not_to allow_value(11).for(:external_authorization_service_timeout) } + it { is_expected.not_to allow_value(0).for(:external_authorization_service_timeout) } + it { is_expected.not_to allow_value('not a certificate').for(:external_auth_client_cert) } + it { is_expected.to allow_value('').for(:external_auth_client_cert) } + it { is_expected.to allow_value('').for(:external_auth_client_key) } + + context 'when setting a valid client certificate for external authorization' do + let(:certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') } + + before do + setting.external_auth_client_cert = certificate_data + end + + it 'requires a valid client key when a certificate is set' do + expect(setting).not_to allow_value('fefefe').for(:external_auth_client_key) + end + + it 'requires a matching certificate' do + other_private_key = File.read('spec/fixtures/x509_certificate_pk.key') + + expect(setting).not_to allow_value(other_private_key).for(:external_auth_client_key) + end + + it 'the credentials are valid when the private key can be read and matches the certificate' do + tls_attributes = [:external_auth_client_key_pass, + :external_auth_client_key, + :external_auth_client_cert] + setting.external_auth_client_key = File.read('spec/fixtures/passphrase_x509_certificate_pk.key') + setting.external_auth_client_key_pass = '5iveL!fe' + + setting.validate + + expect(setting.errors).not_to include(*tls_attributes) + end + end + end end context 'restrict creating duplicates' do diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb index 94798f0590d..f63ad958ed3 100644 --- a/spec/models/concerns/protected_ref_access_spec.rb +++ b/spec/models/concerns/protected_ref_access_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe ProtectedRefAccess do + include ExternalAuthorizationServiceHelpers + subject(:protected_ref_access) do create(:protected_branch, :maintainers_can_push).push_access_levels.first end @@ -29,5 +31,15 @@ describe ProtectedRefAccess do expect(protected_ref_access.check_access(developer)).to be_falsy end + + context 'external authorization' do + it 'is false if external authorization denies access' do + maintainer = create(:user) + project.add_maintainer(maintainer) + external_service_deny_access(maintainer, project) + + expect(protected_ref_access.check_access(maintainer)).to be_falsey + end + end end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 892dd053e39..0cd69cb4817 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Issue do + include ExternalAuthorizationServiceHelpers + describe "Associations" do it { is_expected.to belong_to(:milestone) } it { is_expected.to have_many(:assignees) } @@ -779,4 +781,47 @@ describe Issue do it_behaves_like 'throttled touch' do subject { create(:issue, updated_at: 1.hour.ago) } end + + context 'when an external authentication service' do + before do + enable_external_authorization_service_check + end + + describe '#visible_to_user?' do + it 'is `false` when an external authorization service is enabled' do + issue = build(:issue, project: build(:project, :public)) + + expect(issue).not_to be_visible_to_user + end + + it 'checks the external service to determine if an issue is readable by a user' do + project = build(:project, :public, + external_authorization_classification_label: 'a-label') + issue = build(:issue, project: project) + user = build(:user) + + expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).with(user, 'a-label') { false } + expect(issue.visible_to_user?(user)).to be_falsy + end + + it 'does not check the external service if a user does not have access to the project' do + project = build(:project, :private, + external_authorization_classification_label: 'a-label') + issue = build(:issue, project: project) + user = build(:user) + + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + expect(issue.visible_to_user?(user)).to be_falsy + end + + it 'does not check the external webservice for admins' do + issue = build(:issue) + user = build(:admin) + + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + issue.visible_to_user?(user) + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5eb31430ccd..7222580e115 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Project do include ProjectForksHelper include GitHelpers + include ExternalAuthorizationServiceHelpers it_behaves_like 'having unique enum values' @@ -4417,6 +4418,25 @@ describe Project do end end + describe '#external_authorization_classification_label' do + it 'falls back to the default when none is configured' do + enable_external_authorization_service_check + + expect(build(:project).external_authorization_classification_label) + .to eq('default_label') + end + + it 'returns the classification label if it was configured on the project' do + enable_external_authorization_service_check + + project = build(:project, + external_authorization_classification_label: 'hello') + + expect(project.external_authorization_classification_label) + .to eq('hello') + end + end + describe "#pages_https_only?" do subject { build(:project) } diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb index c03d95b34db..09be831dcd5 100644 --- a/spec/policies/base_policy_spec.rb +++ b/spec/policies/base_policy_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe BasePolicy do + include ExternalAuthorizationServiceHelpers + describe '.class_for' do it 'detects policy class based on the subject ancestors' do expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy) @@ -16,4 +18,25 @@ describe BasePolicy do expect(DeclarativePolicy.class_for(:global)).to eq(GlobalPolicy) end end + + describe 'read cross project' do + let(:current_user) { create(:user) } + let(:user) { create(:user) } + + subject { described_class.new(current_user, [user]) } + + it { is_expected.to be_allowed(:read_cross_project) } + + context 'when an external authorization service is enabled' do + before do + enable_external_authorization_service_check + end + + it { is_expected.not_to be_allowed(:read_cross_project) } + + it 'allows admins' do + expect(described_class.new(build(:admin), nil)).to be_allowed(:read_cross_project) + end + end + end end diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index 008d118b557..b149dbcf871 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe IssuePolicy do + include ExternalAuthorizationServiceHelpers + let(:guest) { create(:user) } let(:author) { create(:user) } let(:assignee) { create(:user) } @@ -204,4 +206,21 @@ describe IssuePolicy do end end end + + context 'with external authorization enabled' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:policies) { described_class.new(user, issue) } + + before do + enable_external_authorization_service_check + end + + it 'can read the issue iid without accessing the external service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(policies).to be_allowed(:read_issue_iid) + end + end end diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb index 1efa70addc2..81279225d61 100644 --- a/spec/policies/merge_request_policy_spec.rb +++ b/spec/policies/merge_request_policy_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe MergeRequestPolicy do + include ExternalAuthorizationServiceHelpers + let(:guest) { create(:user) } let(:author) { create(:user) } let(:developer) { create(:user) } @@ -47,4 +49,21 @@ describe MergeRequestPolicy do expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request) end end + + context 'with external authorization enabled' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:policies) { described_class.new(user, merge_request) } + + before do + enable_external_authorization_service_check + end + + it 'can read the issue iid without accessing the external service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(policies).to be_allowed(:read_merge_request_iid) + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 125ed818bc6..42f8bf3137b 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe ProjectPolicy do + include ExternalAuthorizationServiceHelpers include_context 'ProjectPolicy context' set(:guest) { create(:user) } set(:reporter) { create(:user) } @@ -292,4 +293,56 @@ describe ProjectPolicy do projects: [clusterable]) end end + + context 'reading a project' do + it 'allows access when a user has read access to the repo' do + expect(described_class.new(owner, project)).to be_allowed(:read_project) + expect(described_class.new(developer, project)).to be_allowed(:read_project) + expect(described_class.new(admin, project)).to be_allowed(:read_project) + end + + it 'never checks the external service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(described_class.new(owner, project)).to be_allowed(:read_project) + end + + context 'with an external authorization service' do + before do + enable_external_authorization_service_check + end + + it 'allows access when the external service allows it' do + external_service_allow_access(owner, project) + external_service_allow_access(developer, project) + + expect(described_class.new(owner, project)).to be_allowed(:read_project) + expect(described_class.new(developer, project)).to be_allowed(:read_project) + end + + it 'does not check the external service for admins and allows access' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(described_class.new(admin, project)).to be_allowed(:read_project) + end + + it 'prevents all but seeing a public project in a list when access is denied' do + [developer, owner, build(:user), nil].each do |user| + external_service_deny_access(user, project) + policy = described_class.new(user, project) + + expect(policy).not_to be_allowed(:read_project) + expect(policy).not_to be_allowed(:owner_access) + expect(policy).not_to be_allowed(:change_namespace) + end + end + + it 'passes the full path to external authorization for logging purposes' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(owner, 'default_label', project.full_path).and_call_original + + described_class.new(owner, project).allowed?(:read_project) + end + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 2bfb17d9c9a..352ea448c00 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -46,6 +46,8 @@ shared_examples 'languages and percentages JSON response' do end describe API::Projects do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } @@ -1336,6 +1338,39 @@ describe API::Projects do end end end + + context 'with external authorization' do + let(:project) do + create(:project, + namespace: user.namespace, + external_authorization_classification_label: 'the-label') + end + + context 'when the user has access to the project' do + before do + external_service_allow_access(user, project) + end + + it 'includes the label in the response' do + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['external_authorization_classification_label']).to eq('the-label') + end + end + + context 'when the external service denies access' do + before do + external_service_deny_access(user, project) + end + + it 'returns a 404' do + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(404) + end + end + end end describe 'GET /projects/:id/users' do @@ -1890,6 +1925,20 @@ describe API::Projects do expect(response).to have_gitlab_http_status(403) end end + + context 'when updating external classification' do + before do + enable_external_authorization_service_check + end + + it 'updates the classification label' do + put(api("/projects/#{project.id}", user), params: { external_authorization_classification_label: 'new label' }) + + expect(response).to have_gitlab_http_status(200) + + expect(project.reload.external_authorization_classification_label).to eq('new label') + end + end end describe 'POST /projects/:id/archive' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index f33eb5b9e02..2d6b2d5da23 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -114,6 +114,39 @@ describe API::Settings, 'Settings' do expect(json_response['performance_bar_allowed_group_id']).to be_nil end + context 'external policy classification settings' do + let(:settings) do + { + external_authorization_service_enabled: true, + external_authorization_service_url: 'https://custom.service/', + external_authorization_service_default_label: 'default', + external_authorization_service_timeout: 9.99, + external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'), + external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'), + external_auth_client_key_pass: "5iveL!fe" + } + end + let(:attribute_names) { settings.keys.map(&:to_s) } + + it 'includes the attributes in the API' do + get api("/application/settings", admin) + + expect(response).to have_gitlab_http_status(200) + attribute_names.each do |attribute| + expect(json_response.keys).to include(attribute) + end + end + + it 'allows updating the settings' do + put api("/application/settings", admin), params: settings + + expect(response).to have_gitlab_http_status(200) + settings.each do |attribute, value| + expect(ApplicationSetting.current.public_send(attribute)).to eq(value) + end + end + end + context "missing plantuml_url value when plantuml_enabled is true" do it "returns a blank parameter error message" do put api("/application/settings", admin), params: { plantuml_enabled: true } diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb index d02b4c554b1..b58d95ccb43 100644 --- a/spec/serializers/group_child_entity_spec.rb +++ b/spec/serializers/group_child_entity_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe GroupChildEntity do + include ExternalAuthorizationServiceHelpers include Gitlab::Routing.url_helpers let(:user) { create(:user) } @@ -109,4 +110,22 @@ describe GroupChildEntity do it_behaves_like 'group child json' end + + describe 'for a project with external authorization enabled' do + let(:object) do + create(:project, :with_avatar, + description: 'Awesomeness') + end + + before do + enable_external_authorization_service_check + object.add_maintainer(user) + end + + it 'does not hit the external authorization service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(json[:can_edit]).to eq(false) + end + end end diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index a4a733eff77..258e5635113 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe ApplicationSettings::UpdateService do + include ExternalAuthorizationServiceHelpers + let(:application_settings) { create(:application_setting) } let(:admin) { create(:user, :admin) } let(:params) { {} } @@ -143,4 +145,37 @@ describe ApplicationSettings::UpdateService do end end end + + context 'when external authorization is enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not save the settings with an error if the service denies access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(admin, 'new-label') { false } + + described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute + + expect(application_settings.errors[:external_authorization_service_default_label]).to be_present + end + + it 'saves the setting when the user has access to the label' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(admin, 'new-label') { true } + + described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute + + # Read the attribute directly to avoid the stub from + # `enable_external_authorization_service_check` + expect(application_settings[:external_authorization_service_default_label]).to eq('new-label') + end + + it 'does not validate the label if it was not passed' do + expect(::Gitlab::ExternalAuthorization) + .not_to receive(:access_allowed?) + + described_class.new(application_settings, admin, { home_page_url: 'http://foo.bar' }).execute + end + end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 9ba4a11104a..794a4b4ccd3 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe NotificationService, :mailer do include EmailSpec::Matchers + include ExternalAuthorizationServiceHelpers include NotificationHelpers let(:notification) { described_class.new } @@ -2217,6 +2218,46 @@ describe NotificationService, :mailer do end end + context 'with external authorization service' do + let(:issue) { create(:issue) } + let(:project) { issue.project } + let(:note) { create(:note, noteable: issue, project: project) } + let(:member) { create(:user) } + + subject { NotificationService.new } + + before do + project.add_maintainer(member) + member.global_notification_setting.update!(level: :watch) + end + + it 'sends email when the service is not enabled' do + expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original + + subject.new_issue(issue, member) + end + + context 'when the service is enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not send an email' do + expect(Notify).not_to receive(:new_issue_email) + + subject.new_issue(issue, member) + end + + it 'still delivers email to admins' do + member.update!(admin: true) + + expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original + + subject.new_issue(issue, member) + end + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e8418b09dc2..e1ec932918e 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Projects::CreateService, '#execute' do + include ExternalAuthorizationServiceHelpers include GitHelpers let(:gitlab_shell) { Gitlab::Shell.new } @@ -344,6 +345,42 @@ describe Projects::CreateService, '#execute' do expect(rugged.config['gitlab.fullpath']).to eq project.full_path end + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not save the project with an error if the service denies access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label', any_args) { false } + + project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' })) + + expect(project.errors[:external_authorization_classification_label]).to be_present + expect(project).not_to be_persisted + end + + it 'saves the project when the user has access to the label' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label', any_args) { true } + + project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' })) + + expect(project).to be_persisted + expect(project.external_authorization_classification_label).to eq('new-label') + end + + it 'does not save the project when the user has no access to the default label and no label is provided' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'default_label', any_args) { false } + + project = create_project(user, opts) + + expect(project.errors[:external_authorization_classification_label]).to be_present + expect(project).not_to be_persisted + end + end + def create_project(user, opts) Projects::CreateService.new(user, opts).execute end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 90eaea9c872..95eb17b5e3a 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Projects::UpdateService do + include ExternalAuthorizationServiceHelpers include ProjectForksHelper let(:user) { create(:user) } @@ -361,6 +362,46 @@ describe Projects::UpdateService do call_service end end + + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not save the project with an error if the service denies access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label') { false } + + result = update_project(project, user, { external_authorization_classification_label: 'new-label' }) + + expect(result[:message]).to be_present + expect(result[:status]).to eq(:error) + end + + it 'saves the new label if the service allows access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label') { true } + + result = update_project(project, user, { external_authorization_classification_label: 'new-label' }) + + expect(result[:status]).to eq(:success) + expect(project.reload.external_authorization_classification_label).to eq('new-label') + end + + it 'checks the default label when the classification label was cleared' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'default_label') { true } + + update_project(project, user, { external_authorization_classification_label: '' }) + end + + it 'does not check the label when it does not change' do + expect(::Gitlab::ExternalAuthorization) + .not_to receive(:access_allowed?) + + update_project(project, user, { name: 'New name' }) + end + end end describe '#run_auto_devops_pipeline?' do diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb new file mode 100644 index 00000000000..79dd9a3d58e --- /dev/null +++ b/spec/support/external_authorization_service_helpers.rb @@ -0,0 +1,33 @@ +module ExternalAuthorizationServiceHelpers + def enable_external_authorization_service_check + stub_application_setting(external_authorization_service_enabled: true) + + stub_application_setting(external_authorization_service_url: 'https://authorize.me') + stub_application_setting(external_authorization_service_default_label: 'default_label') + stub_request(:post, "https://authorize.me").to_return(status: 200) + end + + def external_service_set_access(allowed, user, project) + enable_external_authorization_service_check + classification_label = ::Gitlab::CurrentSettings.current_application_settings + .external_authorization_service_default_label + + # Reload the project so cached licensed features are reloaded + if project + classification_label = Project.find(project.id).external_authorization_classification_label + end + + allow(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?) + .with(user, classification_label, any_args) + .and_return(allowed) + end + + def external_service_allow_access(user, project = nil) + external_service_set_access(true, user, project) + end + + def external_service_deny_access(user, project = nil) + external_service_set_access(false, user, project) + end +end diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb new file mode 100644 index 00000000000..8dd78fd0a25 --- /dev/null +++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +shared_examples 'disabled when using an external authorization service' do + include ExternalAuthorizationServiceHelpers + + it 'works when the feature is not enabled' do + subject + + expect(response).to be_success + end + + it 'renders a 404 with a message when the feature is enabled' do + enable_external_authorization_service_check + + subject + + expect(response).to have_gitlab_http_status(403) + end +end + +shared_examples 'unauthorized when external service denies access' do + include ExternalAuthorizationServiceHelpers + + it 'allows access when the authorization service allows it' do + external_service_allow_access(user, project) + + subject + + # Account for redirects after updates + expect(response.status).to be_between(200, 302) + end + + it 'allows access when the authorization service denies it' do + external_service_deny_access(user, project) + + subject + + expect(response).to have_gitlab_http_status(403) + end +end diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb new file mode 100644 index 00000000000..d7e17cc0b70 --- /dev/null +++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +shared_examples 'a finder with external authorization service' do + include ExternalAuthorizationServiceHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + project.add_maintainer(user) + end + + it 'finds the subject' do + expect(described_class.new(user).execute).to include(subject) + end + + context 'with an external authorization service' do + before do + enable_external_authorization_service_check + end + + it 'does not include the subject when no project was given' do + expect(described_class.new(user).execute).not_to include(subject) + end + + it 'includes the subject when a project id was given' do + expect(described_class.new(user, project_params).execute).to include(subject) + end + end +end diff --git a/spec/validators/x509_certificate_credentials_validator_spec.rb b/spec/validators/x509_certificate_credentials_validator_spec.rb new file mode 100644 index 00000000000..24ef68c1fab --- /dev/null +++ b/spec/validators/x509_certificate_credentials_validator_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe X509CertificateCredentialsValidator do + let(:certificate_data) { File.read('spec/fixtures/x509_certificate.crt') } + let(:pkey_data) { File.read('spec/fixtures/x509_certificate_pk.key') } + + let(:validatable) do + Class.new do + include ActiveModel::Validations + + attr_accessor :certificate, :private_key, :passphrase + + def initialize(certificate, private_key, passphrase = nil) + @certificate, @private_key, @passphrase = certificate, private_key, passphrase + end + end + end + + subject(:validator) do + described_class.new(certificate: :certificate, pkey: :private_key) + end + + it 'is not valid when the certificate is not valid' do + record = validatable.new('not a certificate', nil) + + validator.validate(record) + + expect(record.errors[:certificate]).to include('is not a valid X509 certificate.') + end + + it 'is not valid without a certificate' do + record = validatable.new(nil, nil) + + validator.validate(record) + + expect(record.errors[:certificate]).not_to be_empty + end + + context 'when a valid certificate is passed' do + let(:record) { validatable.new(certificate_data, nil) } + + it 'does not track an error for the certificate' do + validator.validate(record) + + expect(record.errors[:certificate]).to be_empty + end + + it 'adds an error when not passing a correct private key' do + validator.validate(record) + + expect(record.errors[:private_key]).to include('could not read private key, is the passphrase correct?') + end + + it 'has no error when the private key is correct' do + record.private_key = pkey_data + + validator.validate(record) + + expect(record.errors).to be_empty + end + end + + context 'when using a passphrase' do + let(:passphrase_certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') } + let(:passphrase_pkey_data) { File.read('spec/fixtures/passphrase_x509_certificate_pk.key') } + + let(:record) { validatable.new(passphrase_certificate_data, passphrase_pkey_data, '5iveL!fe') } + + subject(:validator) do + described_class.new(certificate: :certificate, pkey: :private_key, pass: :passphrase) + end + + it 'is valid with the correct data' do + validator.validate(record) + + expect(record.errors).to be_empty + end + + it 'adds an error when the passphrase is wrong' do + record.passphrase = 'wrong' + + validator.validate(record) + + expect(record.errors[:private_key]).not_to be_empty + end + end +end -- cgit v1.2.1 From f1dce2160956f27fecc4a59d36d1e1e03903a84b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 5 Apr 2019 04:33:01 -0700 Subject: Enable FindCommit caching for project and commits pages This reduces a handful of duplicate FindCommit calls while viewing the projects and commits pages. --- spec/controllers/projects/commits_controller_spec.rb | 2 ++ spec/controllers/projects_controller_spec.rb | 4 ++++ 2 files changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 9fb455dd974..9f753e5641f 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -113,6 +113,8 @@ describe Projects::CommitsController do render_views before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original unless id.include?(' ') + get(:signatures, params: { namespace_id: project.namespace, diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 04cc315b175..1ce06bc877c 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -78,6 +78,10 @@ describe ProjectsController do end context "user has access to project" do + before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + end + context "and does not have notification setting" do it "initializes notification as disabled" do get :show, params: { namespace_id: public_project.namespace, id: public_project } -- cgit v1.2.1 From 46b1b9c1d61c269588bd3cd4203420608ddd7f0b Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Fri, 5 Apr 2019 13:02:56 +0000 Subject: Revert "Merge branch 'if-57131-external_auth_to_ce' into 'master'" This reverts merge request !26823 --- .../admin/application_settings_controller_spec.rb | 22 ---- spec/controllers/boards/issues_controller_spec.rb | 26 ---- .../concerns/project_unauthorized_spec.rb | 51 -------- .../dashboard/groups_controller_spec.rb | 50 +++----- .../dashboard/labels_controller_spec.rb | 6 +- .../dashboard/milestones_controller_spec.rb | 6 - .../dashboard/projects_controller_spec.rb | 24 +--- .../controllers/dashboard/todos_controller_spec.rb | 6 - spec/controllers/groups/avatars_controller_spec.rb | 10 -- spec/controllers/groups/boards_controller_spec.rb | 8 -- .../controllers/groups/children_controller_spec.rb | 12 -- .../groups/group_members_controller_spec.rb | 86 ------------- spec/controllers/groups/labels_controller_spec.rb | 6 - .../groups/milestones_controller_spec.rb | 6 - .../groups/settings/ci_cd_controller_spec.rb | 15 --- .../groups/variables_controller_spec.rb | 34 ----- spec/controllers/groups_controller_spec.rb | 96 -------------- .../controllers/projects/boards_controller_spec.rb | 4 - .../controllers/projects/issues_controller_spec.rb | 11 -- spec/controllers/projects_controller_spec.rb | 32 ----- spec/controllers/search_controller_spec.rb | 39 ------ spec/controllers/users_controller_spec.rb | 12 -- ...ard_with_external_authorization_service_spec.rb | 38 ------ ...age_with_external_authorization_service_spec.rb | 58 --------- .../classification_label_on_project_pages_spec.rb | 22 ---- spec/features/projects/forks/fork_list_spec.rb | 35 ----- ...ues_with_external_authorization_enabled_spec.rb | 128 ------------------- ...external_authorization_service_settings_spec.rb | 21 --- spec/features/users/show_spec.rb | 22 ---- spec/finders/issues_finder_spec.rb | 7 - spec/finders/labels_finder_spec.rb | 7 - spec/finders/merge_requests_finder_spec.rb | 7 - spec/finders/snippets_finder_spec.rb | 32 ----- spec/finders/todos_finder_spec.rb | 7 - spec/fixtures/passphrase_x509_certificate.crt | 27 ---- spec/fixtures/passphrase_x509_certificate_pk.key | 54 -------- spec/fixtures/x509_certificate.crt | 27 ---- spec/fixtures/x509_certificate_pk.key | 51 -------- .../gitlab/external_authorization/access_spec.rb | 142 --------------------- .../gitlab/external_authorization/cache_spec.rb | 48 ------- .../gitlab/external_authorization/client_spec.rb | 97 -------------- .../gitlab/external_authorization/logger_spec.rb | 45 ------- .../gitlab/external_authorization/response_spec.rb | 52 -------- spec/lib/gitlab/external_authorization_spec.rb | 54 -------- .../gitlab/import_export/safe_model_attributes.yml | 1 - spec/models/application_setting_spec.rb | 48 +------ spec/models/concerns/protected_ref_access_spec.rb | 12 -- spec/models/issue_spec.rb | 45 ------- spec/models/project_spec.rb | 20 --- spec/policies/base_policy_spec.rb | 23 ---- spec/policies/issue_policy_spec.rb | 19 --- spec/policies/merge_request_policy_spec.rb | 19 --- spec/policies/project_policy_spec.rb | 53 -------- spec/requests/api/projects_spec.rb | 49 ------- spec/requests/api/settings_spec.rb | 33 ----- spec/serializers/group_child_entity_spec.rb | 19 --- .../application_settings/update_service_spec.rb | 35 ----- spec/services/notification_service_spec.rb | 41 ------ spec/services/projects/create_service_spec.rb | 37 ------ spec/services/projects/update_service_spec.rb | 41 ------ .../external_authorization_service_helpers.rb | 33 ----- ...ternal_authorization_service_shared_examples.rb | 40 ------ .../finder_with_external_authorization_enabled.rb | 30 ----- .../x509_certificate_credentials_validator_spec.rb | 87 ------------- 64 files changed, 22 insertions(+), 2206 deletions(-) delete mode 100644 spec/controllers/concerns/project_unauthorized_spec.rb delete mode 100644 spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb delete mode 100644 spec/features/groups/group_page_with_external_authorization_service_spec.rb delete mode 100644 spec/features/projects/classification_label_on_project_pages_spec.rb delete mode 100644 spec/features/projects/forks/fork_list_spec.rb delete mode 100644 spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb delete mode 100644 spec/features/projects/settings/external_authorization_service_settings_spec.rb delete mode 100644 spec/fixtures/passphrase_x509_certificate.crt delete mode 100644 spec/fixtures/passphrase_x509_certificate_pk.key delete mode 100644 spec/fixtures/x509_certificate.crt delete mode 100644 spec/fixtures/x509_certificate_pk.key delete mode 100644 spec/lib/gitlab/external_authorization/access_spec.rb delete mode 100644 spec/lib/gitlab/external_authorization/cache_spec.rb delete mode 100644 spec/lib/gitlab/external_authorization/client_spec.rb delete mode 100644 spec/lib/gitlab/external_authorization/logger_spec.rb delete mode 100644 spec/lib/gitlab/external_authorization/response_spec.rb delete mode 100644 spec/lib/gitlab/external_authorization_spec.rb delete mode 100644 spec/support/external_authorization_service_helpers.rb delete mode 100644 spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb delete mode 100644 spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb delete mode 100644 spec/validators/x509_certificate_credentials_validator_spec.rb (limited to 'spec') diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 60b725f4cac..9af472df74e 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -85,28 +85,6 @@ describe Admin::ApplicationSettingsController do expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) end - - context 'external policy classification settings' do - let(:settings) do - { - external_authorization_service_enabled: true, - external_authorization_service_url: 'https://custom.service/', - external_authorization_service_default_label: 'default', - external_authorization_service_timeout: 3, - external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'), - external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'), - external_auth_client_key_pass: "5iveL!fe" - } - end - - it 'updates settings when the feature is available' do - put :update, params: { application_setting: settings } - - settings.each do |attribute, value| - expect(ApplicationSetting.current.public_send(attribute)).to eq(value) - end - end - end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 309cac47928..5eb05f01b8d 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Boards::IssuesController do - include ExternalAuthorizationServiceHelpers - let(:project) { create(:project, :private) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } @@ -138,30 +136,6 @@ describe Boards::IssuesController do end end - context 'with external authorization' do - before do - sign_in(user) - enable_external_authorization_service_check - end - - it 'returns a 403 for group boards' do - group = create(:group) - group_board = create(:board, group: group) - - list_issues(user: user, board: group_board) - - expect(response).to have_gitlab_http_status(403) - end - - it 'is successful for project boards' do - project_board = create(:board, project: project) - - list_issues(user: user, board: project_board) - - expect(response).to have_gitlab_http_status(200) - end - end - def list_issues(user:, board:, list: nil) sign_in(user) diff --git a/spec/controllers/concerns/project_unauthorized_spec.rb b/spec/controllers/concerns/project_unauthorized_spec.rb deleted file mode 100644 index 90b59b027cf..00000000000 --- a/spec/controllers/concerns/project_unauthorized_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' - -describe ProjectUnauthorized do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } - - before do - sign_in user - end - - render_views - - describe '#project_unauthorized_proc' do - controller(::Projects::ApplicationController) do - def show - head :ok - end - end - - let(:project) { create(:project) } - - before do - project.add_developer(user) - end - - it 'renders a 200 when the service allows access to the project' do - external_service_allow_access(user, project) - - get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param } - - expect(response).to have_gitlab_http_status(200) - end - - it 'renders a 403 when the service denies access to the project' do - external_service_deny_access(user, project) - - get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param } - - expect(response).to have_gitlab_http_status(403) - expect(response.body).to match("External authorization denied access to this project") - end - - it 'renders a 404 when the user cannot see the project at all' do - other_project = create(:project, :private) - - get :show, params: { namespace_id: other_project.namespace.to_param, id: other_project.to_param } - - expect(response).to have_gitlab_http_status(404) - end - end -end diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb index 775b3ca40b2..c8d99f79277 100644 --- a/spec/controllers/dashboard/groups_controller_spec.rb +++ b/spec/controllers/dashboard/groups_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Dashboard::GroupsController do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } before do @@ -13,43 +11,33 @@ describe Dashboard::GroupsController do expect(described_class).to include(GroupTree) end - describe '#index' do - it 'only includes projects the user is a member of' do - member_of_group = create(:group) - member_of_group.add_developer(user) - create(:group, :public) - - get :index - - expect(assigns(:groups)).to contain_exactly(member_of_group) - end + it 'only includes projects the user is a member of' do + member_of_group = create(:group) + member_of_group.add_developer(user) + create(:group, :public) - context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do - let!(:top_level_result) { create(:group, name: 'chef-top') } - let!(:top_level_a) { create(:group, name: 'top-a') } - let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) } - let!(:other_group) { create(:group, name: 'other') } + get :index - before do - top_level_result.add_maintainer(user) - top_level_a.add_maintainer(user) - end + expect(assigns(:groups)).to contain_exactly(member_of_group) + end - it 'renders only groups the user is a member of when searching hierarchy correctly' do - get :index, params: { filter: 'chef' }, format: :json + context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do + let!(:top_level_result) { create(:group, name: 'chef-top') } + let!(:top_level_a) { create(:group, name: 'top-a') } + let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) } + let!(:other_group) { create(:group, name: 'other') } - expect(response).to have_gitlab_http_status(200) - all_groups = [top_level_result, top_level_a, sub_level_result_a] - expect(assigns(:groups)).to contain_exactly(*all_groups) - end + before do + top_level_result.add_maintainer(user) + top_level_a.add_maintainer(user) end - it 'works when the external authorization service is enabled' do - enable_external_authorization_service_check - - get :index + it 'renders only groups the user is a member of when searching hierarchy correctly' do + get :index, params: { filter: 'chef' }, format: :json expect(response).to have_gitlab_http_status(200) + all_groups = [top_level_result, top_level_a, sub_level_result_a] + expect(assigns(:groups)).to contain_exactly(*all_groups) end end end diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb index 01de896f9f4..a3bfb2f3a87 100644 --- a/spec/controllers/dashboard/labels_controller_spec.rb +++ b/spec/controllers/dashboard/labels_controller_spec.rb @@ -13,17 +13,13 @@ describe Dashboard::LabelsController do describe "#index" do let!(:unrelated_label) { create(:label, project: create(:project, :public)) } - subject { get :index, format: :json } - it 'returns global labels for projects the user has a relationship with' do - subject + get :index, format: :json expect(json_response).to be_kind_of(Array) expect(json_response.size).to eq(1) expect(json_response[0]["id"]).to be_nil expect(json_response[0]["title"]).to eq(label.title) end - - it_behaves_like 'disabled when using an external authorization service' end end diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 1614739db05..828de0e7ca5 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -81,11 +81,5 @@ describe Dashboard::MilestonesController do expect(response.body).to include("Open\n2") expect(response.body).to include("Closed\n0") end - - context 'external authorization' do - subject { get :index } - - it_behaves_like 'disabled when using an external authorization service' - end end end diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb index c17cb49e460..649441f4917 100644 --- a/spec/controllers/dashboard/projects_controller_spec.rb +++ b/spec/controllers/dashboard/projects_controller_spec.rb @@ -1,29 +1,7 @@ require 'spec_helper' describe Dashboard::ProjectsController do - include ExternalAuthorizationServiceHelpers - - describe '#index' do - context 'user not logged in' do - it_behaves_like 'authenticates sessionless user', :index, :atom - end - - context 'user logged in' do - before do - sign_in create(:user) - end - - context 'external authorization' do - it 'works when the external authorization service is enabled' do - enable_external_authorization_service_check - - get :index - - expect(response).to have_gitlab_http_status(200) - end - end - end - end + it_behaves_like 'authenticates sessionless user', :index, :atom context 'json requests' do render_views diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index abbf0b52306..d88beaff0e1 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -105,12 +105,6 @@ describe Dashboard::TodosController do end end end - - context 'external authorization' do - subject { get :index } - - it_behaves_like 'disabled when using an external authorization service' - end end describe 'PATCH #restore' do diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb index 6ececa6f372..772d1d0c1dd 100644 --- a/spec/controllers/groups/avatars_controller_spec.rb +++ b/spec/controllers/groups/avatars_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Groups::AvatarsController do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } let(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } @@ -17,12 +15,4 @@ describe Groups::AvatarsController do expect(@group.avatar.present?).to be_falsey expect(@group).to be_valid end - - it 'works when external authorization service is enabled' do - enable_external_authorization_service_check - - delete :destroy, params: { group_id: group } - - expect(response).to have_gitlab_http_status(302) - end end diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index 0ca5ce51750..27ee37b3817 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -82,10 +82,6 @@ describe Groups::BoardsController do end end - it_behaves_like 'disabled when using an external authorization service' do - subject { list_boards } - end - def list_boards(format: :html) get :index, params: { group_id: group }, format: format end @@ -164,10 +160,6 @@ describe Groups::BoardsController do end end - it_behaves_like 'disabled when using an external authorization service' do - subject { read_board board: board } - end - def read_board(board:, format: :html) get :show, params: { group_id: group, diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb index 4085c8f95a9..e1b97013408 100644 --- a/spec/controllers/groups/children_controller_spec.rb +++ b/spec/controllers/groups/children_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Groups::ChildrenController do - include ExternalAuthorizationServiceHelpers - let(:group) { create(:group, :public) } let(:user) { create(:user) } let!(:group_member) { create(:group_member, group: group, user: user) } @@ -319,15 +317,5 @@ describe Groups::ChildrenController do end end end - - context 'external authorization' do - it 'works when external authorization service is enabled' do - enable_external_authorization_service_check - - get :index, params: { group_id: group }, format: :json - - expect(response).to have_gitlab_http_status(200) - end - end end end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 96a58d6d87c..3a801fabafc 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -1,11 +1,8 @@ require 'spec_helper' describe Groups::GroupMembersController do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } - let(:membership) { create(:group_member, group: group) } describe 'GET index' do it 'renders index with 200 status code' do @@ -266,87 +263,4 @@ describe Groups::GroupMembersController do end end end - - context 'with external authorization enabled' do - before do - enable_external_authorization_service_check - group.add_owner(user) - sign_in(user) - end - - describe 'GET #index' do - it 'is successful' do - get :index, params: { group_id: group } - - expect(response).to have_gitlab_http_status(200) - end - end - - describe 'POST #create' do - it 'is successful' do - post :create, params: { group_id: group, users: user, access_level: Gitlab::Access::GUEST } - - expect(response).to have_gitlab_http_status(302) - end - end - - describe 'PUT #update' do - it 'is successful' do - put :update, - params: { - group_member: { access_level: Gitlab::Access::GUEST }, - group_id: group, - id: membership - }, - format: :js - - expect(response).to have_gitlab_http_status(200) - end - end - - describe 'DELETE #destroy' do - it 'is successful' do - delete :destroy, params: { group_id: group, id: membership } - - expect(response).to have_gitlab_http_status(302) - end - end - - describe 'POST #destroy' do - it 'is successful' do - sign_in(create(:user)) - - post :request_access, params: { group_id: group } - - expect(response).to have_gitlab_http_status(302) - end - end - - describe 'POST #approve_request_access' do - it 'is successful' do - access_request = create(:group_member, :access_request, group: group) - post :approve_access_request, params: { group_id: group, id: access_request } - - expect(response).to have_gitlab_http_status(302) - end - end - - describe 'DELETE #leave' do - it 'is successful' do - group.add_owner(create(:user)) - - delete :leave, params: { group_id: group } - - expect(response).to have_gitlab_http_status(302) - end - end - - describe 'POST #resend_invite' do - it 'is successful' do - post :resend_invite, params: { group_id: group, id: membership } - - expect(response).to have_gitlab_http_status(302) - end - end - end end diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 9af47114838..fa664a29066 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -37,12 +37,6 @@ describe Groups::LabelsController do expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) end end - - context 'external authorization' do - subject { get :index, params: { group_id: group.to_param } } - - it_behaves_like 'disabled when using an external authorization service' - end end describe 'POST #toggle_subscription' do diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index d70946cbc8f..043cf28514b 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -80,12 +80,6 @@ describe Groups::MilestonesController do expect(response.content_type).to eq 'application/json' end end - - context 'external authorization' do - subject { get :index, params: { group_id: group.to_param } } - - it_behaves_like 'disabled when using an external authorization service' - end end describe '#show' do diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index b998f64ef72..3290ed8b088 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Groups::Settings::CiCdController do - include ExternalAuthorizationServiceHelpers - let(:group) { create(:group) } let(:user) { create(:user) } @@ -35,19 +33,6 @@ describe Groups::Settings::CiCdController do expect(response).to have_gitlab_http_status(404) end end - - context 'external authorization' do - before do - enable_external_authorization_service_check - group.add_owner(user) - end - - it 'renders show with 200 status code' do - get :show, params: { group_id: group } - - expect(response).to have_gitlab_http_status(200) - end - end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb index 40f05167350..29ec3588316 100644 --- a/spec/controllers/groups/variables_controller_spec.rb +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Groups::VariablesController do - include ExternalAuthorizationServiceHelpers - let(:group) { create(:group) } let(:user) { create(:user) } @@ -36,36 +34,4 @@ describe Groups::VariablesController do include_examples 'PATCH #update updates variables' end - - context 'with external authorization enabled' do - before do - enable_external_authorization_service_check - end - - describe 'GET #show' do - let!(:variable) { create(:ci_group_variable, group: group) } - - it 'is successful' do - get :show, params: { group_id: group }, format: :json - - expect(response).to have_gitlab_http_status(200) - end - end - - describe 'PATCH #update' do - let!(:variable) { create(:ci_group_variable, group: group) } - let(:owner) { group } - - it 'is successful' do - patch :update, - params: { - group_id: group, - variables_attributes: [{ id: variable.id, key: 'hello' }] - }, - format: :json - - expect(response).to have_gitlab_http_status(200) - end - end - end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 0adcba4f0b8..2b803e7151f 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe GroupsController do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } let(:admin) { create(:admin) } let(:group) { create(:group, :public) } @@ -660,98 +658,4 @@ describe GroupsController do end end end - - describe 'external authorization' do - before do - group.add_owner(user) - sign_in(user) - end - - context 'with external authorization service enabled' do - before do - enable_external_authorization_service_check - end - - describe 'GET #show' do - it 'is successful' do - get :show, params: { id: group.to_param } - - expect(response).to have_gitlab_http_status(200) - end - - it 'does not allow other formats' do - get :show, params: { id: group.to_param }, format: :atom - - expect(response).to have_gitlab_http_status(403) - end - end - - describe 'GET #edit' do - it 'is successful' do - get :edit, params: { id: group.to_param } - - expect(response).to have_gitlab_http_status(200) - end - end - - describe 'GET #new' do - it 'is successful' do - get :new - - expect(response).to have_gitlab_http_status(200) - end - end - - describe 'GET #index' do - it 'is successful' do - get :index - - # Redirects to the dashboard - expect(response).to have_gitlab_http_status(302) - end - end - - describe 'POST #create' do - it 'creates a group' do - expect do - post :create, params: { group: { name: 'a name', path: 'a-name' } } - end.to change { Group.count }.by(1) - end - end - - describe 'PUT #update' do - it 'updates a group' do - expect do - put :update, params: { id: group.to_param, group: { name: 'world' } } - end.to change { group.reload.name } - end - end - - describe 'DELETE #destroy' do - it 'deletes the group' do - delete :destroy, params: { id: group.to_param } - - expect(response).to have_gitlab_http_status(302) - end - end - end - - describe 'GET #activity' do - subject { get :activity, params: { id: group.to_param } } - - it_behaves_like 'disabled when using an external authorization service' - end - - describe 'GET #issues' do - subject { get :issues, params: { id: group.to_param } } - - it_behaves_like 'disabled when using an external authorization service' - end - - describe 'GET #merge_requests' do - subject { get :merge_requests, params: { id: group.to_param } } - - it_behaves_like 'disabled when using an external authorization service' - end - end end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index b1203fd00b0..1eeded06459 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -98,10 +98,6 @@ describe Projects::BoardsController do end end - it_behaves_like 'unauthorized when external service denies access' do - subject { list_boards } - end - def list_boards(format: :html) get :index, params: { namespace_id: project.namespace, diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index bfa23af76d5..c34d7c13d57 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -127,17 +127,6 @@ describe Projects::IssuesController do expect(assigns(:issues).size).to eq(2) end end - - context 'external authorization' do - before do - sign_in user - project.add_developer(user) - end - - it_behaves_like 'unauthorized when external service denies access' do - subject { get :index, params: { namespace_id: project.namespace, project_id: project } } - end - end end describe 'GET #new' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 04cc315b175..56d38b9475e 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1,7 +1,6 @@ require('spec_helper') describe ProjectsController do - include ExternalAuthorizationServiceHelpers include ProjectForksHelper let(:project) { create(:project) } @@ -408,37 +407,6 @@ describe ProjectsController do it_behaves_like 'updating a project' end - - context 'as maintainer' do - before do - project.add_maintainer(user) - sign_in(user) - end - - it_behaves_like 'unauthorized when external service denies access' do - subject do - put :update, - params: { - namespace_id: project.namespace, - id: project, - project: { description: 'Hello world' } - } - project.reload - end - - it 'updates when the service allows access' do - external_service_allow_access(user, project) - - expect { subject }.to change(project, :description) - end - - it 'does not update when the service rejects access' do - external_service_deny_access(user, project) - - expect { subject }.not_to change(project, :description) - end - end - end end describe '#transfer' do diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 752d6ae55cc..02a0cfe0272 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe SearchController do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } before do @@ -78,41 +76,4 @@ describe SearchController do expect(assigns[:search_objects].count).to eq(0) end end - - context 'with external authorization service enabled' do - let(:project) { create(:project, namespace: user.namespace) } - let(:note) { create(:note_on_issue, project: project) } - - before do - enable_external_authorization_service_check - end - - describe 'GET #show' do - it 'renders a 403 when no project is given' do - get :show, params: { scope: 'notes', search: note.note } - - expect(response).to have_gitlab_http_status(403) - end - - it 'renders a 200 when a project was set' do - get :show, params: { project_id: project.id, scope: 'notes', search: note.note } - - expect(response).to have_gitlab_http_status(200) - end - end - - describe 'GET #autocomplete' do - it 'renders a 403 when no project is given' do - get :autocomplete, params: { term: 'hello' } - - expect(response).to have_gitlab_http_status(403) - end - - it 'renders a 200 when a project was set' do - get :autocomplete, params: { project_id: project.id, term: 'hello' } - - expect(response).to have_gitlab_http_status(200) - end - end - end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 42d28c53d34..4f6a6881193 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -223,12 +223,6 @@ describe UsersController do end end - context 'external authorization' do - subject { get :calendar_activities, params: { username: user.username } } - - it_behaves_like 'disabled when using an external authorization service' - end - def create_push_event push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) EventCreateService.new.push(project, public_user, push_data) @@ -292,12 +286,6 @@ describe UsersController do expect(JSON.parse(response.body)).to have_key('html') end end - - context 'external authorization' do - subject { get :snippets, params: { username: user.username } } - - it_behaves_like 'disabled when using an external authorization service' - end end describe 'GET #exists' do diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb deleted file mode 100644 index 4098dd02141..00000000000 --- a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'spec_helper' - -describe 'The group dashboard' do - include ExternalAuthorizationServiceHelpers - - let(:user) { create(:user) } - - before do - sign_in user - end - - describe 'The top navigation' do - it 'has all the expected links' do - visit dashboard_groups_path - - within('.navbar') do - expect(page).to have_button('Projects') - expect(page).to have_button('Groups') - expect(page).to have_link('Activity') - expect(page).to have_link('Milestones') - expect(page).to have_link('Snippets') - end - end - - it 'hides some links when an external authorization service is enabled' do - enable_external_authorization_service_check - visit dashboard_groups_path - - within('.navbar') do - expect(page).to have_button('Projects') - expect(page).to have_button('Groups') - expect(page).not_to have_link('Activity') - expect(page).not_to have_link('Milestones') - expect(page).to have_link('Snippets') - end - end - end -end diff --git a/spec/features/groups/group_page_with_external_authorization_service_spec.rb b/spec/features/groups/group_page_with_external_authorization_service_spec.rb deleted file mode 100644 index c05c3f4f3d6..00000000000 --- a/spec/features/groups/group_page_with_external_authorization_service_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe 'The group page' do - include ExternalAuthorizationServiceHelpers - - let(:user) { create(:user) } - let(:group) { create(:group) } - - before do - sign_in user - group.add_owner(user) - end - - def expect_all_sidebar_links - within('.nav-sidebar') do - expect(page).to have_link('Overview') - expect(page).to have_link('Details') - expect(page).to have_link('Activity') - expect(page).to have_link('Issues') - expect(page).to have_link('Merge Requests') - expect(page).to have_link('Members') - end - end - - describe 'The sidebar' do - it 'has all the expected links' do - visit group_path(group) - - expect_all_sidebar_links - end - - it 'shows all project features when policy control is enabled' do - stub_application_setting(external_authorization_service_enabled: true) - - visit group_path(group) - - expect_all_sidebar_links - end - - it 'hides some links when an external authorization service configured with an url' do - enable_external_authorization_service_check - visit group_path(group) - - within('.nav-sidebar') do - expect(page).to have_link('Overview') - expect(page).to have_link('Details') - expect(page).not_to have_link('Activity') - expect(page).not_to have_link('Contribution Analytics') - - expect(page).not_to have_link('Issues') - expect(page).not_to have_link('Merge Requests') - expect(page).to have_link('Members') - end - end - end -end diff --git a/spec/features/projects/classification_label_on_project_pages_spec.rb b/spec/features/projects/classification_label_on_project_pages_spec.rb deleted file mode 100644 index 92f8aa8eb8d..00000000000 --- a/spec/features/projects/classification_label_on_project_pages_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe 'Classification label on project pages' do - let(:project) do - create(:project, external_authorization_classification_label: 'authorized label') - end - let(:user) { create(:user) } - - before do - stub_application_setting(external_authorization_service_enabled: true) - project.add_maintainer(user) - sign_in(user) - end - - it 'shows the classification label on the project page' do - visit project_path(project) - - expect(page).to have_content('authorized label') - end -end diff --git a/spec/features/projects/forks/fork_list_spec.rb b/spec/features/projects/forks/fork_list_spec.rb deleted file mode 100644 index 2c41c61a660..00000000000 --- a/spec/features/projects/forks/fork_list_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -describe 'listing forks of a project' do - include ProjectForksHelper - include ExternalAuthorizationServiceHelpers - - let(:source) { create(:project, :public, :repository) } - let!(:fork) { fork_project(source, nil, repository: true) } - let(:user) { create(:user) } - - before do - source.add_maintainer(user) - sign_in(user) - end - - it 'shows the forked project in the list with commit as description' do - visit project_forks_path(source) - - page.within('li.project-row') do - expect(page).to have_content(fork.full_name) - expect(page).to have_css('a.commit-row-message') - end - end - - it 'does not show the commit message when an external authorization service is used' do - enable_external_authorization_service_check - - visit project_forks_path(source) - - page.within('li.project-row') do - expect(page).to have_content(fork.full_name) - expect(page).not_to have_css('a.commit-row-message') - end - end -end diff --git a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb deleted file mode 100644 index a8612d77a5e..00000000000 --- a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb +++ /dev/null @@ -1,128 +0,0 @@ -require 'spec_helper' - -describe 'viewing an issue with cross project references' do - include ExternalAuthorizationServiceHelpers - include Gitlab::Routing.url_helpers - - let(:user) { create(:user) } - let(:other_project) do - create(:project, :public, - external_authorization_classification_label: 'other_label') - end - let(:other_issue) do - create(:issue, :closed, - title: 'I am in another project', - project: other_project) - end - let(:other_confidential_issue) do - create(:issue, :confidential, :closed, - title: 'I am in another project and confidential', - project: other_project) - end - let(:other_merge_request) do - create(:merge_request, :closed, - title: 'I am a merge request in another project', - source_project: other_project) - end - let(:description_referencing_other_issue) do - "Referencing: #{other_issue.to_reference(project)}, "\ - "a confidential issue #{confidential_issue.to_reference}, "\ - "a cross project confidential issue #{other_confidential_issue.to_reference(project)}, and "\ - "a cross project merge request #{other_merge_request.to_reference(project)}" - end - let(:project) { create(:project) } - let(:issue) do - create(:issue, - project: project, - description: description_referencing_other_issue ) - end - let(:confidential_issue) do - create(:issue, :confidential, :closed, - title: "I am in the same project and confidential", - project: project) - end - - before do - project.add_developer(user) - sign_in(user) - end - - it 'shows all information related to the cross project reference' do - visit project_issue_path(project, issue) - - expect(page).to have_link("#{other_issue.to_reference(project)} (#{other_issue.state})") - expect(page).to have_xpath("//a[@title='#{other_issue.title}']") - end - - it 'shows a link to the confidential issue in the same project' do - visit project_issue_path(project, issue) - - expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})") - expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']") - end - - it 'does not show the link to a cross project confidential issue when the user does not have access' do - visit project_issue_path(project, issue) - - expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})") - expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") - end - - it 'shows the link to a cross project confidential issue when the user has access' do - other_project.add_developer(user) - - visit project_issue_path(project, issue) - - expect(page).to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})") - expect(page).to have_xpath("//a[@title='#{other_confidential_issue.title}']") - end - - context 'when an external authorization service is enabled' do - before do - enable_external_authorization_service_check - end - - it 'only hits the external service for the project the user is viewing' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(user, 'default_label', any_args).at_least(1).and_return(true) - expect(::Gitlab::ExternalAuthorization) - .not_to receive(:access_allowed?).with(user, 'other_label', any_args) - - visit project_issue_path(project, issue) - end - - it 'shows only the link to the cross project references' do - visit project_issue_path(project, issue) - - expect(page).to have_link("#{other_issue.to_reference(project)}") - expect(page).to have_link("#{other_merge_request.to_reference(project)}") - expect(page).not_to have_content("#{other_issue.to_reference(project)} (#{other_issue.state})") - expect(page).not_to have_xpath("//a[@title='#{other_issue.title}']") - expect(page).not_to have_content("#{other_merge_request.to_reference(project)} (#{other_merge_request.state})") - expect(page).not_to have_xpath("//a[@title='#{other_merge_request.title}']") - end - - it 'does not link a cross project confidential issue if the user does not have access' do - visit project_issue_path(project, issue) - - expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)}") - expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") - end - - it 'links a cross project confidential issue without exposing information when the user has access' do - other_project.add_developer(user) - - visit project_issue_path(project, issue) - - expect(page).to have_link("#{other_confidential_issue.to_reference(project)}") - expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") - end - - it 'shows a link to the confidential issue in the same project' do - visit project_issue_path(project, issue) - - expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})") - expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']") - end - end -end diff --git a/spec/features/projects/settings/external_authorization_service_settings_spec.rb b/spec/features/projects/settings/external_authorization_service_settings_spec.rb deleted file mode 100644 index 31b2892cf6f..00000000000 --- a/spec/features/projects/settings/external_authorization_service_settings_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe 'Projects > Settings > External Authorization Classification Label setting' do - let(:user) { create(:user) } - let(:project) { create(:project_empty_repo) } - - before do - project.add_maintainer(user) - sign_in(user) - end - - it 'shows the field to set a classification label' do - stub_application_setting(external_authorization_service_enabled: true) - - visit edit_project_path(project) - - expect(page).to have_selector('#project_external_authorization_classification_label') - end -end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 351750c0179..86379164cf0 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe 'User page' do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } context 'with public profile' do @@ -88,24 +86,4 @@ describe 'User page' do end end end - - context 'most recent activity' do - it 'shows the most recent activity' do - visit(user_path(user)) - - expect(page).to have_content('Most Recent Activity') - end - - context 'when external authorization is enabled' do - before do - enable_external_authorization_service_check - end - - it 'hides the most recent activity' do - visit(user_path(user)) - - expect(page).not_to have_content('Most Recent Activity') - end - end - end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 4133987a07e..fe53fabe54c 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -559,13 +559,6 @@ describe IssuesFinder do expect(issues.count).to eq 0 end end - - context 'external authorization' do - it_behaves_like 'a finder with external authorization service' do - let!(:subject) { create(:issue, project: project) } - let(:project_params) { { project_id: project.id } } - end - end end describe '#row_count', :request_store do diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index 98b4933fef6..3f060ba0553 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -226,12 +226,5 @@ describe LabelsFinder do expect(finder.execute).to eq [project_label_1] end end - - context 'external authorization' do - it_behaves_like 'a finder with external authorization service' do - let!(:subject) { create(:label, project: project) } - let(:project_params) { { project_id: project.id } } - end - end end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 9d4b9af3ec3..f508b9bdb6f 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -253,13 +253,6 @@ describe MergeRequestsFinder do expect(finder.row_count).to eq(1) end end - - context 'external authorization' do - it_behaves_like 'a finder with external authorization service' do - let!(:subject) { create(:merge_request, source_project: project) } - let(:project_params) { { project_id: project.id } } - end - end end context 'when projects require different access levels for merge requests' do diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index d367f9015c7..93287f3e9b8 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe SnippetsFinder do - include ExternalAuthorizationServiceHelpers include Gitlab::Allowable describe '#initialize' do @@ -165,35 +164,4 @@ describe SnippetsFinder do end it_behaves_like 'snippet visibility' - - context 'external authorization' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let!(:snippet) { create(:project_snippet, :public, project: project) } - - before do - project.add_maintainer(user) - end - - it_behaves_like 'a finder with external authorization service' do - let!(:subject) { create(:project_snippet, project: project) } - let(:project_params) { { project: project } } - end - - it 'includes the result if the external service allows access' do - external_service_allow_access(user, project) - - results = described_class.new(user, project: project).execute - - expect(results).to contain_exactly(snippet) - end - - it 'does not include any results if the external service denies access' do - external_service_deny_access(user, project) - - results = described_class.new(user, project: project).execute - - expect(results).to be_empty - end - end end diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index 22318a9946a..d4ed41d54f0 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -47,13 +47,6 @@ describe TodosFinder do end end end - - context 'external authorization' do - it_behaves_like 'a finder with external authorization service' do - let!(:subject) { create(:todo, project: project, user: user) } - let(:project_params) { { project_id: project.id } } - end - end end describe '#sort' do diff --git a/spec/fixtures/passphrase_x509_certificate.crt b/spec/fixtures/passphrase_x509_certificate.crt deleted file mode 100644 index 6973163b79e..00000000000 --- a/spec/fixtures/passphrase_x509_certificate.crt +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEpTCCAo0CAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5 -MB4XDTE4MDMyMzE0MDIwOFoXDTE5MDMyMzE0MDIwOFowHTEbMBkGA1UEAwwSZ2l0 -bGFiLXBhc3NwaHJhc2VkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA -zpsWHOewP/khfDsLUWxaRCinrBzVJm2C01bVahKVR3g/JD4vEH901Wod9Pvbh/9e -PEfE+YZmgSUUopbL3JUheMnyW416F43HKE/fPW4+QeuIEceuhCXg20eOXmvnWWNM -0hXZh4hq69rwvMPREC/LkZy/QkTDKhJNLNAqAQu2AJ3C7Yga8hFQYEhx1hpfGtwD -z/Nf3efat9WN/d6yW9hfJ98NCmImTm5l9Pc0YPNWCAf96vsqsNHBrTkFy6CQwkhH -K1ynVYuqnHYxSc4FPCT5SAleD9gR/xFBAHb7pPy4yGxMSEmiWaMjjZCVPsghj1jM -Ej77MTDL3U9LeDfiILhvZ+EeQxqPiFwwG2eaIn3ZEs2Ujvw7Z2VpG9VMcPTnB4jK -ot6qPM1YXnkGWQ6iT0DTPS3h7zg1xIJXI5N2sI6GXuKrXXwZ1wPqzFLKPv+xBjp8 -P6dih+EImfReFi9zIO1LqGMY+XmRcqodsb6jzsmBimJkqBtatJM7FuUUUN56wiaj -q9+BWbm+ZdQ2lvqndMljjUjTh6pNERfGAJgkNuLn3X9hXVE0TSpmn0nOgaL5izP3 -7FWUt0PTyGgK2zq9SEhZmK2TKckLkKMk/ZBBBVM/nrnjs72IlbsqdcVoTnApytZr -xVYTj1hV7QlAfaU3w/M534qXDiy8+HfX5ksWQMtSklECAwEAATANBgkqhkiG9w0B -AQUFAAOCAgEAMMhzSRq9PqCpui74nwjhmn8Dm2ky7A+MmoXNtk70cS/HWrjzaacb -B/rxsAUp7f0pj4QMMM0ETMFpbNs8+NPd2FRY0PfWE4yyDpvZO2Oj1HZKLHX72Gjn -K5KB9DYlVsXhGPfuFWXpxGWF2Az9hDWnj58M3DOAps+6tHuAtudQUuwf5ENQZWwE -ySpr7yoHm1ykgl0Tsb9ZHi9qLrWRRMNYXRT+gvwP1bba8j9jOtjO/xYiIskwMPLM -W8SFmQxbg0Cvi8Q89PB6zoTNOhPQyoyeSlw9meeZJHAMK2zxeglEm8C4EQ+I9Y6/ -yylM5/Sc55TjWAvRFgbsq+OozgMvffk/Q2fzcGF44J9DEQ7nrhmJxJ+X4enLknR5 -Hw4+WhdYA+bwjx3YZBNTh9/YMgNPYwQhf5gtcZGTd6X4j6qZfJ6CXBmhkC1Cbfyl -yM7B7i4JAqPWMeDP50pXCgyKlwgw1JuFW+xkbkYQAj7wtggQ6z1Vjb5W8R8kYn9q -LXClVtThEeSV5KkVwNX21aFcUs8qeQ+zsgKqpEyM5oILQQ1gDSxLTtrr2KuN+WJN -wM0acwD45X7gA/aZYpCGkIgHIBq0zIDP1s6IqeebFJjW8lWofhRxOEWomWdRweJG -N7qQ1WCTQxAPGAkDI8QPjaspvnAhFKmpBG/mR5IXLFKDbttu7WNdYDo= ------END CERTIFICATE----- diff --git a/spec/fixtures/passphrase_x509_certificate_pk.key b/spec/fixtures/passphrase_x509_certificate_pk.key deleted file mode 100644 index f9760dfe70e..00000000000 --- a/spec/fixtures/passphrase_x509_certificate_pk.key +++ /dev/null @@ -1,54 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-128-CBC,79CCB506B0FD42A6F1BAE6D72E1CB20C - -EuZQOfgaO6LVCNytTHNJmbiq1rbum9xg6ohfBTVt7Cw4+8yLezWva/3sJQtnEk2P -M2yEQYWIiCX+clPkRiRL8WLjRfLTNcYS6QxxuJdpOrowPrBYr4Aig8jBUUBI4VQf -w1ZEUQd0mxQGnyzkKpsudFOntCtZbvbrBsIAQUNLcrKEFk3XW/BqE1Q/ja6WfWqX -b6EKg6DoXi92V90O6sLDfpmTKZq3ThvVDFuWeJ2K/GVp2cs+MkBIBJ8XX+NT1nWg -g+Ok+yaSI/N9ILX4XDgXunJGwcooI8PhHSjkDWRusi8vbo7RFqIKiSF+h6tIwktF -Uss3JESKgXZCQ7upCnHSzK/aWFtwHtXxqOi7esqEZd+1sB0LY+XMnbaxweCMx2Kj -czktKYvoXUs69Whln+yyXULtl5XhJ8lbvlbIG2FbZ9y+/hHOyBqZyeUyCnXDzv8/ -0U0iZwreP3XPVMsy578pIdcdL27q+r05j4yjrJfbX3T9xp2u3F9uVubCa4euEBwV -yrFdsxJLKON8pFeDS49m5gHNsHmeZ0sUeTPZVGNXdabVetkOA0eAAGK4zAoqG79L -hEN7cDenz+E4XHp8gMzwwMiVyU4FuAb6SXkfSodctmSTWVbzNBja0FBek3UXy+pn -9qq7cIpe7NY5gzcbyoy9lSkyYVkAm8j6BIYtY1ZUAmtCklC2ADWARTjd7dI7aEbO -QbXxNIq2+O/zMOXfougSPoDP8SLyLuE1p6SwfWV7Dwf119hn+mjWlGzAZDxxHhsR -yYUQCUe0NIKzuUp3WYIx8xIb7/WFwit/JaFaxurjBnhkkEviBn+TgXiuFBO3tv/d -URpZ39rH0mrDsR61pCiIcoNVkQkynHcAFPd5VtaeSJPvZP280uOCPPS31cr6/0LB -1JX3lZoWWCuA+JQjxtZDaDTcvEUbfOQ2rexQQo4uylNkBF9F5WOdQBkKG/AfqBq8 -S/TdubYzvpcKhFAlXsI67JdbxGlU4HCsxOLwWzSUYclN4W3l7s7KZ5zxt+MU03Uf -vara9uuZHiKUjZohjXeqcXTc+UyC8VH1dF19M3Cj9RNrwl2xEDUMtIiALBjbGp1E -pu2nPj9NhWf9Vw5MtSszutesxXba2nPmvvGvvZ7N3h/k4NsKL7JdENF7XqkI0D2K -jpO1t6d3cazS1VpMWLZS45kWaM3Y07tVR3V+4Iv9Vo1e9H2u/Z5U4YeJ44sgMsct -dBOAhHdUAI5+P+ocLXiCKo+EcS0cKvz+CC4ux0vvcF3JrTqZJN1U/JxRka2EyJ1B -2Xtu3DF36XpBJcs+MJHjJ+kUn6DHYoYxZa+bB8LX6+FQ+G7ue+Dx/RsGlP7if1nq -DAaM6kZg7/FbFzOZyl5xhwAJMxfgNNU7nSbk9lrvQ4mdwgFjvgGu3jlER4+TcleE -4svXInxp1zK6ES44tI9fXkhPaFkafxAL7eUSyjjEwMC06h+FtqK3mmoKLo5NrGJE -zVl69r2WdoSQEylVN1Kbp+U4YbfncInLJqBq2q5w9ASL/8Rhe8b52q6PuVX/bjoz -0pkSu+At4jVbAhRpER5NGlzG884IaqqvBvMYR5zFJeRroIijyUyH0KslK37/sXRk -ty0yKrkm31De9gDa3+XlgAVDAgbEQmGVwVVcV0IYYJbjIf36lUdGh4+3krwxolr/ -vZct5Z7QxfJlBtdOstjz5U9o05yOhjoNrPZJXuKMmWOQjSwr7rRSdqmAABF9IrBf -Pa/ChF1y5j3gJESAFMyiea3kvLq1EbZRaKoybsQE2ctBQ8EQjzUz+OOxVO6GJ4W9 -XHyfcviFrpsVcJEpXQlEtGtKdfKLp48cytob1Fu1JOYPDCrafUQINCZP4H3Nt892 -zZiTmdwux7pbgf4KbONImN5XkpvdCGjQHSkYMmm5ETRK8s7Fmvt2aBPtlyXxJDOq -iJUqwDV5HZXOnQVE/v/yESKgo2Cb8BWqPZ4/8Ubgu/OADYyv/dtjQel8QQ2FMhO4 -2tnwWbBBJk8VpR/vjFHkGSnj+JJfW/vUVQ+06D3wHYhNp7mh4M+37AngwzGCp7k+ -9aFwb2FBGghArB03E4lIO/959T0cX95WZ6tZtLLEsf3+ug7PPOSswCqsoPsXzFJH -MgXVGKFXccNSsWol7VvrX/uja7LC1OE+pZNXxCRzSs4aljJBpvQ6Mty0lk2yBC0R -MdujMoZH9PG9U6stwFd+P17tlGrQdRD3H2uimn82Ck+j2l0z0pzN0JB2WBYEyK0O -1MC36wLICWjgIPLPOxDEEBeZPbc24DCcYfs/F/hSCHv/XTJzVVILCX11ShGPSXlI -FL9qyq6jTNh/pVz6NiN/WhUPBFfOSzLRDyU0MRsSHM8b/HPpf3NOI3Ywmmj65c2k -2kle1F2M5ZTL+XvLS61qLJ/8AgXWvDHP3xWuKGG/pM40CRTUkRW6NAokMr2/pEFw -IHTE2+84dOKnUIEczzMY3aqzNmYDCmhOY0jD/Ieb4hy9tN+1lbQ/msYMIJ1w7CFR -38yB/UbDD90NcuDhjrMbzVUv1At2rW7GM9lSbxGOlYDmtMNEL63md1pQ724v4gSE -mzoFcMkqdh+hjFvv11o4H32lF3mPYcXuL+po76tqxGOiUrLKe/ZqkT5XAclYV/7H -k3Me++PCh4ZqXBRPvR8Xr90NETtiFCkBQXLdhNWXrRe2v0EbSX+cYAWk68FQKCHa -HKTz9T7wAvB6QWBXFhH9iCP8rnQLCEhLEhdrt+4v2KFkIVzBgOlMoHsZsMp0sBeq -c5ZVbJdiKik3P/8ZQTn4jmOnQXCEyWx+LU4acks8Aho4lqq9yKq2DZpwbIRED47E -r7R/NUevhqqzEHZ2SGD6EDqRN+bHJEi64vq0ryaEielusYXZqlnFXDHJcfLCmR5X -3bj5pCwQF4ScTukrGQB/c4henG4vlF4CaD0CIIK3W6tH+AoDohYJts6YK49LGxmK -yXiyKNak8zHYBBoRvd2avRHyGuR5yC9KrN8cbC/kZqMDvAyM65pIK+U7exJwYJhv -ezCcbiH3bK3anpiRpdeNOot2ba/Y+/ks+DRC+xs4QDIhrmSEBCsLv1JbcWjtHSaG -lm+1DSVduUk/kN+fBnlfif+TQV9AP3/wb8ekk8jjKXsL7H1tJKHsLLIIvrgrpxjw ------END RSA PRIVATE KEY----- diff --git a/spec/fixtures/x509_certificate.crt b/spec/fixtures/x509_certificate.crt deleted file mode 100644 index 8a84890b928..00000000000 --- a/spec/fixtures/x509_certificate.crt +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEnDCCAoQCAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5 -MB4XDTE4MDMxOTE1MjYzMloXDTE5MDMxOTE1MjYzMlowFDESMBAGA1UEAwwJbG9j -YWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+tcM7iphsLlR -ccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzAn/eVU4jyVWkaBym6MHa8CiDOro9H -OXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/2FAgFWzrB2HnYSShiN8tBeeDI5cJ -ii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UTK37k2kbDQZ41rv1ng2w0AUZt0LRA -NWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ -+1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNkI+cyv0Gle6tk+CkOfE1m0CvNWlNg -b8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5xMXpdUCsh22CZZHe/4SeFE64amkf -1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q/nLdY8haMC6KOtpbAWvKX/Jqq0z1 -nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVrVef0pb2mfdtzjzUrYCP0PtnQExPB -rocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8RSvAoEUs9VbPiUfN7WAyU1K1rTYH -KV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRlq07Q5LDz33h9KXw1LZT8MWRinVJf -RePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEAATANBgkqhkiG9w0BAQUFAAOCAgEA -Skp0tbvVsg3RG2pX0GP25j0ix+f78zG0+BJ6LiKGMoCIBtGKitfUjBg83ru/ILpa -fpgrQpNQVUnGQ9tmpnqV605ZBBRUC1CRDsvUnyN6p7+yQAq6Fl+2ZKONHpPk+Bl4 -CIewgdkHjTwTpvIM/1DFVCz4R1FxNjY3uqOVcNDczMYEk2Pn2GZNNN35hUHHxWh4 -89ZvI+XKuRFZq3cDPA60PySeJJpCRScWGgnkdEX1gTtWH3WUlq9llxIvRexyNyzZ -Yqvcfx5UT75/Pp+JPh9lpUCcKLHeUiadjkiLxu3IcrYa4gYx4lA8jgm7adNEahd0 -oMAHoO9DU6XMo7o6tnQH3xQv9RAbQanjuyJR9N7mwmc59bQ6mW+pxCk843GwT73F -slseJ1nE1fQQQD7mn/KGjmeWtxY2ElUjTay9ff9/AgJeQYRW+oH0cSdo8WCpc2+G -+LZtLWfBgFLHseRlmarSe2pP8KmbaTd3q7Bu0GekVQOxYcNX59Pj4muQZDVLh8aX -mSQ+Ifts/ljT649MISHn2AZMR4+BUx63tFcatQhbAGGH5LeFdbaGcaVdsUVyZ9a2 -HBmFWNsgEPtcC+WmNzCXbv7jQsLAJXufKG5MnurJgNf/n5uKCmpGsEJDT/KF1k/3 -x9YnqM7zTyV6un+LS3HjEJvwQmqPWe+vFAeXWGCoWxE= ------END CERTIFICATE----- diff --git a/spec/fixtures/x509_certificate_pk.key b/spec/fixtures/x509_certificate_pk.key deleted file mode 100644 index c02a3cf6189..00000000000 --- a/spec/fixtures/x509_certificate_pk.key +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEA+tcM7iphsLlRccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzA -n/eVU4jyVWkaBym6MHa8CiDOro9HOXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/ -2FAgFWzrB2HnYSShiN8tBeeDI5cJii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UT -K37k2kbDQZ41rv1ng2w0AUZt0LRANWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK -88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ+1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNk -I+cyv0Gle6tk+CkOfE1m0CvNWlNgb8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5 -xMXpdUCsh22CZZHe/4SeFE64amkf1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q -/nLdY8haMC6KOtpbAWvKX/Jqq0z1nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVr -Vef0pb2mfdtzjzUrYCP0PtnQExPBrocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8 -RSvAoEUs9VbPiUfN7WAyU1K1rTYHKV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRl -q07Q5LDz33h9KXw1LZT8MWRinVJfRePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEA -AQKCAgBf1urJ1Meeji/gGETVx9qBWLbDjn9QTayZSyyEd78155tDShIPDLmxQRHW -MGIReo/5FGSkOgS+DWBZRZ77oGOGrtuMnjkheXhDr8dZvw5b1PBv5ntqWrLnfMYP -/Ag7xZMyiJLbPqmMX5j1gsFt8zPzUoVMnnl9DYryV0Edrs/utHgfJCM+6yzleUQB -PkGkqo1yWVVFZ3Nt2nDt9dNsdlC594+dYQ1m2JuArNvYNiw3dpHT98GnhRc1aLh4 -U+q22FiFn3BKGQat43JdlaLa6KO5f8MIQRYWuI8tss2DGPlhRv9AnUcVsLBjAuIH -bmUVrBosxCYUQ6giatjd2sZPfdC+VIDCbIWRthxkXJ9I/Ap8R98xx/7qIcPFc+XA -hcK1xOM7zIq2xgAOFeeh8O8Wq9cH8NmUhMCgzIE0WT32Zo0JAW6l0kZc82Y/Yofz -U+TJKo0NOFZe687HOhanOHbbQSG29XOqxMYTABZ7Ixf+4RZPD5+yQgZWP1BhLluy -PxZhsLl67xvbfB2i9VVorMN7PbFx5hbni3C7/p63Z0rG5q4/uJBbX3Uuh6KdhIo+ -Zh9UC6u29adIthdxz+ZV5wBccTOgaeHB9wRL9Hbp6ZxyqesQB4RTsFtPNXxZ7K43 -fmJgHZvHhF5gSbeB8JAeBf0cy3pytJM49ZxplifeGVzUJP2gAQKCAQEA/1T9quz5 -sOD03FxV//oRWD1kqfunq3v56sIBG4ZMVZKUqc6wLjTmeklLYKq85AWX8gnCHi0g -nmG/xDh/rt1/IngMWP98WVuD67hFbrj87g7A7YGIiwZ2gi6hqhqmALN+5JjCSTPp -XOiPvNnXP0XM4gIHBXV8diHq5rF9NsSh4vx3OExr8KQqVzWoDcnnWNfnDlrFB8cq -ViII+UqdovXp59hAVOsc+pYAe+8JeQDX17H3U/NMkUw4gU2aWUCvUVjxi9oBG/CW -ncIdYuW8zne4qXbX7YLC0QUUIDVOWzhLauAUBduTqRTldJo0KAxu887tf+uStXs8 -RACLGIaBQw7BXQKCAQEA+38NFnpflKquU92xRtmqWAVaW7rm865ZO6EIaS4JII/N -/Ebu1YZrAhT0ruGJQaolYj8w79BEZRF2CYDPZxKFv/ye0O7rWCAGtCdWQ0BXcrIU -7SdlsdfTNXO1R3WbwCyVxyjg6YF7FjbTaaOAoTiosTjDs2ZOgkbdh/sMeWkSN5HB -aQz4c8rqq0kkYucLqp4nWYSWSJn88bL8ctwEwW77MheJiSpo1ohNRP3ExHnbCbYw -RIj7ATSz74ebpd9NMauB5clvMMh4jRG0EQyt7KCoOyfPRFc3fddvTr03LlgFfX/n -qoxd2nejgAS3NnG1XMxdcUa7cPannt46Sef1uZo3gQKCAQB454zquCYQDKXGBu8u -NAKsjv2wxBqESENyV4VgvDo/NxawRdAFQUV12GkaEB87ti5aDSbfVS0h8lV1G+/S -JM5DyybFqcz/Hyebofk20d/q9g+DJ5g5hMjvIhepTc8Xe+d1ZaRyN2Oke/c8TMbx -DiNTTfR3MEfMRIlPzfHl0jx6GGR3wzBFleb6vsyiIt4qoqmlkXPFGBlDCgDH0v5M -ITgucacczuw8+HSoOut4Yd7TI1FjbkzubHJBQDb7VnbuBTjzqTpnOYiIkVeK8hBy -kBxgGodqz0Vi5o2+Jp/A8Co+JHc2wt/r65ovmali4WhUiMLLlQg2aXGDHeK/rUle -MIl9AoIBAQCPKCYSCnyHypRK5uG3W8VsLzfdCUnXogHnQGXiQTMu1szA8ruWzdnx -qG4TcgxIVYrMHv5DNAEKquLOzATDPjbmLu1ULvvGAQzv1Yhz5ZchkZ7507g+gIUY -YxHoaFjNDlP/txQ3tt2SqoizFD/vBap4nsA/SVgdLiuB8PSL07Rr70rx+lEe0H2+ -HHda2Pu6FiZ9/Uvybb0e8+xhkT4fwYW5YM6IRpzAqXuabv1nfZmiMJPPH04JxK88 -BKwjwjVVtbPOUlg5o5ODcXVXUylZjaXVbna8Bw1uU4hngKt9dNtDMeB0I0x1RC7M -e2Ky2g0LksUJ6uJdjfmiJAt38FLeYJuBAoIBAC2oqaqr86Dug5v8xHpgFoC5u7z7 -BRhaiHpVrUr+wnaNJEXfAEmyKf4xF5xDJqldnYG3c9ETG/7bLcg1dcrMPzXx94Si -MI3ykwiPeI/sVWYmUlq4U8zCIC7MY6sWzWt3oCBNoCN/EeYx9e7+eLNBB+fADAXq -v9RMGlUIy7beX0uac8Bs771dsxIb/RrYw58wz+jrwGlzuDmcPWiu+ARu7hnBqCAV -AITlCV/tsEk7u08oBuv47+rVGCh1Qb19pNswyTtTZARAGErJO0Q+39BNuu0M2TIn -G3M8eNmGHC+mNsZTVgKRuyk9Ye0s4Bo0KcqSndiPFGHjcrF7/t+RqEOXr/E= ------END RSA PRIVATE KEY----- diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb deleted file mode 100644 index 5dc2521b310..00000000000 --- a/spec/lib/gitlab/external_authorization/access_spec.rb +++ /dev/null @@ -1,142 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do - subject(:access) { described_class.new(build(:user), 'dummy_label') } - - describe '#loaded?' do - it 'is `true` when it was loaded recently' do - Timecop.freeze do - allow(access).to receive(:loaded_at).and_return(5.minutes.ago) - - expect(access).to be_loaded - end - end - - it 'is `false` when there is no loading time' do - expect(access).not_to be_loaded - end - - it 'is `false` when there the result was loaded a long time ago' do - Timecop.freeze do - allow(access).to receive(:loaded_at).and_return(2.weeks.ago) - - expect(access).not_to be_loaded - end - end - end - - describe 'load!' do - let(:fake_client) { double('ExternalAuthorization::Client') } - let(:fake_response) do - double( - 'Response', - 'successful?' => true, - 'valid?' => true, - 'reason' => nil - ) - end - - before do - allow(access).to receive(:load_from_cache) - allow(fake_client).to receive(:request_access).and_return(fake_response) - allow(Gitlab::ExternalAuthorization::Client).to receive(:new) { fake_client } - end - - context 'when loading from the webservice' do - it 'loads from the webservice it the cache was empty' do - expect(access).to receive(:load_from_cache) - expect(access).to receive(:load_from_service).and_call_original - - access.load! - - expect(access).to be_loaded - end - - it 'assigns the accessibility, reason and loaded_at' do - allow(fake_response).to receive(:successful?).and_return(false) - allow(fake_response).to receive(:reason).and_return('Inaccessible label') - - access.load! - - expect(access.reason).to eq('Inaccessible label') - expect(access).not_to have_access - expect(access.loaded_at).not_to be_nil - end - - it 'returns itself' do - expect(access.load!).to eq(access) - end - - it 'stores the result in redis' do - Timecop.freeze do - fake_cache = double - expect(fake_cache).to receive(:store).with(true, nil, Time.now) - expect(access).to receive(:cache).and_return(fake_cache) - - access.load! - end - end - - context 'when the request fails' do - before do - allow(fake_client).to receive(:request_access) do - raise ::Gitlab::ExternalAuthorization::RequestFailed.new('Service unavailable') - end - end - - it 'is loaded' do - access.load! - - expect(access).to be_loaded - end - - it 'assigns the correct accessibility, reason and loaded_at' do - access.load! - - expect(access.reason).to eq('Service unavailable') - expect(access).not_to have_access - expect(access.loaded_at).not_to be_nil - end - - it 'does not store the result in redis' do - fake_cache = double - expect(fake_cache).not_to receive(:store) - allow(access).to receive(:cache).and_return(fake_cache) - - access.load! - end - end - end - - context 'When loading from cache' do - let(:fake_cache) { double('ExternalAuthorization::Cache') } - - before do - allow(access).to receive(:cache).and_return(fake_cache) - end - - it 'does not load from the webservice' do - Timecop.freeze do - expect(fake_cache).to receive(:load).and_return([true, nil, Time.now]) - - expect(access).to receive(:load_from_cache).and_call_original - expect(access).not_to receive(:load_from_service) - - access.load! - end - end - - it 'loads from the webservice when the cached result was too old' do - Timecop.freeze do - expect(fake_cache).to receive(:load).and_return([true, nil, 2.days.ago]) - - expect(access).to receive(:load_from_cache).and_call_original - expect(access).to receive(:load_from_service).and_call_original - allow(fake_cache).to receive(:store) - - access.load! - end - end - end - end -end diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb deleted file mode 100644 index 58e7d626707..00000000000 --- a/spec/lib/gitlab/external_authorization/cache_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do - let(:user) { build_stubbed(:user) } - let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" } - - subject(:cache) { described_class.new(user, 'dummy_label') } - - def read_from_redis(key) - Gitlab::Redis::Cache.with do |redis| - redis.hget(cache_key, key) - end - end - - def set_in_redis(key, value) - Gitlab::Redis::Cache.with do |redis| - redis.hmset(cache_key, key, value) - end - end - - describe '#load' do - it 'reads stored info from redis' do - Timecop.freeze do - set_in_redis(:access, false) - set_in_redis(:reason, 'Access denied for now') - set_in_redis(:refreshed_at, Time.now) - - access, reason, refreshed_at = cache.load - - expect(access).to eq(false) - expect(reason).to eq('Access denied for now') - expect(refreshed_at).to be_within(1.second).of(Time.now) - end - end - end - - describe '#store' do - it 'sets the values in redis' do - Timecop.freeze do - cache.store(true, 'the reason', Time.now) - - expect(read_from_redis(:access)).to eq('true') - expect(read_from_redis(:reason)).to eq('the reason') - expect(read_from_redis(:refreshed_at)).to eq(Time.now.to_s) - end - end - end -end diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb deleted file mode 100644 index fa18c1e56e8..00000000000 --- a/spec/lib/gitlab/external_authorization/client_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ExternalAuthorization::Client do - let(:user) { build(:user, email: 'dummy_user@example.com') } - let(:dummy_url) { 'https://dummy.net/' } - subject(:client) { described_class.new(user, 'dummy_label') } - - before do - stub_application_setting(external_authorization_service_url: dummy_url) - end - - describe '#request_access' do - it 'performs requests to the configured endpoint' do - expect(Excon).to receive(:post).with(dummy_url, any_args) - - client.request_access - end - - it 'adds the correct params for the user to the body of the request' do - expected_body = { - user_identifier: 'dummy_user@example.com', - project_classification_label: 'dummy_label' - }.to_json - expect(Excon).to receive(:post) - .with(dummy_url, hash_including(body: expected_body)) - - client.request_access - end - - it 'respects the the timeout' do - stub_application_setting( - external_authorization_service_timeout: 3 - ) - - expect(Excon).to receive(:post).with(dummy_url, - hash_including( - connect_timeout: 3, - read_timeout: 3, - write_timeout: 3 - )) - - client.request_access - end - - it 'adds the mutual tls params when they are present' do - stub_application_setting( - external_auth_client_cert: 'the certificate data', - external_auth_client_key: 'the key data', - external_auth_client_key_pass: 'open sesame' - ) - expected_params = { - client_cert_data: 'the certificate data', - client_key_data: 'the key data', - client_key_pass: 'open sesame' - } - - expect(Excon).to receive(:post).with(dummy_url, hash_including(expected_params)) - - client.request_access - end - - it 'returns an expected response' do - expect(Excon).to receive(:post) - - expect(client.request_access) - .to be_kind_of(::Gitlab::ExternalAuthorization::Response) - end - - it 'wraps exceptions if the request fails' do - expect(Excon).to receive(:post) { raise Excon::Error.new('the request broke') } - - expect { client.request_access } - .to raise_error(::Gitlab::ExternalAuthorization::RequestFailed) - end - - describe 'for ldap users' do - let(:user) do - create(:omniauth_user, - email: 'dummy_user@example.com', - extern_uid: 'external id', - provider: 'ldapprovider') - end - - it 'includes the ldap dn for ldap users' do - expected_body = { - user_identifier: 'dummy_user@example.com', - project_classification_label: 'dummy_label', - user_ldap_dn: 'external id' - }.to_json - expect(Excon).to receive(:post) - .with(dummy_url, hash_including(body: expected_body)) - - client.request_access - end - end - end -end diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb deleted file mode 100644 index 81f1b2390e6..00000000000 --- a/spec/lib/gitlab/external_authorization/logger_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ExternalAuthorization::Logger do - let(:request_time) { Time.parse('2018-03-26 20:22:15') } - - def fake_access(has_access, user, load_type = :request) - access = double('access') - allow(access).to receive_messages(user: user, - has_access?: has_access, - loaded_at: request_time, - label: 'dummy_label', - load_type: load_type) - - access - end - - describe '.log_access' do - it 'logs a nice message for an access request' do - expected_message = "GRANTED admin@example.com access to 'dummy_label' (the/project/path)" - fake_access = fake_access(true, build(:user, email: 'admin@example.com')) - - expect(described_class).to receive(:info).with(expected_message) - - described_class.log_access(fake_access, 'the/project/path') - end - - it 'does not trip without a project path' do - expected_message = "DENIED admin@example.com access to 'dummy_label'" - fake_access = fake_access(false, build(:user, email: 'admin@example.com')) - - expect(described_class).to receive(:info).with(expected_message) - - described_class.log_access(fake_access, nil) - end - - it 'adds the load time for cached accesses' do - expected_message = "DENIED admin@example.com access to 'dummy_label' - cache #{request_time}" - fake_access = fake_access(false, build(:user, email: 'admin@example.com'), :cache) - - expect(described_class).to receive(:info).with(expected_message) - - described_class.log_access(fake_access, nil) - end - end -end diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb deleted file mode 100644 index 43211043eca..00000000000 --- a/spec/lib/gitlab/external_authorization/response_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ExternalAuthorization::Response do - let(:excon_response) { double } - subject(:response) { described_class.new(excon_response) } - - describe '#valid?' do - it 'is valid for 200, 401, and 403 responses' do - [200, 401, 403].each do |status| - allow(excon_response).to receive(:status).and_return(status) - - expect(response).to be_valid - end - end - - it "is invalid for other statuses" do - expect(excon_response).to receive(:status).and_return(500) - - expect(response).not_to be_valid - end - end - - describe '#reason' do - it 'returns a reason if it was included in the response body' do - expect(excon_response).to receive(:body).and_return({ reason: 'Not authorized' }.to_json) - - expect(response.reason).to eq('Not authorized') - end - - it 'returns nil when there was no body' do - expect(excon_response).to receive(:body).and_return('') - - expect(response.reason).to eq(nil) - end - end - - describe '#successful?' do - it 'is `true` if the status is 200' do - allow(excon_response).to receive(:status).and_return(200) - - expect(response).to be_successful - end - - it 'is `false` if the status is 401 or 403' do - [401, 403].each do |status| - allow(excon_response).to receive(:status).and_return(status) - - expect(response).not_to be_successful - end - end - end -end diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb deleted file mode 100644 index 7394fbfe0ce..00000000000 --- a/spec/lib/gitlab/external_authorization_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ExternalAuthorization, :request_store do - include ExternalAuthorizationServiceHelpers - - let(:user) { build(:user) } - let(:label) { 'dummy_label' } - - describe '#access_allowed?' do - it 'is always true when the feature is disabled' do - # Not using `stub_application_setting` because the method is prepended in - # `EE::ApplicationSetting` which breaks when using `any_instance` - # https://gitlab.com/gitlab-org/gitlab-ce/issues/33587 - expect(::Gitlab::CurrentSettings.current_application_settings) - .to receive(:external_authorization_service_enabled) { false } - - expect(described_class).not_to receive(:access_for_user_to_label) - - expect(described_class.access_allowed?(user, label)).to be_truthy - end - end - - describe '#rejection_reason' do - it 'is always nil when the feature is disabled' do - expect(::Gitlab::CurrentSettings.current_application_settings) - .to receive(:external_authorization_service_enabled) { false } - - expect(described_class).not_to receive(:access_for_user_to_label) - - expect(described_class.rejection_reason(user, label)).to be_nil - end - end - - describe '#access_for_user_to_label' do - it 'only loads the access once per request' do - enable_external_authorization_service_check - - expect(::Gitlab::ExternalAuthorization::Access) - .to receive(:new).with(user, label).once.and_call_original - - 2.times { described_class.access_for_user_to_label(user, label, nil) } - end - - it 'logs the access request once per request' do - expect(::Gitlab::ExternalAuthorization::Logger) - .to receive(:log_access) - .with(an_instance_of(::Gitlab::ExternalAuthorization::Access), - 'the/project/path') - .once - - 2.times { described_class.access_for_user_to_label(user, label, 'the/project/path') } - end - end -end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 30bb58ac990..d0ed588f05f 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -496,7 +496,6 @@ Project: - merge_requests_ff_only_enabled - merge_requests_rebase_enabled - jobs_cache_index -- external_authorization_classification_label - pages_https_only Author: - name diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index c7d7dbac736..c81572d739e 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ApplicationSetting do - subject(:setting) { described_class.create_from_defaults } + let(:setting) { described_class.create_from_defaults } it { include(CacheableAttributes) } it { include(ApplicationSettingImplementation) } @@ -284,52 +284,6 @@ describe ApplicationSetting do expect(subject).to be_valid end end - - describe 'when external authorization service is enabled' do - before do - setting.external_authorization_service_enabled = true - end - - it { is_expected.not_to allow_value('not a URL').for(:external_authorization_service_url) } - it { is_expected.to allow_value('https://example.com').for(:external_authorization_service_url) } - it { is_expected.to allow_value('').for(:external_authorization_service_url) } - it { is_expected.not_to allow_value(nil).for(:external_authorization_service_default_label) } - it { is_expected.not_to allow_value(11).for(:external_authorization_service_timeout) } - it { is_expected.not_to allow_value(0).for(:external_authorization_service_timeout) } - it { is_expected.not_to allow_value('not a certificate').for(:external_auth_client_cert) } - it { is_expected.to allow_value('').for(:external_auth_client_cert) } - it { is_expected.to allow_value('').for(:external_auth_client_key) } - - context 'when setting a valid client certificate for external authorization' do - let(:certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') } - - before do - setting.external_auth_client_cert = certificate_data - end - - it 'requires a valid client key when a certificate is set' do - expect(setting).not_to allow_value('fefefe').for(:external_auth_client_key) - end - - it 'requires a matching certificate' do - other_private_key = File.read('spec/fixtures/x509_certificate_pk.key') - - expect(setting).not_to allow_value(other_private_key).for(:external_auth_client_key) - end - - it 'the credentials are valid when the private key can be read and matches the certificate' do - tls_attributes = [:external_auth_client_key_pass, - :external_auth_client_key, - :external_auth_client_cert] - setting.external_auth_client_key = File.read('spec/fixtures/passphrase_x509_certificate_pk.key') - setting.external_auth_client_key_pass = '5iveL!fe' - - setting.validate - - expect(setting.errors).not_to include(*tls_attributes) - end - end - end end context 'restrict creating duplicates' do diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb index f63ad958ed3..94798f0590d 100644 --- a/spec/models/concerns/protected_ref_access_spec.rb +++ b/spec/models/concerns/protected_ref_access_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' describe ProtectedRefAccess do - include ExternalAuthorizationServiceHelpers - subject(:protected_ref_access) do create(:protected_branch, :maintainers_can_push).push_access_levels.first end @@ -31,15 +29,5 @@ describe ProtectedRefAccess do expect(protected_ref_access.check_access(developer)).to be_falsy end - - context 'external authorization' do - it 'is false if external authorization denies access' do - maintainer = create(:user) - project.add_maintainer(maintainer) - external_service_deny_access(maintainer, project) - - expect(protected_ref_access.check_access(maintainer)).to be_falsey - end - end end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 0cd69cb4817..892dd053e39 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' describe Issue do - include ExternalAuthorizationServiceHelpers - describe "Associations" do it { is_expected.to belong_to(:milestone) } it { is_expected.to have_many(:assignees) } @@ -781,47 +779,4 @@ describe Issue do it_behaves_like 'throttled touch' do subject { create(:issue, updated_at: 1.hour.ago) } end - - context 'when an external authentication service' do - before do - enable_external_authorization_service_check - end - - describe '#visible_to_user?' do - it 'is `false` when an external authorization service is enabled' do - issue = build(:issue, project: build(:project, :public)) - - expect(issue).not_to be_visible_to_user - end - - it 'checks the external service to determine if an issue is readable by a user' do - project = build(:project, :public, - external_authorization_classification_label: 'a-label') - issue = build(:issue, project: project) - user = build(:user) - - expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).with(user, 'a-label') { false } - expect(issue.visible_to_user?(user)).to be_falsy - end - - it 'does not check the external service if a user does not have access to the project' do - project = build(:project, :private, - external_authorization_classification_label: 'a-label') - issue = build(:issue, project: project) - user = build(:user) - - expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) - expect(issue.visible_to_user?(user)).to be_falsy - end - - it 'does not check the external webservice for admins' do - issue = build(:issue) - user = build(:admin) - - expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) - - issue.visible_to_user?(user) - end - end - end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 7222580e115..5eb31430ccd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' describe Project do include ProjectForksHelper include GitHelpers - include ExternalAuthorizationServiceHelpers it_behaves_like 'having unique enum values' @@ -4418,25 +4417,6 @@ describe Project do end end - describe '#external_authorization_classification_label' do - it 'falls back to the default when none is configured' do - enable_external_authorization_service_check - - expect(build(:project).external_authorization_classification_label) - .to eq('default_label') - end - - it 'returns the classification label if it was configured on the project' do - enable_external_authorization_service_check - - project = build(:project, - external_authorization_classification_label: 'hello') - - expect(project.external_authorization_classification_label) - .to eq('hello') - end - end - describe "#pages_https_only?" do subject { build(:project) } diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb index 09be831dcd5..c03d95b34db 100644 --- a/spec/policies/base_policy_spec.rb +++ b/spec/policies/base_policy_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe BasePolicy do - include ExternalAuthorizationServiceHelpers - describe '.class_for' do it 'detects policy class based on the subject ancestors' do expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy) @@ -18,25 +16,4 @@ describe BasePolicy do expect(DeclarativePolicy.class_for(:global)).to eq(GlobalPolicy) end end - - describe 'read cross project' do - let(:current_user) { create(:user) } - let(:user) { create(:user) } - - subject { described_class.new(current_user, [user]) } - - it { is_expected.to be_allowed(:read_cross_project) } - - context 'when an external authorization service is enabled' do - before do - enable_external_authorization_service_check - end - - it { is_expected.not_to be_allowed(:read_cross_project) } - - it 'allows admins' do - expect(described_class.new(build(:admin), nil)).to be_allowed(:read_cross_project) - end - end - end end diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index b149dbcf871..008d118b557 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe IssuePolicy do - include ExternalAuthorizationServiceHelpers - let(:guest) { create(:user) } let(:author) { create(:user) } let(:assignee) { create(:user) } @@ -206,21 +204,4 @@ describe IssuePolicy do end end end - - context 'with external authorization enabled' do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - let(:issue) { create(:issue, project: project) } - let(:policies) { described_class.new(user, issue) } - - before do - enable_external_authorization_service_check - end - - it 'can read the issue iid without accessing the external service' do - expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) - - expect(policies).to be_allowed(:read_issue_iid) - end - end end diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb index 81279225d61..1efa70addc2 100644 --- a/spec/policies/merge_request_policy_spec.rb +++ b/spec/policies/merge_request_policy_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe MergeRequestPolicy do - include ExternalAuthorizationServiceHelpers - let(:guest) { create(:user) } let(:author) { create(:user) } let(:developer) { create(:user) } @@ -49,21 +47,4 @@ describe MergeRequestPolicy do expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request) end end - - context 'with external authorization enabled' do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request, source_project: project) } - let(:policies) { described_class.new(user, merge_request) } - - before do - enable_external_authorization_service_check - end - - it 'can read the issue iid without accessing the external service' do - expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) - - expect(policies).to be_allowed(:read_merge_request_iid) - end - end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 42f8bf3137b..125ed818bc6 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe ProjectPolicy do - include ExternalAuthorizationServiceHelpers include_context 'ProjectPolicy context' set(:guest) { create(:user) } set(:reporter) { create(:user) } @@ -293,56 +292,4 @@ describe ProjectPolicy do projects: [clusterable]) end end - - context 'reading a project' do - it 'allows access when a user has read access to the repo' do - expect(described_class.new(owner, project)).to be_allowed(:read_project) - expect(described_class.new(developer, project)).to be_allowed(:read_project) - expect(described_class.new(admin, project)).to be_allowed(:read_project) - end - - it 'never checks the external service' do - expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) - - expect(described_class.new(owner, project)).to be_allowed(:read_project) - end - - context 'with an external authorization service' do - before do - enable_external_authorization_service_check - end - - it 'allows access when the external service allows it' do - external_service_allow_access(owner, project) - external_service_allow_access(developer, project) - - expect(described_class.new(owner, project)).to be_allowed(:read_project) - expect(described_class.new(developer, project)).to be_allowed(:read_project) - end - - it 'does not check the external service for admins and allows access' do - expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) - - expect(described_class.new(admin, project)).to be_allowed(:read_project) - end - - it 'prevents all but seeing a public project in a list when access is denied' do - [developer, owner, build(:user), nil].each do |user| - external_service_deny_access(user, project) - policy = described_class.new(user, project) - - expect(policy).not_to be_allowed(:read_project) - expect(policy).not_to be_allowed(:owner_access) - expect(policy).not_to be_allowed(:change_namespace) - end - end - - it 'passes the full path to external authorization for logging purposes' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(owner, 'default_label', project.full_path).and_call_original - - described_class.new(owner, project).allowed?(:read_project) - end - end - end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 352ea448c00..2bfb17d9c9a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -46,8 +46,6 @@ shared_examples 'languages and percentages JSON response' do end describe API::Projects do - include ExternalAuthorizationServiceHelpers - let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } @@ -1338,39 +1336,6 @@ describe API::Projects do end end end - - context 'with external authorization' do - let(:project) do - create(:project, - namespace: user.namespace, - external_authorization_classification_label: 'the-label') - end - - context 'when the user has access to the project' do - before do - external_service_allow_access(user, project) - end - - it 'includes the label in the response' do - get api("/projects/#{project.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['external_authorization_classification_label']).to eq('the-label') - end - end - - context 'when the external service denies access' do - before do - external_service_deny_access(user, project) - end - - it 'returns a 404' do - get api("/projects/#{project.id}", user) - - expect(response).to have_gitlab_http_status(404) - end - end - end end describe 'GET /projects/:id/users' do @@ -1925,20 +1890,6 @@ describe API::Projects do expect(response).to have_gitlab_http_status(403) end end - - context 'when updating external classification' do - before do - enable_external_authorization_service_check - end - - it 'updates the classification label' do - put(api("/projects/#{project.id}", user), params: { external_authorization_classification_label: 'new label' }) - - expect(response).to have_gitlab_http_status(200) - - expect(project.reload.external_authorization_classification_label).to eq('new label') - end - end end describe 'POST /projects/:id/archive' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 2d6b2d5da23..f33eb5b9e02 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -114,39 +114,6 @@ describe API::Settings, 'Settings' do expect(json_response['performance_bar_allowed_group_id']).to be_nil end - context 'external policy classification settings' do - let(:settings) do - { - external_authorization_service_enabled: true, - external_authorization_service_url: 'https://custom.service/', - external_authorization_service_default_label: 'default', - external_authorization_service_timeout: 9.99, - external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'), - external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'), - external_auth_client_key_pass: "5iveL!fe" - } - end - let(:attribute_names) { settings.keys.map(&:to_s) } - - it 'includes the attributes in the API' do - get api("/application/settings", admin) - - expect(response).to have_gitlab_http_status(200) - attribute_names.each do |attribute| - expect(json_response.keys).to include(attribute) - end - end - - it 'allows updating the settings' do - put api("/application/settings", admin), params: settings - - expect(response).to have_gitlab_http_status(200) - settings.each do |attribute, value| - expect(ApplicationSetting.current.public_send(attribute)).to eq(value) - end - end - end - context "missing plantuml_url value when plantuml_enabled is true" do it "returns a blank parameter error message" do put api("/application/settings", admin), params: { plantuml_enabled: true } diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb index b58d95ccb43..d02b4c554b1 100644 --- a/spec/serializers/group_child_entity_spec.rb +++ b/spec/serializers/group_child_entity_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe GroupChildEntity do - include ExternalAuthorizationServiceHelpers include Gitlab::Routing.url_helpers let(:user) { create(:user) } @@ -110,22 +109,4 @@ describe GroupChildEntity do it_behaves_like 'group child json' end - - describe 'for a project with external authorization enabled' do - let(:object) do - create(:project, :with_avatar, - description: 'Awesomeness') - end - - before do - enable_external_authorization_service_check - object.add_maintainer(user) - end - - it 'does not hit the external authorization service' do - expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) - - expect(json[:can_edit]).to eq(false) - end - end end diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index 258e5635113..a4a733eff77 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe ApplicationSettings::UpdateService do - include ExternalAuthorizationServiceHelpers - let(:application_settings) { create(:application_setting) } let(:admin) { create(:user, :admin) } let(:params) { {} } @@ -145,37 +143,4 @@ describe ApplicationSettings::UpdateService do end end end - - context 'when external authorization is enabled' do - before do - enable_external_authorization_service_check - end - - it 'does not save the settings with an error if the service denies access' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(admin, 'new-label') { false } - - described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute - - expect(application_settings.errors[:external_authorization_service_default_label]).to be_present - end - - it 'saves the setting when the user has access to the label' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(admin, 'new-label') { true } - - described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute - - # Read the attribute directly to avoid the stub from - # `enable_external_authorization_service_check` - expect(application_settings[:external_authorization_service_default_label]).to eq('new-label') - end - - it 'does not validate the label if it was not passed' do - expect(::Gitlab::ExternalAuthorization) - .not_to receive(:access_allowed?) - - described_class.new(application_settings, admin, { home_page_url: 'http://foo.bar' }).execute - end - end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 794a4b4ccd3..9ba4a11104a 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe NotificationService, :mailer do include EmailSpec::Matchers - include ExternalAuthorizationServiceHelpers include NotificationHelpers let(:notification) { described_class.new } @@ -2218,46 +2217,6 @@ describe NotificationService, :mailer do end end - context 'with external authorization service' do - let(:issue) { create(:issue) } - let(:project) { issue.project } - let(:note) { create(:note, noteable: issue, project: project) } - let(:member) { create(:user) } - - subject { NotificationService.new } - - before do - project.add_maintainer(member) - member.global_notification_setting.update!(level: :watch) - end - - it 'sends email when the service is not enabled' do - expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original - - subject.new_issue(issue, member) - end - - context 'when the service is enabled' do - before do - enable_external_authorization_service_check - end - - it 'does not send an email' do - expect(Notify).not_to receive(:new_issue_email) - - subject.new_issue(issue, member) - end - - it 'still delivers email to admins' do - member.update!(admin: true) - - expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original - - subject.new_issue(issue, member) - end - end - end - def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e1ec932918e..e8418b09dc2 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Projects::CreateService, '#execute' do - include ExternalAuthorizationServiceHelpers include GitHelpers let(:gitlab_shell) { Gitlab::Shell.new } @@ -345,42 +344,6 @@ describe Projects::CreateService, '#execute' do expect(rugged.config['gitlab.fullpath']).to eq project.full_path end - context 'with external authorization enabled' do - before do - enable_external_authorization_service_check - end - - it 'does not save the project with an error if the service denies access' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(user, 'new-label', any_args) { false } - - project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' })) - - expect(project.errors[:external_authorization_classification_label]).to be_present - expect(project).not_to be_persisted - end - - it 'saves the project when the user has access to the label' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(user, 'new-label', any_args) { true } - - project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' })) - - expect(project).to be_persisted - expect(project.external_authorization_classification_label).to eq('new-label') - end - - it 'does not save the project when the user has no access to the default label and no label is provided' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(user, 'default_label', any_args) { false } - - project = create_project(user, opts) - - expect(project.errors[:external_authorization_classification_label]).to be_present - expect(project).not_to be_persisted - end - end - def create_project(user, opts) Projects::CreateService.new(user, opts).execute end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 95eb17b5e3a..90eaea9c872 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Projects::UpdateService do - include ExternalAuthorizationServiceHelpers include ProjectForksHelper let(:user) { create(:user) } @@ -362,46 +361,6 @@ describe Projects::UpdateService do call_service end end - - context 'with external authorization enabled' do - before do - enable_external_authorization_service_check - end - - it 'does not save the project with an error if the service denies access' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(user, 'new-label') { false } - - result = update_project(project, user, { external_authorization_classification_label: 'new-label' }) - - expect(result[:message]).to be_present - expect(result[:status]).to eq(:error) - end - - it 'saves the new label if the service allows access' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(user, 'new-label') { true } - - result = update_project(project, user, { external_authorization_classification_label: 'new-label' }) - - expect(result[:status]).to eq(:success) - expect(project.reload.external_authorization_classification_label).to eq('new-label') - end - - it 'checks the default label when the classification label was cleared' do - expect(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?).with(user, 'default_label') { true } - - update_project(project, user, { external_authorization_classification_label: '' }) - end - - it 'does not check the label when it does not change' do - expect(::Gitlab::ExternalAuthorization) - .not_to receive(:access_allowed?) - - update_project(project, user, { name: 'New name' }) - end - end end describe '#run_auto_devops_pipeline?' do diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb deleted file mode 100644 index 79dd9a3d58e..00000000000 --- a/spec/support/external_authorization_service_helpers.rb +++ /dev/null @@ -1,33 +0,0 @@ -module ExternalAuthorizationServiceHelpers - def enable_external_authorization_service_check - stub_application_setting(external_authorization_service_enabled: true) - - stub_application_setting(external_authorization_service_url: 'https://authorize.me') - stub_application_setting(external_authorization_service_default_label: 'default_label') - stub_request(:post, "https://authorize.me").to_return(status: 200) - end - - def external_service_set_access(allowed, user, project) - enable_external_authorization_service_check - classification_label = ::Gitlab::CurrentSettings.current_application_settings - .external_authorization_service_default_label - - # Reload the project so cached licensed features are reloaded - if project - classification_label = Project.find(project.id).external_authorization_classification_label - end - - allow(::Gitlab::ExternalAuthorization) - .to receive(:access_allowed?) - .with(user, classification_label, any_args) - .and_return(allowed) - end - - def external_service_allow_access(user, project = nil) - external_service_set_access(true, user, project) - end - - def external_service_deny_access(user, project = nil) - external_service_set_access(false, user, project) - end -end diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb deleted file mode 100644 index 8dd78fd0a25..00000000000 --- a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -shared_examples 'disabled when using an external authorization service' do - include ExternalAuthorizationServiceHelpers - - it 'works when the feature is not enabled' do - subject - - expect(response).to be_success - end - - it 'renders a 404 with a message when the feature is enabled' do - enable_external_authorization_service_check - - subject - - expect(response).to have_gitlab_http_status(403) - end -end - -shared_examples 'unauthorized when external service denies access' do - include ExternalAuthorizationServiceHelpers - - it 'allows access when the authorization service allows it' do - external_service_allow_access(user, project) - - subject - - # Account for redirects after updates - expect(response.status).to be_between(200, 302) - end - - it 'allows access when the authorization service denies it' do - external_service_deny_access(user, project) - - subject - - expect(response).to have_gitlab_http_status(403) - end -end diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb deleted file mode 100644 index d7e17cc0b70..00000000000 --- a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' - -shared_examples 'a finder with external authorization service' do - include ExternalAuthorizationServiceHelpers - - let(:user) { create(:user) } - let(:project) { create(:project) } - - before do - project.add_maintainer(user) - end - - it 'finds the subject' do - expect(described_class.new(user).execute).to include(subject) - end - - context 'with an external authorization service' do - before do - enable_external_authorization_service_check - end - - it 'does not include the subject when no project was given' do - expect(described_class.new(user).execute).not_to include(subject) - end - - it 'includes the subject when a project id was given' do - expect(described_class.new(user, project_params).execute).to include(subject) - end - end -end diff --git a/spec/validators/x509_certificate_credentials_validator_spec.rb b/spec/validators/x509_certificate_credentials_validator_spec.rb deleted file mode 100644 index 24ef68c1fab..00000000000 --- a/spec/validators/x509_certificate_credentials_validator_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'spec_helper' - -describe X509CertificateCredentialsValidator do - let(:certificate_data) { File.read('spec/fixtures/x509_certificate.crt') } - let(:pkey_data) { File.read('spec/fixtures/x509_certificate_pk.key') } - - let(:validatable) do - Class.new do - include ActiveModel::Validations - - attr_accessor :certificate, :private_key, :passphrase - - def initialize(certificate, private_key, passphrase = nil) - @certificate, @private_key, @passphrase = certificate, private_key, passphrase - end - end - end - - subject(:validator) do - described_class.new(certificate: :certificate, pkey: :private_key) - end - - it 'is not valid when the certificate is not valid' do - record = validatable.new('not a certificate', nil) - - validator.validate(record) - - expect(record.errors[:certificate]).to include('is not a valid X509 certificate.') - end - - it 'is not valid without a certificate' do - record = validatable.new(nil, nil) - - validator.validate(record) - - expect(record.errors[:certificate]).not_to be_empty - end - - context 'when a valid certificate is passed' do - let(:record) { validatable.new(certificate_data, nil) } - - it 'does not track an error for the certificate' do - validator.validate(record) - - expect(record.errors[:certificate]).to be_empty - end - - it 'adds an error when not passing a correct private key' do - validator.validate(record) - - expect(record.errors[:private_key]).to include('could not read private key, is the passphrase correct?') - end - - it 'has no error when the private key is correct' do - record.private_key = pkey_data - - validator.validate(record) - - expect(record.errors).to be_empty - end - end - - context 'when using a passphrase' do - let(:passphrase_certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') } - let(:passphrase_pkey_data) { File.read('spec/fixtures/passphrase_x509_certificate_pk.key') } - - let(:record) { validatable.new(passphrase_certificate_data, passphrase_pkey_data, '5iveL!fe') } - - subject(:validator) do - described_class.new(certificate: :certificate, pkey: :private_key, pass: :passphrase) - end - - it 'is valid with the correct data' do - validator.validate(record) - - expect(record.errors).to be_empty - end - - it 'adds an error when the passphrase is wrong' do - record.passphrase = 'wrong' - - validator.validate(record) - - expect(record.errors[:private_key]).not_to be_empty - end - end -end -- cgit v1.2.1 From 8fafd0f32ecd04ee29b7b28de6caaa1ecbafe29f Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Fri, 5 Apr 2019 13:06:24 +0000 Subject: Removes the undescriptive CI Charts header Additional changes include removal of translation records Added changelog --- spec/fixtures/valid.po | 3 --- 1 file changed, 3 deletions(-) (limited to 'spec') diff --git a/spec/fixtures/valid.po b/spec/fixtures/valid.po index dbe2f952bad..155b6cbb95d 100644 --- a/spec/fixtures/valid.po +++ b/spec/fixtures/valid.po @@ -35,9 +35,6 @@ msgid_plural "%d pipelines" msgstr[0] "1 pipeline" msgstr[1] "%d pipelines" -msgid "A collection of graphs regarding Continuous Integration" -msgstr "Una colección de gráficos sobre Integración Continua" - msgid "About auto deploy" msgstr "Acerca del auto despliegue" -- cgit v1.2.1 From e32c82ed1eabc0e409e91d5d210f1ecc6fd4c091 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 5 Apr 2019 13:18:35 +0000 Subject: Revert "Merge branch 'require-all-templates-to-include-default-stages' into 'master'" This reverts merge request !26954 --- spec/lib/gitlab/ci/templates/templates_spec.rb | 54 ++++++++++++++++---------- spec/lib/gitlab/ci/yaml_processor_spec.rb | 13 ++----- 2 files changed, 37 insertions(+), 30 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index b52064b3036..4e3681cd943 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -3,32 +3,46 @@ require 'spec_helper' describe "CI YML Templates" do - using RSpec::Parameterized::TableSyntax - - subject { Gitlab::Ci::YamlProcessor.new(content) } - - where(:template_name) do - Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name) + ABSTRACT_TEMPLATES = %w[Serverless].freeze + # These templates depend on the presence of the `project` + # param to enable processing of `include:` within CI config. + PROJECT_DEPENDENT_TEMPLATES = %w[Auto-DevOps DAST].freeze + + def self.concrete_templates + Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template| + ABSTRACT_TEMPLATES.include?(template.name) + end end - with_them do - let(:content) do - <<~EOS - include: - - template: #{template_name} - - concrete_build_implemented_by_a_user: - stage: test - script: do something - EOS + def self.abstract_templates + Gitlab::Template::GitlabCiYmlTemplate.all.select do |template| + ABSTRACT_TEMPLATES.include?(template.name) end + end + + describe 'concrete templates with CI/CD jobs' do + concrete_templates.each do |template| + it "#{template.name} template should be valid" do + # Trigger processing of included files + project = create(:project, :test_repo) if PROJECT_DEPENDENT_TEMPLATES.include?(template.name) - it 'is valid' do - expect { subject }.not_to raise_error + expect { Gitlab::Ci::YamlProcessor.new(template.content, project: project) } + .not_to raise_error + end end + end - it 'require default stages to be included' do - expect(subject.stages).to include(*Gitlab::Ci::Config::Entry::Stages.default) + describe 'abstract templates without concrete jobs defined' do + abstract_templates.each do |template| + it "#{template.name} template should be valid after being implemented" do + content = template.content + <<~EOS + concrete_build_implemented_by_a_user: + stage: build + script: do something + EOS + + expect { Gitlab::Ci::YamlProcessor.new(content) }.not_to raise_error + end end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 2bea17ed030..8b39c4e4dd0 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -626,8 +626,8 @@ module Gitlab context "when an array is provided" do let(:include_content) { ["/local.gitlab-ci.yml"] } - it "returns a validation error" do - expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /does not have project/) + it "does not return any error" do + expect { subject }.not_to raise_error end end @@ -643,18 +643,11 @@ module Gitlab let(:include_content) do [ 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml', + '/templates/.after-script-template.yml', { template: 'Auto-DevOps.gitlab-ci.yml' } ] end - before do - WebMock.stub_request(:get, 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml') - .to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: 'prepare: { script: ls -al }') - end - it "does not return any error" do expect { subject }.not_to raise_error end -- cgit v1.2.1 From 770f721962cd30e930ab7b6e06e9386da6325c3c Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Fri, 5 Apr 2019 22:07:09 +0900 Subject: Refactor: extract duplicate steps to a service class --- .../projects/update_statistics_service_spec.rb | 40 ++++++++++++++++++++ spec/workers/project_cache_worker_spec.rb | 19 ++++++---- .../update_project_statistics_worker_spec.rb | 43 +++------------------- 3 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 spec/services/projects/update_statistics_service_spec.rb (limited to 'spec') diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb new file mode 100644 index 00000000000..7e351c9ce54 --- /dev/null +++ b/spec/services/projects/update_statistics_service_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Projects::UpdateStatisticsService do + let(:service) { described_class.new(project, nil, statistics: statistics)} + let(:statistics) { %w(repository_size) } + + describe '#execute' do + context 'with a non-existing project' do + let(:project) { nil } + + it 'does nothing' do + expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!) + + service.execute + end + end + + context 'with an existing project without a repository' do + let(:project) { create(:project) } + + it 'does nothing' do + expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!) + + service.execute + end + end + + context 'with an existing project with a repository' do + let(:project) { create(:project, :repository) } + + it 'refreshes the project statistics' do + expect_any_instance_of(ProjectStatistics).to receive(:refresh!) + .with(only: statistics.map(&:to_sym)) + .and_call_original + + service.execute + end + end + end +end diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index d2445f420f8..3c40269adc7 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -48,7 +48,7 @@ describe ProjectCacheWorker do it 'updates the project statistics' do expect(worker).to receive(:update_statistics) - .with(kind_of(Project), %i(repository_size)) + .with(kind_of(Project), statistics) .and_call_original worker.perform(project.id, [], statistics) @@ -73,28 +73,31 @@ describe ProjectCacheWorker do let(:statistics) { %w(repository_size) } context 'when a lease could not be obtained' do - it 'does not update the repository size' do + it 'does not update the project statistics' do stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) + expect(Projects::UpdateStatisticsService).not_to receive(:new) + expect(UpdateProjectStatisticsWorker).not_to receive(:perform_in) - worker.update_statistics(project, statistics.map(&:to_sym)) + worker.update_statistics(project, statistics) end end context 'when a lease could be obtained' do - it 'updates the project statistics' do + it 'updates the project statistics twice' do stub_exclusive_lease(lease_key, timeout: lease_timeout) - expect(project.statistics).to receive(:refresh!) - .with(only: statistics.map(&:to_sym)) + expect(Projects::UpdateStatisticsService).to receive(:new) + .with(project, nil, statistics: statistics) .and_call_original + .twice expect(UpdateProjectStatisticsWorker).to receive(:perform_in) - .with(lease_timeout, project.id, statistics.map(&:to_sym)) + .with(lease_timeout, project.id, statistics) .and_call_original - worker.update_statistics(project, statistics.map(&:to_sym)) + worker.update_statistics(project, statistics) end end end diff --git a/spec/workers/update_project_statistics_worker_spec.rb b/spec/workers/update_project_statistics_worker_spec.rb index 3411e10da7e..a268fd2e4ba 100644 --- a/spec/workers/update_project_statistics_worker_spec.rb +++ b/spec/workers/update_project_statistics_worker_spec.rb @@ -3,46 +3,15 @@ require 'spec_helper' describe UpdateProjectStatisticsWorker do let(:worker) { described_class.new } let(:project) { create(:project, :repository) } + let(:statistics) { %w(repository_size) } describe '#perform' do - context 'with a non-existing project' do - it 'does nothing' do - expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!) + it 'updates the project statistics' do + expect(Projects::UpdateStatisticsService).to receive(:new) + .with(project, nil, statistics: statistics) + .and_call_original - worker.perform(-1) - end - end - - context 'with an existing project without a repository' do - it 'does nothing' do - allow_any_instance_of(Repository).to receive(:exists?).and_return(false) - - expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!) - - worker.perform(project.id) - end - end - - context 'with an existing project' do - it 'refreshes the project statistics' do - expect_any_instance_of(ProjectStatistics).to receive(:refresh!) - .with(only: []) - .and_call_original - - worker.perform(project.id) - end - - context 'with a specific statistics target' do - it 'refreshes the project repository size' do - statistics_target = %w(repository_size) - - expect_any_instance_of(ProjectStatistics).to receive(:refresh!) - .with(only: statistics_target.map(&:to_sym)) - .and_call_original - - worker.perform(project.id, statistics_target) - end - end + worker.perform(project.id, statistics) end end end -- cgit v1.2.1 From cca4a5fd37b18590767f1ff8d2e2240f06060c0d Mon Sep 17 00:00:00 2001 From: Tucker Chapman Date: Fri, 5 Apr 2019 15:50:21 +0000 Subject: Add checkbox to choose to create new list when creating new label --- spec/features/boards/boards_spec.rb | 2 +- spec/features/boards/sidebar_spec.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index ea69ec0319b..4c6175f5590 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -345,7 +345,7 @@ describe 'Issue Boards', :js do click_link 'Create project label' - fill_in('new_label_name', with: 'Testing New Label') + fill_in('new_label_name', with: 'Testing New Label - with list') first('.suggest-colors a').click diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index ee38e756f9e..dfdb8d589eb 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -343,6 +343,24 @@ describe 'Issue Boards', :js do expect(page).to have_link 'test label' end + expect(page).to have_selector('.board', count: 3) + end + + it 'creates project label and list' do + click_card(card) + + page.within('.labels') do + click_link 'Edit' + click_link 'Create project label' + fill_in 'new_label_name', with: 'test label' + first('.suggest-colors-dropdown a').click + first('.js-add-list').click + click_button 'Create' + wait_for_requests + + expect(page).to have_link 'test label' + end + expect(page).to have_selector('.board', count: 4) end end -- cgit v1.2.1 From e46d4bf4da3ee207043c85524df238475e47d650 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 28 Mar 2019 14:59:24 +0000 Subject: Extract a Git::{Base,Tag,Branch}HooksService --- spec/services/after_branch_delete_service_spec.rb | 15 - spec/services/git/branch_hooks_service_spec.rb | 339 ++++++++++++++++++++++ spec/services/git/branch_push_service_spec.rb | 258 +++------------- spec/services/git/tag_hooks_service_spec.rb | 144 +++++++++ spec/services/git/tag_push_service_spec.rb | 179 +----------- spec/workers/post_receive_spec.rb | 25 +- 6 files changed, 560 insertions(+), 400 deletions(-) delete mode 100644 spec/services/after_branch_delete_service_spec.rb create mode 100644 spec/services/git/branch_hooks_service_spec.rb create mode 100644 spec/services/git/tag_hooks_service_spec.rb (limited to 'spec') diff --git a/spec/services/after_branch_delete_service_spec.rb b/spec/services/after_branch_delete_service_spec.rb deleted file mode 100644 index bc9747d1413..00000000000 --- a/spec/services/after_branch_delete_service_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe AfterBranchDeleteService do - let(:project) { create(:project, :repository) } - let(:user) { create(:user) } - let(:service) { described_class.new(project, user) } - - describe '#execute' do - it 'stops environments attached to branch' do - expect(service).to receive(:stop_environments) - - service.execute('feature') - end - end -end diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb new file mode 100644 index 00000000000..bb87267db7d --- /dev/null +++ b/spec/services/git/branch_hooks_service_spec.rb @@ -0,0 +1,339 @@ +require 'spec_helper' + +describe Git::BranchHooksService do + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:user) { project.creator } + + let(:branch) { project.default_branch } + let(:ref) { "refs/heads/#{branch}" } + let(:commit) { project.commit(sample_commit.id) } + let(:oldrev) { commit.parent_id } + let(:newrev) { commit.id } + + let(:service) do + described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) + end + + describe "Git Push Data" do + subject(:push_data) { service.execute } + + it 'has expected push data attributes' do + is_expected.to match a_hash_including( + object_kind: 'push', + before: oldrev, + after: newrev, + ref: ref, + user_id: user.id, + user_name: user.name, + project_id: project.id + ) + end + + context "with repository data" do + subject { push_data[:repository] } + + it 'has expected attributes' do + is_expected.to match a_hash_including( + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url + ) + end + end + + context "with commits" do + subject { push_data[:commits] } + + it { is_expected.to be_an(Array) } + + it 'has 1 element' do + expect(subject.size).to eq(1) + end + + context "the commit" do + subject { push_data[:commits].first } + + it { expect(subject[:timestamp].in_time_zone).to eq(commit.date.in_time_zone) } + + it 'includes expected commit data' do + is_expected.to match a_hash_including( + id: commit.id, + message: commit.safe_message, + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + commit.id + ].join('/') + ) + end + + context "with a author" do + subject { push_data[:commits].first[:author] } + + it 'includes expected author data' do + is_expected.to match a_hash_including( + name: commit.author_name, + email: commit.author_email + ) + end + end + end + end + end + + describe 'Push Event' do + let(:event) { Event.find_by_action(Event::PUSHED) } + + before do + service.execute + end + + context "with an existing branch" do + it 'generates a push event with one commit' do + expect(event).to be_an_instance_of(PushEvent) + expect(event.project).to eq(project) + expect(event.action).to eq(Event::PUSHED) + expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) + expect(event.push_event_payload.commit_from).to eq(oldrev) + expect(event.push_event_payload.commit_to).to eq(newrev) + expect(event.push_event_payload.ref).to eq('master') + expect(event.push_event_payload.commit_count).to eq(1) + end + end + + context "with a new branch" do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it 'generates a push event with more than one commit' do + expect(event).to be_an_instance_of(PushEvent) + expect(event.project).to eq(project) + expect(event.action).to eq(Event::PUSHED) + expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) + expect(event.push_event_payload.commit_from).to be_nil + expect(event.push_event_payload.commit_to).to eq(newrev) + expect(event.push_event_payload.ref).to eq('master') + expect(event.push_event_payload.commit_count).to be > 1 + end + end + + context 'removing a branch' do + let(:newrev) { Gitlab::Git::BLANK_SHA } + + it 'generates a push event with no commits' do + expect(event).to be_an_instance_of(PushEvent) + expect(event.project).to eq(project) + expect(event.action).to eq(Event::PUSHED) + expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) + expect(event.push_event_payload.commit_from).to eq(oldrev) + expect(event.push_event_payload.commit_to).to be_nil + expect(event.push_event_payload.ref).to eq('master') + expect(event.push_event_payload.commit_count).to eq(0) + end + end + end + + describe 'Invalidating project cache' do + let(:commit_id) do + project.repository.update_file( + user, 'README.md', '', message: 'Update', branch_name: branch + ) + end + + let(:commit) { project.repository.commit(commit_id) } + let(:blank_sha) { Gitlab::Git::BLANK_SHA } + + def clears_cache(extended: []) + expect(ProjectCacheWorker) + .to receive(:perform_async) + .with(project.id, extended, %i[commit_count repository_size]) + + service.execute + end + + def clears_extended_cache + clears_cache(extended: %i[readme]) + end + + context 'on default branch' do + context 'create' do + # FIXME: When creating the default branch,the cache worker runs twice + before do + allow(ProjectCacheWorker).to receive(:perform_async) + end + + let(:oldrev) { blank_sha } + + it { clears_cache } + end + + context 'update' do + it { clears_extended_cache } + end + + context 'remove' do + let(:newrev) { blank_sha } + + # TODO: this case should pass, but we only take account of added files + it { clears_cache } + end + end + + context 'on ordinary branch' do + let(:branch) { 'fix' } + + context 'create' do + let(:oldrev) { blank_sha } + + it { clears_cache } + end + + context 'update' do + it { clears_cache } + end + + context 'remove' do + let(:newrev) { blank_sha } + + it { clears_cache } + end + end + end + + describe 'GPG signatures' do + context 'when the commit has a signature' do + context 'when the signature is already cached' do + before do + create(:gpg_signature, commit_sha: commit.id) + end + + it 'does not queue a CreateGpgSignatureWorker' do + expect(CreateGpgSignatureWorker).not_to receive(:perform_async) + + service.execute + end + end + + context 'when the signature is not yet cached' do + it 'queues a CreateGpgSignatureWorker' do + expect(CreateGpgSignatureWorker).to receive(:perform_async).with([commit.id], project.id) + + service.execute + end + + it 'can queue several commits to create the gpg signature' do + allow(Gitlab::Git::Commit) + .to receive(:shas_with_signatures) + .and_return([sample_commit.id, another_sample_commit.id]) + + expect(CreateGpgSignatureWorker) + .to receive(:perform_async) + .with([sample_commit.id, another_sample_commit.id], project.id) + + service.execute + end + end + end + + context 'when the commit does not have a signature' do + before do + allow(Gitlab::Git::Commit) + .to receive(:shas_with_signatures) + .with(project.repository, [sample_commit.id]) + .and_return([]) + end + + it 'does not queue a CreateGpgSignatureWorker' do + expect(CreateGpgSignatureWorker) + .not_to receive(:perform_async) + .with(sample_commit.id, project.id) + + service.execute + end + end + end + + describe 'Processing commit messages' do + # Create 4 commits, 2 of which have references. Limiting to 2 commits, we + # expect to see one commit message processor enqueued. + let(:commit_ids) do + Array.new(4) do |i| + message = "Issue #{'#' if i.even?}#{i}" + project.repository.update_file( + user, 'README.md', '', message: message, branch_name: branch + ) + end + end + + let(:oldrev) { commit_ids.first } + let(:newrev) { commit_ids.last } + + before do + stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 2) + end + + context 'creating the default branch' do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it 'does not process commit messages' do + expect(ProcessCommitWorker).not_to receive(:perform_async) + + service.execute + end + end + + context 'updating the default branch' do + it 'processes a limited number of commit messages' do + expect(ProcessCommitWorker).to receive(:perform_async).once + + service.execute + end + end + + context 'removing the default branch' do + let(:newrev) { Gitlab::Git::BLANK_SHA } + + it 'does not process commit messages' do + expect(ProcessCommitWorker).not_to receive(:perform_async) + + service.execute + end + end + + context 'creating a normal branch' do + let(:branch) { 'fix' } + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it 'processes a limited number of commit messages' do + expect(ProcessCommitWorker).to receive(:perform_async).once + + service.execute + end + end + + context 'updating a normal branch' do + let(:branch) { 'fix' } + + it 'processes a limited number of commit messages' do + expect(ProcessCommitWorker).to receive(:perform_async).once + + service.execute + end + end + + context 'removing a normal branch' do + let(:branch) { 'fix' } + let(:newrev) { Gitlab::Git::BLANK_SHA } + + it 'does not process commit messages' do + expect(ProcessCommitWorker).not_to receive(:perform_async) + + service.execute + end + end + end +end diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index d0e2169b4a6..322e40a8112 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -8,7 +8,8 @@ describe Git::BranchPushService, services: true do let(:blankrev) { Gitlab::Git::BLANK_SHA } let(:oldrev) { sample_commit.parent_id } let(:newrev) { sample_commit.id } - let(:ref) { 'refs/heads/master' } + let(:branch) { 'master' } + let(:ref) { "refs/heads/#{branch}" } before do project.add_maintainer(user) @@ -132,64 +133,6 @@ describe Git::BranchPushService, services: true do end end - describe "Git Push Data" do - let(:commit) { project.commit(newrev) } - - subject { push_data_from_service(project, user, oldrev, newrev, ref) } - - it { is_expected.to include(object_kind: 'push') } - it { is_expected.to include(before: oldrev) } - it { is_expected.to include(after: newrev) } - it { is_expected.to include(ref: ref) } - it { is_expected.to include(user_id: user.id) } - it { is_expected.to include(user_name: user.name) } - it { is_expected.to include(project_id: project.id) } - - context "with repository data" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:repository] } - - it { is_expected.to include(name: project.name) } - it { is_expected.to include(url: project.url_to_repo) } - it { is_expected.to include(description: project.description) } - it { is_expected.to include(homepage: project.web_url) } - end - - context "with commits" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits] } - - it { is_expected.to be_an(Array) } - it 'has 1 element' do - expect(subject.size).to eq(1) - end - - context "the commit" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first } - - it { is_expected.to include(id: commit.id) } - it { is_expected.to include(message: commit.safe_message) } - it { expect(subject[:timestamp].in_time_zone).to eq(commit.date.in_time_zone) } - it do - is_expected.to include( - url: [ - Gitlab.config.gitlab.url, - project.namespace.to_param, - project.to_param, - 'commit', - commit.id - ].join('/') - ) - end - - context "with a author" do - subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first[:author] } - - it { is_expected.to include(name: commit.author_name) } - it { is_expected.to include(email: commit.author_email) } - end - end - end - end - describe "Pipelines" do subject { execute_service(project, user, oldrev, newrev, ref) } @@ -203,59 +146,13 @@ describe Git::BranchPushService, services: true do end end - describe "Push Event" do - context "with an existing branch" do - let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) } - let(:event) { Event.find_by_action(Event::PUSHED) } - - it 'generates a push event with one commit' do - expect(event).to be_an_instance_of(PushEvent) - expect(event.project).to eq(project) - expect(event.action).to eq(Event::PUSHED) - expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) - expect(event.push_event_payload.commit_from).to eq(oldrev) - expect(event.push_event_payload.commit_to).to eq(newrev) - expect(event.push_event_payload.ref).to eq('master') - expect(event.push_event_payload.commit_count).to eq(1) - end - end - - context "with a new branch" do - let!(:new_branch_data) { push_data_from_service(project, user, Gitlab::Git::BLANK_SHA, newrev, ref) } - let(:event) { Event.find_by_action(Event::PUSHED) } - - it 'generates a push event with more than one commit' do - expect(event).to be_an_instance_of(PushEvent) - expect(event.project).to eq(project) - expect(event.action).to eq(Event::PUSHED) - expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) - expect(event.push_event_payload.commit_from).to be_nil - expect(event.push_event_payload.commit_to).to eq(newrev) - expect(event.push_event_payload.ref).to eq('master') - expect(event.push_event_payload.commit_count).to be > 1 - end - end - - context "Updates merge requests" do - it "when pushing a new branch for the first time" do - expect(UpdateMergeRequestsWorker).to receive(:perform_async) - .with(project.id, user.id, blankrev, 'newrev', ref) - execute_service(project, user, blankrev, 'newrev', ref ) - end - end - - describe 'system hooks' do - let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) } - let!(:system_hooks_service) { SystemHooksService.new } + describe "Updates merge requests" do + it "when pushing a new branch for the first time" do + expect(UpdateMergeRequestsWorker) + .to receive(:perform_async) + .with(project.id, user.id, blankrev, 'newrev', ref) - it "sends a system hook after pushing a branch" do - allow(SystemHooksService).to receive(:new).and_return(system_hooks_service) - allow(system_hooks_service).to receive(:execute_hooks) - - execute_service(project, user, oldrev, newrev, ref) - - expect(system_hooks_service).to have_received(:execute_hooks).with(push_data, :push_hooks) - end + execute_service(project, user, blankrev, 'newrev', ref ) end end @@ -700,125 +597,64 @@ describe Git::BranchPushService, services: true do end end - describe '#update_caches' do - let(:service) do - described_class.new(project, - user, - oldrev: oldrev, - newrev: newrev, - ref: ref) - end - - context 'on the default branch' do - before do - allow(service).to receive(:default_branch?).and_return(true) - end - - it 'flushes the caches of any special files that have been changed' do - commit = double(:commit) - diff = double(:diff, new_path: 'README.md') - - expect(commit).to receive(:raw_deltas) - .and_return([diff]) - - service.push_commits = [commit] + describe "CI environments" do + context 'create branch' do + let(:oldrev) { blankrev } - expect(ProjectCacheWorker).to receive(:perform_async) - .with(project.id, %i(readme), %i(commit_count repository_size)) + it 'does nothing' do + expect(::Ci::StopEnvironmentsService).not_to receive(:new) - service.update_caches + execute_service(project, user, oldrev, newrev, ref) end end - context 'on a non-default branch' do - before do - allow(service).to receive(:default_branch?).and_return(false) - end - - it 'does not flush any conditional caches' do - expect(ProjectCacheWorker).to receive(:perform_async) - .with(project.id, [], %i(commit_count repository_size)) - .and_call_original + context 'update branch' do + it 'does nothing' do + expect(::Ci::StopEnvironmentsService).not_to receive(:new) - service.update_caches + execute_service(project, user, oldrev, newrev, ref) end end - end - - describe '#process_commit_messages' do - let(:service) do - described_class.new(project, - user, - oldrev: oldrev, - newrev: newrev, - ref: ref) - end - it 'only schedules a limited number of commits' do - service.push_commits = Array.new(1000, double(:commit, to_hash: {}, matches_cross_reference_regex?: true)) - - expect(ProcessCommitWorker).to receive(:perform_async).exactly(100).times - - service.process_commit_messages - end - - it "skips commits which don't include cross-references" do - service.push_commits = [double(:commit, to_hash: {}, matches_cross_reference_regex?: false)] - - expect(ProcessCommitWorker).not_to receive(:perform_async) - - service.process_commit_messages - end - end - - describe '#update_signatures' do - let(:service) do - described_class.new( - project, - user, - oldrev: oldrev, - newrev: newrev, - ref: 'refs/heads/master' - ) - end + context 'delete branch' do + let(:newrev) { blankrev } - context 'when the commit has a signature' do - context 'when the signature is already cached' do - before do - create(:gpg_signature, commit_sha: sample_commit.id) + it 'stops environments' do + expect_next_instance_of(::Ci::StopEnvironmentsService) do |stop_service| + expect(stop_service.project).to eq(project) + expect(stop_service.current_user).to eq(user) + expect(stop_service).to receive(:execute).with(branch) end - it 'does not queue a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).not_to receive(:perform_async) - - execute_service(project, user, oldrev, newrev, ref) - end + execute_service(project, user, oldrev, newrev, ref) end + end + end - context 'when the signature is not yet cached' do - it 'queues a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id], project.id) + describe 'Hooks' do + context 'run on a branch' do + it 'delegates to Git::BranchHooksService' do + expect_next_instance_of(::Git::BranchHooksService) do |hooks_service| + expect(hooks_service.project).to eq(project) + expect(hooks_service.current_user).to eq(user) + expect(hooks_service.params).to include( + oldrev: oldrev, + newrev: newrev, + ref: ref + ) - execute_service(project, user, oldrev, newrev, ref) + expect(hooks_service).to receive(:execute) end - it 'can queue several commits to create the gpg signature' do - allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).and_return([sample_commit.id, another_sample_commit.id]) - - expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id, another_sample_commit.id], project.id) - - execute_service(project, user, oldrev, newrev, ref) - end + execute_service(project, user, oldrev, newrev, ref) end end - context 'when the commit does not have a signature' do - before do - allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).with(project.repository, [sample_commit.id]).and_return([]) - end + context 'run on a tag' do + let(:ref) { 'refs/tags/v1.1.0' } - it 'does not queue a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).not_to receive(:perform_async).with(sample_commit.id, project.id) + it 'does nothing' do + expect(::Git::BranchHooksService).not_to receive(:new) execute_service(project, user, oldrev, newrev, ref) end @@ -830,8 +666,4 @@ describe Git::BranchPushService, services: true do service.execute service end - - def push_data_from_service(project, user, oldrev, newrev, ref) - execute_service(project, user, oldrev, newrev, ref).push_data - end end diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb new file mode 100644 index 00000000000..8f91ce3b4c5 --- /dev/null +++ b/spec/services/git/tag_hooks_service_spec.rb @@ -0,0 +1,144 @@ +require 'spec_helper' + +describe Git::TagHooksService, :service do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + let(:oldrev) { Gitlab::Git::BLANK_SHA } + let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0 + let(:ref) { "refs/tags/#{tag_name}" } + let(:tag_name) { 'v1.1.0' } + + let(:tag) { project.repository.find_tag(tag_name) } + let(:commit) { tag.dereferenced_target } + + let(:service) do + described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) + end + + describe 'System hooks' do + it 'Executes system hooks' do + push_data = service.execute + + expect_next_instance_of(SystemHooksService) do |system_hooks_service| + expect(system_hooks_service) + .to receive(:execute_hooks) + .with(push_data, :tag_push_hooks) + end + + service.execute + end + end + + describe "Webhooks" do + it "executes hooks on the project" do + expect(project).to receive(:execute_hooks) + + service.execute + end + end + + describe "Pipelines" do + before do + stub_ci_pipeline_to_return_yaml_file + project.add_developer(user) + end + + it "creates a new pipeline" do + expect { service.execute }.to change { Ci::Pipeline.count } + + expect(Ci::Pipeline.last).to be_push + end + end + + describe 'Push data' do + shared_examples_for 'tag push data expectations' do + subject(:push_data) { service.execute } + it 'has expected push data attributes' do + is_expected.to match a_hash_including( + object_kind: 'tag_push', + ref: ref, + before: oldrev, + after: newrev, + message: tag.message, + user_id: user.id, + user_name: user.name, + project_id: project.id + ) + end + + context "with repository data" do + subject { push_data[:repository] } + + it 'has expected repository attributes' do + is_expected.to match a_hash_including( + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url + ) + end + end + + context "with commits" do + subject { push_data[:commits] } + + it { is_expected.to be_an(Array) } + + it 'has 1 element' do + expect(subject.size).to eq(1) + end + + context "the commit" do + subject { push_data[:commits].first } + + it { is_expected.to include(timestamp: commit.date.xmlschema) } + + it 'has expected commit attributes' do + is_expected.to match a_hash_including( + id: commit.id, + message: commit.safe_message, + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + commit.id + ].join('/') + ) + end + + context "with an author" do + subject { push_data[:commits].first[:author] } + + it 'has expected author attributes' do + is_expected.to match a_hash_including( + name: commit.author_name, + email: commit.author_email + ) + end + end + end + end + end + + context 'annotated tag' do + include_examples 'tag push data expectations' + end + + context 'lightweight tag' do + let(:tag_name) { 'light-tag' } + let(:newrev) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } + + before do + # Create the lightweight tag + rugged_repo(project.repository).tags.create(tag_name, newrev) + + # Clear tag list cache + project.repository.expire_tags_cache + end + + include_examples 'tag push data expectations' + end + end +end diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb index 2d960fc9f08..5e89a912060 100644 --- a/spec/services/git/tag_push_service_spec.rb +++ b/spec/services/git/tag_push_service_spec.rb @@ -31,178 +31,27 @@ describe Git::TagPushService do end end - describe 'System Hooks' do - let!(:push_data) { service.tap(&:execute).push_data } - - it "executes system hooks after pushing a tag" do - expect_next_instance_of(SystemHooksService) do |system_hooks_service| - expect(system_hooks_service) - .to receive(:execute_hooks) - .with(push_data, :tag_push_hooks) - end - - service.execute - end - end - - describe "Pipelines" do - subject { service.execute } - - before do - stub_ci_pipeline_to_return_yaml_file - project.add_developer(user) - end - - it "creates a new pipeline" do - expect { subject }.to change { Ci::Pipeline.count } - expect(Ci::Pipeline.last).to be_push - end - end - - describe "Git Tag Push Data" do - subject { @push_data } - let(:tag) { project.repository.find_tag(tag_name) } - let(:commit) { tag.dereferenced_target } - - context 'annotated tag' do - let(:tag_name) { Gitlab::Git.ref_name(ref) } - - before do - service.execute - @push_data = service.push_data - end - - it { is_expected.to include(object_kind: 'tag_push') } - it { is_expected.to include(ref: ref) } - it { is_expected.to include(before: oldrev) } - it { is_expected.to include(after: newrev) } - it { is_expected.to include(message: tag.message) } - it { is_expected.to include(user_id: user.id) } - it { is_expected.to include(user_name: user.name) } - it { is_expected.to include(project_id: project.id) } - - context "with repository data" do - subject { @push_data[:repository] } - - it { is_expected.to include(name: project.name) } - it { is_expected.to include(url: project.url_to_repo) } - it { is_expected.to include(description: project.description) } - it { is_expected.to include(homepage: project.web_url) } - end - - context "with commits" do - subject { @push_data[:commits] } - - it { is_expected.to be_an(Array) } - it 'has 1 element' do - expect(subject.size).to eq(1) - end - - context "the commit" do - subject { @push_data[:commits].first } - - it { is_expected.to include(id: commit.id) } - it { is_expected.to include(message: commit.safe_message) } - it { is_expected.to include(timestamp: commit.date.xmlschema) } - it do - is_expected.to include( - url: [ - Gitlab.config.gitlab.url, - project.namespace.to_param, - project.to_param, - 'commit', - commit.id - ].join('/') - ) - end - - context "with a author" do - subject { @push_data[:commits].first[:author] } - - it { is_expected.to include(name: commit.author_name) } - it { is_expected.to include(email: commit.author_email) } - end + describe 'Hooks' do + context 'run on a tag' do + it 'delegates to Git::TagHooksService' do + expect_next_instance_of(::Git::TagHooksService) do |hooks_service| + expect(hooks_service.project).to eq(service.project) + expect(hooks_service.current_user).to eq(service.current_user) + expect(hooks_service.params).to eq(service.params) + + expect(hooks_service).to receive(:execute) end - end - end - - context 'lightweight tag' do - let(:tag_name) { 'light-tag' } - let(:newrev) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } - let(:ref) { "refs/tags/light-tag" } - - before do - # Create the lightweight tag - rugged_repo(project.repository).tags.create(tag_name, newrev) - - # Clear tag list cache - project.repository.expire_tags_cache service.execute - @push_data = service.push_data - end - - it { is_expected.to include(object_kind: 'tag_push') } - it { is_expected.to include(ref: ref) } - it { is_expected.to include(before: oldrev) } - it { is_expected.to include(after: newrev) } - it { is_expected.to include(message: tag.message) } - it { is_expected.to include(user_id: user.id) } - it { is_expected.to include(user_name: user.name) } - it { is_expected.to include(project_id: project.id) } - - context "with repository data" do - subject { @push_data[:repository] } - - it { is_expected.to include(name: project.name) } - it { is_expected.to include(url: project.url_to_repo) } - it { is_expected.to include(description: project.description) } - it { is_expected.to include(homepage: project.web_url) } - end - - context "with commits" do - subject { @push_data[:commits] } - - it { is_expected.to be_an(Array) } - it 'has 1 element' do - expect(subject.size).to eq(1) - end - - context "the commit" do - subject { @push_data[:commits].first } - - it { is_expected.to include(id: commit.id) } - it { is_expected.to include(message: commit.safe_message) } - it { is_expected.to include(timestamp: commit.date.xmlschema) } - it do - is_expected.to include( - url: [ - Gitlab.config.gitlab.url, - project.namespace.to_param, - project.to_param, - 'commit', - commit.id - ].join('/') - ) - end - - context "with a author" do - subject { @push_data[:commits].first[:author] } - - it { is_expected.to include(name: commit.author_name) } - it { is_expected.to include(email: commit.author_email) } - end - end end end - end - describe "Webhooks" do - context "execute webhooks" do - let(:service) { described_class.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') } + context 'run on a branch' do + let(:ref) { 'refs/heads/master' } + + it 'does nothing' do + expect(::Git::BranchHooksService).not_to receive(:new) - it "when pushing tags" do - expect(project).to receive(:execute_hooks) service.execute end end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 66958a4c116..a3fe8fa4501 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -63,8 +63,12 @@ describe PostReceive do let(:changes) { "123456 789012 refs/heads/tést" } it "calls Git::BranchPushService" do - expect_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true) - expect_any_instance_of(Git::TagPushService).not_to receive(:execute) + expect_next_instance_of(Git::BranchPushService) do |service| + expect(service).to receive(:execute).and_return(true) + end + + expect(Git::TagPushService).not_to receive(:new) + described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -73,8 +77,12 @@ describe PostReceive do let(:changes) { "123456 789012 refs/tags/tag" } it "calls Git::TagPushService" do - expect_any_instance_of(Git::BranchPushService).not_to receive(:execute) - expect_any_instance_of(Git::TagPushService).to receive(:execute).and_return(true) + expect(Git::BranchPushService).not_to receive(:execute) + + expect_next_instance_of(Git::TagPushService) do |service| + expect(service).to receive(:execute).and_return(true) + end + described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -83,8 +91,9 @@ describe PostReceive do let(:changes) { "123456 789012 refs/merge-requests/123" } it "does not call any of the services" do - expect_any_instance_of(Git::BranchPushService).not_to receive(:execute) - expect_any_instance_of(Git::TagPushService).not_to receive(:execute) + expect(Git::BranchPushService).not_to receive(:new) + expect(Git::TagPushService).not_to receive(:new) + described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -127,7 +136,9 @@ describe PostReceive do allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data) # silence hooks so we can isolate allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true) - allow_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true) + expect_next_instance_of(Git::BranchPushService) do |service| + expect(service).to receive(:execute).and_return(true) + end end it 'calls SystemHooksService' do -- cgit v1.2.1 From 64858317adc4f017fe589342155faba9df31f093 Mon Sep 17 00:00:00 2001 From: Gosia Ksionek Date: Fri, 5 Apr 2019 18:49:46 +0000 Subject: Add part of needed code Add columns to store project creation settings Add project creation level column in groups and default project creation column in application settings Remove obsolete line from schema Update migration with project_creation_level column existence check Rename migrations to avoid conflicts Update migration methods Update migration method --- .../admin/application_settings_controller_spec.rb | 7 ++ spec/controllers/admin/groups_controller_spec.rb | 6 ++ spec/controllers/groups_controller_spec.rb | 7 ++ spec/factories/groups.rb | 1 + spec/features/groups/group_settings_spec.rb | 8 ++ spec/features/projects/new_project_spec.rb | 19 ++++ .../features/projects/user_creates_project_spec.rb | 27 +++++ spec/helpers/namespaces_helper_spec.rb | 72 ++++++++++++- spec/models/group_spec.rb | 8 ++ spec/policies/group_policy_spec.rb | 114 +++++++++++++++++++++ spec/requests/api/settings_spec.rb | 4 +- 11 files changed, 269 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 9af472df74e..1a7be4c9a85 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -85,6 +85,13 @@ describe Admin::ApplicationSettingsController do expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) end + + it 'updates the default_project_creation for string value' do + put :update, params: { application_setting: { default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } } + + expect(response).to redirect_to(admin_application_settings_path) + expect(ApplicationSetting.current.default_project_creation).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 647fce0ecef..22165faa625 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -60,5 +60,11 @@ describe Admin::GroupsController do expect(response).to redirect_to(admin_group_path(group)) expect(group.users).not_to include group_user end + + it 'updates the project_creation_level successfully' do + expect do + post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } } + end.to change { group.reload.project_creation_level }.to(::Gitlab::Access::NO_ONE_PROJECT_ACCESS) + end end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 2b803e7151f..4a28a27da79 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -349,6 +349,13 @@ describe GroupsController do expect(assigns(:group).errors).not_to be_empty expect(assigns(:group).path).not_to eq('new_path') end + + it 'updates the project_creation_level successfully' do + post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } } + + expect(response).to have_gitlab_http_status(302) + expect(group.reload.project_creation_level).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end end describe '#ensure_canonical_path' do diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index dcef8571f41..18a0c2ec731 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -4,6 +4,7 @@ FactoryBot.define do path { name.downcase.gsub(/\s/, '_') } type 'Group' owner nil + project_creation_level ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS after(:create) do |group| if group.owner diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 378e4d5febc..5cef5f0521f 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -77,6 +77,14 @@ describe 'Edit group settings' do end end + describe 'project creation level menu' do + it 'shows the selection menu' do + visit edit_group_path(group) + + expect(page).to have_content('Allowed to create projects') + end + end + describe 'edit group avatar' do before do visit edit_group_path(group) diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 75c72a68069..b54ea929978 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -252,4 +252,23 @@ describe 'New project' do end end end + + context 'Namespace selector' do + context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + before do + group.add_developer(user) + visit new_project_path(namespace_id: group.id) + end + + it 'selects the group namespace' do + page.within('#blank-project-pane') do + namespace = find('#project_namespace_id option[selected]') + + expect(namespace.text).to eq group.full_path + end + end + end + end end diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb index 8d7e2883b2a..c0932539131 100644 --- a/spec/features/projects/user_creates_project_spec.rb +++ b/spec/features/projects/user_creates_project_spec.rb @@ -54,4 +54,31 @@ describe 'User creates a project', :js do expect(project.namespace).to eq(subgroup) end end + + context 'in a group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + before do + group.add_developer(user) + end + + it 'creates a new project' do + visit(new_project_path) + + fill_in :project_name, with: 'a-new-project' + fill_in :project_path, with: 'a-new-project' + + page.find('.js-select-namespace').click + page.find("div[role='option']", text: group.full_path).click + + page.within('#content-body') do + click_button('Create project') + end + + expect(page).to have_content("Project 'a-new-project' was successfully created") + + project = Project.find_by(name: 'a-new-project') + expect(project.namespace).to eq(group) + end + end end diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb index 7ccbdcd1332..601f864ef36 100644 --- a/spec/helpers/namespaces_helper_spec.rb +++ b/spec/helpers/namespaces_helper_spec.rb @@ -1,10 +1,38 @@ require 'spec_helper' -describe NamespacesHelper do +describe NamespacesHelper, :postgresql do let!(:admin) { create(:admin) } - let!(:admin_group) { create(:group, :private) } + let!(:admin_project_creation_level) { nil } + let!(:admin_group) do + create(:group, + :private, + project_creation_level: admin_project_creation_level) + end let!(:user) { create(:user) } - let!(:user_group) { create(:group, :private) } + let!(:user_project_creation_level) { nil } + let!(:user_group) do + create(:group, + :private, + project_creation_level: user_project_creation_level) + end + let!(:subgroup1) do + create(:group, + :private, + parent: admin_group, + project_creation_level: nil) + end + let!(:subgroup2) do + create(:group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) + end + let!(:subgroup3) do + create(:group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end before do admin_group.add_owner(admin) @@ -105,5 +133,43 @@ describe NamespacesHelper do helper.namespaces_options end end + + describe 'include_groups_with_developer_maintainer_access parameter' do + context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set for a project' do + let!(:admin_project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + + it 'returns groups where user is a developer' do + allow(helper).to receive(:current_user).and_return(user) + stub_application_setting(default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + admin_group.add_user(user, GroupMember::DEVELOPER) + + options = helper.namespaces_options_with_developer_maintainer_access + + expect(options).to include(admin_group.name) + expect(options).not_to include(subgroup1.name) + expect(options).to include(subgroup2.name) + expect(options).not_to include(subgroup3.name) + expect(options).to include(user_group.name) + expect(options).to include(user.name) + end + end + + context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set globally' do + it 'return groups where default is not overridden' do + allow(helper).to receive(:current_user).and_return(user) + stub_application_setting(default_project_creation: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) + admin_group.add_user(user, GroupMember::DEVELOPER) + + options = helper.namespaces_options_with_developer_maintainer_access + + expect(options).to include(admin_group.name) + expect(options).to include(subgroup1.name) + expect(options).to include(subgroup2.name) + expect(options).not_to include(subgroup3.name) + expect(options).to include(user_group.name) + expect(options).to include(user.name) + end + end + end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index ad3e3061b9a..e6e7298a043 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -959,4 +959,12 @@ describe Group do end end end + + describe 'project_creation_level' do + it 'outputs the default one if it is nil' do + group = create(:group, project_creation_level: nil) + + expect(group.project_creation_level).to eq(Gitlab::CurrentSettings.default_project_creation) + end + end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index dc98baca6dc..59f3a961d50 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -347,6 +347,120 @@ describe GroupPolicy do end end + context "create_projects" do + context 'when group has no project creation level set' do + let(:group) { create(:group, project_creation_level: nil) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + + context 'when group has project creation level set to no one' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_disallowed(:create_projects) } + end + end + + context 'when group has project creation level set to maintainer only' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + + context 'when group has project creation level set to developers + maintainer' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + end + it_behaves_like 'clusterable policies' do let(:clusterable) { create(:group) } let(:cluster) do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index f33eb5b9e02..f869325e892 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -44,6 +44,7 @@ describe API::Settings, 'Settings' do put api("/application/settings", admin), params: { default_projects_limit: 3, + default_project_creation: 2, password_authentication_enabled_for_web: false, repository_storages: ['custom'], plantuml_enabled: true, @@ -64,12 +65,13 @@ describe API::Settings, 'Settings' do performance_bar_allowed_group_path: group.full_path, instance_statistics_visibility_private: true, diff_max_patch_bytes: 150_000, - default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE, + default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE, local_markdown_version: 3 } expect(response).to have_gitlab_http_status(200) expect(json_response['default_projects_limit']).to eq(3) + expect(json_response['default_project_creation']).to eq(::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) expect(json_response['password_authentication_enabled_for_web']).to be_falsey expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['plantuml_enabled']).to be_truthy -- cgit v1.2.1 From bf47270e9072fccc1bc9575b4d70b9d5c8ac021f Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Thu, 28 Mar 2019 17:37:06 -0400 Subject: Improve diff navigation header - Compare versions header is full width except in the unified diff mode with no tree sidebar - Bar is always full width, but the content within stays centered when unified and no tree sidebar - File header is the same height as the "Compare versions header" - aligns with the design system grid guidelines => 56px - Diff file headers use a button group, switch icon order to open file externally being the last option, all buttons will become icon buttons (icon delivery by @dimitrieh) - If a file header becomes sticky no rounded corner/double border problem is visible anymore --- spec/javascripts/diffs/components/app_spec.js | 18 ++++++++++++++++++ .../diffs/components/compare_versions_spec.js | 20 ++++++++++++++++++++ .../diffs/components/diff_file_header_spec.js | 4 ++-- spec/javascripts/lib/utils/common_utils_spec.js | 6 +++--- 4 files changed, 43 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 8d7c52a2876..3ce69bc3c20 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -57,6 +57,24 @@ describe('diffs/components/app', () => { wrapper.destroy(); }); + it('adds container-limiting classes when showFileTree is false with inline diffs', () => { + createComponent({}, ({ state }) => { + state.diffs.showTreeList = false; + state.diffs.isParallelView = false; + }); + + expect(wrapper.contains('.container-limited.limit-container-width')).toBe(true); + }); + + it('does not add container-limiting classes when showFileTree is false with inline diffs', () => { + createComponent({}, ({ state }) => { + state.diffs.showTreeList = true; + state.diffs.isParallelView = false; + }); + + expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false); + }); + it('displays loading icon on loading', () => { createComponent({}, ({ state }) => { state.diffs.isLoading = true; diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js index e886f962d2f..77f8352047c 100644 --- a/spec/javascripts/diffs/components/compare_versions_spec.js +++ b/spec/javascripts/diffs/components/compare_versions_spec.js @@ -66,6 +66,26 @@ describe('CompareVersions', () => { expect(inlineBtn.innerHTML).toContain('Inline'); expect(parallelBtn.innerHTML).toContain('Side-by-side'); }); + + it('adds container-limiting classes when showFileTree is false with inline diffs', () => { + vm.isLimitedContainer = true; + + vm.$nextTick(() => { + const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width'); + + expect(limitedContainer).not.toBeNull(); + }); + }); + + it('does not add container-limiting classes when showFileTree is false with inline diffs', () => { + vm.isLimitedContainer = false; + + vm.$nextTick(() => { + const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width'); + + expect(limitedContainer).toBeNull(); + }); + }); }); describe('setInlineDiffViewType', () => { diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 6614069f44d..e1170c9762e 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -672,7 +672,7 @@ describe('diff_file_header', () => { vm = mountComponentWithStore(Component, { props, store }); - expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show changes only'); + expect(vm.$el.querySelector('.ic-doc-changes')).not.toBeNull(); }); it('shows expand text', () => { @@ -680,7 +680,7 @@ describe('diff_file_header', () => { vm = mountComponentWithStore(Component, { props, store }); - expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show full file'); + expect(vm.$el.querySelector('.ic-doc-expand')).not.toBeNull(); }); it('renders loading icon', () => { diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 2084c36e484..da012e1d5f7 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -2,7 +2,7 @@ import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; import MockAdapter from 'axios-mock-adapter'; import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data'; -import BreakpointInstance from '~/breakpoints'; +import breakpointInstance from '~/breakpoints'; const PIXEL_TOLERANCE = 0.2; @@ -383,7 +383,7 @@ describe('common_utils', () => { describe('contentTop', () => { it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => { - spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('sm'); + spyOn(breakpointInstance, 'isDesktop').and.returnValue(false); setFixtures(`
      @@ -398,7 +398,7 @@ describe('common_utils', () => { }); it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => { - spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('lg'); + spyOn(breakpointInstance, 'isDesktop').and.returnValue(true); setFixtures(`
      -- cgit v1.2.1 From da0c9b005648cb683801ea8765f5f5f9fdb92c0c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 5 Apr 2019 08:17:30 -0700 Subject: Fix real-time updates for projects that contain a reserved word Projects that included reserved words (e.g. test-create) would fail to generate a proper ETag key because of the name. To fix this, we add forward slashes to match the exact name so that /test-create doesn't get matched, but /create does. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/60113 --- spec/lib/gitlab/etag_caching/router_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb index f69cb502ca6..a7cb0bb2a87 100644 --- a/spec/lib/gitlab/etag_caching/router_spec.rb +++ b/spec/lib/gitlab/etag_caching/router_spec.rb @@ -19,6 +19,24 @@ describe Gitlab::EtagCaching::Router do expect(result.name).to eq 'issue_title' end + it 'matches with a project name that includes a suffix of create' do + result = described_class.match( + '/group/test-create/issues/123/realtime_changes' + ) + + expect(result).to be_present + expect(result.name).to eq 'issue_title' + end + + it 'matches with a project name that includes a prefix of create' do + result = described_class.match( + '/group/create-test/issues/123/realtime_changes' + ) + + expect(result).to be_present + expect(result.name).to eq 'issue_title' + end + it 'matches project pipelines endpoint' do result = described_class.match( '/my-group/my-project/pipelines.json' -- cgit v1.2.1 From c56970e3f72192bf02a0cae535b45357981a848a Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Fri, 5 Apr 2019 19:17:13 +0300 Subject: Remove duplicates from issue related merge requests Remove duplicates returned by Issues#related_merge_requests API that relies on ReferencedMergeRequestsService which returns 2 arrays one of related MRs and one of related MRs that close the issue(i.e. a subset of first one). We only need related MRs in this case so just pick the first array. --- spec/requests/api/issues_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index a5434d3ea80..86484ce62f8 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -2189,6 +2189,18 @@ describe API::Issues do expect_paginated_array_response(related_mr.id) end + context 'merge request closes an issue' do + let!(:closing_issue_mr_rel) do + create(:merge_requests_closing_issues, issue: issue, merge_request: related_mr) + end + + it 'returns closing MR only once' do + get_related_merge_requests(project.id, issue.iid, user) + + expect_paginated_array_response([related_mr.id]) + end + end + context 'no merge request mentioned a issue' do it 'returns empty array' do get_related_merge_requests(project.id, closed_issue.iid, user) -- cgit v1.2.1 From 7650677d3d832f9d65c8d38a2485ca60b97731c4 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Fri, 5 Apr 2019 02:15:56 +0200 Subject: Rewrite related MRs widget with Vue This MR rewrites existing Related Merge Requests widget with Vue with reusing shared Related Issues components --- .../internal_references_spec.rb | 2 +- spec/javascripts/fixtures/issues.rb | 58 +++++++++++ .../components/related_merge_requests_spec.js | 89 +++++++++++++++++ .../related_merge_requests/store/actions_spec.js | 110 +++++++++++++++++++++ .../related_merge_requests/store/mutations_spec.js | 49 +++++++++ 5 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js create mode 100644 spec/javascripts/related_merge_requests/store/actions_spec.js create mode 100644 spec/javascripts/related_merge_requests/store/mutations_spec.js (limited to 'spec') diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb index 23385ba65fc..870e92b8de8 100644 --- a/spec/features/issuables/markdown_references/internal_references_spec.rb +++ b/spec/features/issuables/markdown_references/internal_references_spec.rb @@ -70,7 +70,7 @@ describe "Internal references", :js do page.within("#merge-requests ul") do expect(page).to have_content(private_project_merge_request.title) - expect(page).to have_css(".merge-request-status") + expect(page).to have_css(".ic-issue-open-m") end expect(page).to have_content("mentioned in merge request #{private_project_merge_request.to_reference(public_project)}") diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 645b3aa788a..0f3f9a10f94 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -65,3 +65,61 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller store_frontend_fixture(response, fixture_file_name) end end + +describe API::Issues, '(JavaScript fixtures)', type: :request do + include ApiHelpers + include JavaScriptFixturesHelpers + + def get_related_merge_requests(project_id, issue_iid, user = nil) + get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user) + end + + def create_referencing_mr(user, project, issue) + attributes = { + author: user, + source_project: project, + target_project: project, + source_branch: "master", + target_branch: "test", + assignee: user, + description: "See #{issue.to_reference}" + } + create(:merge_request, attributes).tap do |merge_request| + create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true)) + end + end + + it 'issues/related_merge_requests.json' do |example| + user = create(:user) + project = create(:project, :public, creator_id: user.id, namespace: user.namespace) + issue_title = 'foo' + issue_description = 'closed' + milestone = create(:milestone, title: '1.0.0', project: project) + issue = create :issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + + project.add_reporter(user) + create_referencing_mr(user, project, issue) + + create(:merge_request, + :simple, + author: user, + source_project: project, + target_project: project, + description: "Some description") + project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace) + create_referencing_mr(user, project2, issue).update!(head_pipeline: create(:ci_pipeline)) + + get_related_merge_requests(project.id, issue.iid, user) + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end diff --git a/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js new file mode 100644 index 00000000000..29760f79c3c --- /dev/null +++ b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js @@ -0,0 +1,89 @@ +import { mount, createLocalVue } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; +import RelatedMergeRequests from '~/related_merge_requests/components/related_merge_requests.vue'; +import createStore from '~/related_merge_requests/store/index'; + +const FIXTURE_PATH = 'issues/related_merge_requests.json'; +const API_ENDPOINT = '/api/v4/projects/2/issues/33/related_merge_requests'; +const localVue = createLocalVue(); + +describe('RelatedMergeRequests', () => { + let wrapper; + let mock; + let mockData; + + beforeEach(done => { + loadFixtures(FIXTURE_PATH); + mockData = getJSONFixture(FIXTURE_PATH); + mock = new MockAdapter(axios); + mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 }); + + wrapper = mount(RelatedMergeRequests, { + localVue, + sync: false, + store: createStore(), + propsData: { + endpoint: API_ENDPOINT, + projectNamespace: 'gitlab-org', + projectPath: 'gitlab-ce', + }, + }); + + setTimeout(done); + }); + + afterEach(() => { + wrapper.destroy(); + mock.restore(); + }); + + describe('methods', () => { + describe('getAssignees', () => { + const assignees = [{ name: 'foo' }, { name: 'bar' }]; + + describe('when there is assignees array', () => { + it('should return assignees array', () => { + const mr = { assignees }; + + expect(wrapper.vm.getAssignees(mr)).toEqual(assignees); + }); + }); + + it('should return an array with single assingee', () => { + const mr = { assignee: assignees[0] }; + + expect(wrapper.vm.getAssignees(mr)).toEqual([assignees[0]]); + }); + + it('should return empty array when assignee is not set', () => { + expect(wrapper.vm.getAssignees({})).toEqual([]); + expect(wrapper.vm.getAssignees({ assignee: null })).toEqual([]); + }); + }); + }); + + describe('template', () => { + it('should render related merge request items', () => { + expect(wrapper.find('.js-items-count').text()).toEqual('2'); + expect(wrapper.findAll(RelatedIssuableItem).length).toEqual(2); + + const props = wrapper + .findAll(RelatedIssuableItem) + .at(1) + .props(); + const data = mockData[1]; + + expect(props.idKey).toEqual(data.id); + expect(props.pathIdSeparator).toEqual('!'); + expect(props.pipelineStatus).toBe(data.head_pipeline.detailed_status); + expect(props.assignees).toEqual([data.assignee]); + expect(props.isMergeRequest).toBe(true); + expect(props.confidential).toEqual(false); + expect(props.title).toEqual(data.title); + expect(props.state).toEqual(data.state); + expect(props.createdAt).toEqual(data.created_at); + }); + }); +}); diff --git a/spec/javascripts/related_merge_requests/store/actions_spec.js b/spec/javascripts/related_merge_requests/store/actions_spec.js new file mode 100644 index 00000000000..65e436fbb17 --- /dev/null +++ b/spec/javascripts/related_merge_requests/store/actions_spec.js @@ -0,0 +1,110 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import * as types from '~/related_merge_requests/store/mutation_types'; +import actionsModule, * as actions from '~/related_merge_requests/store/actions'; +import testAction from 'spec/helpers/vuex_action_helper'; + +describe('RelatedMergeRequest store actions', () => { + let state; + let flashSpy; + let mock; + + beforeEach(() => { + state = { + apiEndpoint: '/api/related_merge_requests', + }; + flashSpy = spyOnDependency(actionsModule, 'createFlash'); + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('setInitialState', () => { + it('commits types.SET_INITIAL_STATE with given props', done => { + const props = { a: 1, b: 2 }; + + testAction( + actions.setInitialState, + props, + {}, + [{ type: types.SET_INITIAL_STATE, payload: props }], + [], + done, + ); + }); + }); + + describe('requestData', () => { + it('commits types.REQUEST_DATA', done => { + testAction(actions.requestData, null, {}, [{ type: types.REQUEST_DATA }], [], done); + }); + }); + + describe('receiveDataSuccess', () => { + it('commits types.RECEIVE_DATA_SUCCESS with data', done => { + const data = { a: 1, b: 2 }; + + testAction( + actions.receiveDataSuccess, + data, + {}, + [{ type: types.RECEIVE_DATA_SUCCESS, payload: data }], + [], + done, + ); + }); + }); + + describe('receiveDataError', () => { + it('commits types.RECEIVE_DATA_ERROR', done => { + testAction( + actions.receiveDataError, + null, + {}, + [{ type: types.RECEIVE_DATA_ERROR }], + [], + done, + ); + }); + }); + + describe('fetchMergeRequests', () => { + describe('for a successful request', () => { + it('should dispatch success action', done => { + const data = { a: 1 }; + mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(200, data, { 'x-total': 2 }); + + testAction( + actions.fetchMergeRequests, + null, + state, + [], + [{ type: 'requestData' }, { type: 'receiveDataSuccess', payload: { data, total: 2 } }], + done, + ); + }); + }); + + describe('for a failing request', () => { + it('should dispatch error action', done => { + mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(400); + + testAction( + actions.fetchMergeRequests, + null, + state, + [], + [{ type: 'requestData' }, { type: 'receiveDataError' }], + () => { + expect(flashSpy).toHaveBeenCalledTimes(1); + expect(flashSpy).toHaveBeenCalledWith(jasmine.stringMatching('Something went wrong')); + + done(); + }, + ); + }); + }); + }); +}); diff --git a/spec/javascripts/related_merge_requests/store/mutations_spec.js b/spec/javascripts/related_merge_requests/store/mutations_spec.js new file mode 100644 index 00000000000..21b6e26376b --- /dev/null +++ b/spec/javascripts/related_merge_requests/store/mutations_spec.js @@ -0,0 +1,49 @@ +import mutations from '~/related_merge_requests/store/mutations'; +import * as types from '~/related_merge_requests/store/mutation_types'; + +describe('RelatedMergeRequests Store Mutations', () => { + describe('SET_INITIAL_STATE', () => { + it('should set initial state according to given data', () => { + const apiEndpoint = '/api'; + const state = {}; + + mutations[types.SET_INITIAL_STATE](state, { apiEndpoint }); + + expect(state.apiEndpoint).toEqual(apiEndpoint); + }); + }); + + describe('REQUEST_DATA', () => { + it('should set loading flag', () => { + const state = {}; + + mutations[types.REQUEST_DATA](state); + + expect(state.isFetchingMergeRequests).toEqual(true); + }); + }); + + describe('RECEIVE_DATA_SUCCESS', () => { + it('should set loading flag and data', () => { + const state = {}; + const mrs = [1, 2, 3]; + + mutations[types.RECEIVE_DATA_SUCCESS](state, { data: mrs, total: mrs.length }); + + expect(state.isFetchingMergeRequests).toEqual(false); + expect(state.mergeRequests).toEqual(mrs); + expect(state.totalCount).toEqual(mrs.length); + }); + }); + + describe('RECEIVE_DATA_ERROR', () => { + it('should set loading and error flags', () => { + const state = {}; + + mutations[types.RECEIVE_DATA_ERROR](state); + + expect(state.isFetchingMergeRequests).toEqual(false); + expect(state.hasErrorFetchingMergeRequests).toEqual(true); + }); + }); +}); -- cgit v1.2.1 From 5b64beca82b17d37e5ab2206175c06242ae5ac27 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 13:26:32 +0300 Subject: Extract duplicate quick action spec to a shared example --- .../issues/user_uses_quick_actions_spec.rb | 37 +--------------------- .../duplicate_quick_action_shared_examples.rb | 34 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 36 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 6a8b5e76cda..9938a4e781c 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -58,6 +58,7 @@ describe 'Issues > User uses quick actions', :js do it_behaves_like 'confidential quick action' it_behaves_like 'remove_due_date quick action' + it_behaves_like 'duplicate quick action' describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } @@ -87,42 +88,6 @@ describe 'Issues > User uses quick actions', :js do end end - describe 'mark issue as duplicate' do - let(:issue) { create(:issue, project: project) } - let(:original_issue) { create(:issue, project: project) } - - context 'when the current user can update issues' do - it 'does not create a note, and marks the issue as a duplicate' do - add_note("/duplicate ##{original_issue.to_reference}") - - expect(page).not_to have_content "/duplicate #{original_issue.to_reference}" - expect(page).to have_content 'Commands applied' - expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" - - expect(issue.reload).to be_closed - end - end - - context 'when the current user cannot update the issue' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it 'does not create a note, and does not mark the issue as a duplicate' do - add_note("/duplicate ##{original_issue.to_reference}") - - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" - - expect(issue.reload).to be_open - end - end - end - describe 'move the issue to another project' do let(:issue) { create(:issue, project: project) } diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb index 24576fe0021..633c7135fbc 100644 --- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb @@ -1,4 +1,38 @@ # frozen_string_literal: true shared_examples 'duplicate quick action' do + context 'mark issue as duplicate' do + let(:original_issue) { create(:issue, project: project) } + + context 'when the current user can update issues' do + it 'does not create a note, and marks the issue as a duplicate' do + add_note("/duplicate ##{original_issue.to_reference}") + + expect(page).not_to have_content "/duplicate #{original_issue.to_reference}" + expect(page).to have_content 'Commands applied' + expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" + + expect(issue.reload).to be_closed + end + end + + context 'when the current user cannot update the issue' do + let(:guest) { create(:user) } + before do + project.add_guest(guest) + gitlab_sign_out + sign_in(guest) + visit project_issue_path(project, issue) + end + + it 'does not create a note, and does not mark the issue as a duplicate' do + add_note("/duplicate ##{original_issue.to_reference}") + + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" + + expect(issue.reload).to be_open + end + end + end end -- cgit v1.2.1 From 42fe7e91b3d306596a92b9103ccbdf900493a50a Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 14:56:08 +0300 Subject: Extract wip quick action to a shared example --- .../merge_request/user_uses_quick_actions_spec.rb | 48 +--------------------- .../wip_quick_action_shared_examples.rb | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 47 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index 5e466fb41d0..8d308729f62 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -58,52 +58,6 @@ describe 'Merge request > User uses quick actions', :js do it_behaves_like 'merge quick action' it_behaves_like 'target_branch quick action' - - describe 'toggling the WIP prefix in the title from note' do - context 'when the current user can toggle the WIP prefix' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - wait_for_requests - end - - it 'adds the WIP: prefix to the title' do - add_note("/wip") - - expect(page).not_to have_content '/wip' - expect(page).to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq true - end - - it 'removes the WIP: prefix from the title' do - merge_request.title = merge_request.wip_title - merge_request.save - add_note("/wip") - - expect(page).not_to have_content '/wip' - expect(page).to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq false - end - end - - context 'when the current user cannot toggle the WIP prefix' do - before do - project.add_guest(guest) - sign_in(guest) - visit project_merge_request_path(project, merge_request) - end - - it 'does not change the WIP prefix' do - add_note("/wip") - - expect(page).not_to have_content '/wip' - expect(page).not_to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq false - end - end - end + it_behaves_like 'wip quick action' end end diff --git a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb index 6abb12b41b2..60d6e53e74f 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb @@ -1,4 +1,47 @@ # frozen_string_literal: true shared_examples 'wip quick action' do + context 'when the current user can toggle the WIP prefix' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + wait_for_requests + end + + it 'adds the WIP: prefix to the title' do + add_note('/wip') + + expect(page).not_to have_content '/wip' + expect(page).to have_content 'Commands applied' + + expect(merge_request.reload.work_in_progress?).to eq true + end + + it 'removes the WIP: prefix from the title' do + merge_request.update!(title: merge_request.wip_title) + add_note('/wip') + + expect(page).not_to have_content '/wip' + expect(page).to have_content 'Commands applied' + + expect(merge_request.reload.work_in_progress?).to eq false + end + end + + context 'when the current user cannot toggle the WIP prefix' do + before do + project.add_guest(guest) + sign_in(guest) + visit project_merge_request_path(project, merge_request) + end + + it 'does not change the WIP prefix' do + add_note('/wip') + + expect(page).not_to have_content '/wip' + expect(page).not_to have_content 'Commands applied' + + expect(merge_request.reload.work_in_progress?).to eq false + end + end end -- cgit v1.2.1 From 135b78bb258cbe1bee2a93ec30a51b9dbd73bb02 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Fri, 5 Apr 2019 20:22:50 +0000 Subject: Revert "Merge branch 'bump_kubernetes_1_11_9' into 'master'" This reverts merge request !26991 --- spec/lib/gitlab/kubernetes/helm/pod_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index 10876709f36..06c8d127951 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 'generates the appropriate specifications for the container' do container = subject.generate.spec.containers.first expect(container.name).to eq('helm') - expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.13.1-kube-1.11.9') + expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7') 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"]) -- cgit v1.2.1 From 8906de37202d8631e6a4bca9a68d09b181502b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Alc=C3=A1ntara?= Date: Fri, 5 Apr 2019 20:35:23 +0000 Subject: Do not display ingress IP help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit if there isn’t an ingress IP assigned for the cluster yet --- spec/javascripts/clusters/clusters_bundle_spec.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 0d3dcc29f22..0a98df45b5d 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -300,9 +300,13 @@ describe('Clusters', () => { describe('toggleIngressDomainHelpText', () => { const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS; + let ingressPreviousState; + let ingressNewState; - const ingressPreviousState = { status: INSTALLABLE }; - const ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + beforeEach(() => { + ingressPreviousState = { status: INSTALLABLE }; + ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + }); describe(`when ingress application new status is ${INSTALLED}`, () => { beforeEach(() => { @@ -333,7 +337,7 @@ describe('Clusters', () => { }); describe('when ingress application new status and old status are the same', () => { - it('does not modify custom domain help text', () => { + it('does not display custom domain help text', () => { ingressPreviousState.status = INSTALLED; ingressNewState.status = ingressPreviousState.status; @@ -342,5 +346,15 @@ describe('Clusters', () => { expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); }); }); + + describe(`when ingress new status is ${INSTALLED} and there isn’t an ip assigned`, () => { + it('does not display custom domain help text', () => { + ingressNewState.externalIp = null; + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); }); }); -- cgit v1.2.1 From f44737e453a35ef7361cd90af2b5a5ae09473992 Mon Sep 17 00:00:00 2001 From: Sarah Yasonik Date: Fri, 5 Apr 2019 20:41:31 +0000 Subject: Support multiple queries per chart on metrics dash Adding support for metrics alerts disabled multiple query support. To avoid a data model refactor, this enables the visual of multiple queries per chart on the front end, combining queries based on metric group, title, and y-axis label. This also adds support for adding and editing alerts based on the query selected rather than the single metric associated with the chart. --- spec/javascripts/monitoring/charts/area_spec.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index 549a7935c0f..4ff519ae0e7 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -65,7 +65,7 @@ describe('Area component', () => { expect(props.data).toBe(areaChart.vm.chartData); expect(props.option).toBe(areaChart.vm.chartOptions); expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText); - expect(props.thresholds).toBe(areaChart.props('alertData')); + expect(props.thresholds).toBe(areaChart.vm.thresholds); }); it('recieves a tooltip title', () => { @@ -105,12 +105,13 @@ describe('Area component', () => { seriesName: areaChart.vm.chartData[0].name, componentSubType: type, value: [mockDate, 5.55555], + seriesIndex: 0, }, ], value: mockDate, }); - describe('series is of line type', () => { + describe('when series is of line type', () => { beforeEach(() => { areaChart.vm.formatTooltipText(generateSeriesData('line')); }); @@ -131,7 +132,7 @@ describe('Area component', () => { }); }); - describe('series is of scatter type', () => { + describe('when series is of scatter type', () => { beforeEach(() => { areaChart.vm.formatTooltipText(generateSeriesData('scatter')); }); -- cgit v1.2.1 From 8cf0af88e664b6e3da7d95180c48241591782419 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Fri, 5 Apr 2019 21:59:57 +0000 Subject: Wrap long chart tooltip series label names Resolves an issue where long series label names overflow the popover chart tooltip container --- spec/javascripts/monitoring/charts/area_spec.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index 4ff519ae0e7..41a6c04efb9 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlAreaChart } from '@gitlab/ui/dist/charts'; +import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; import Area from '~/monitoring/components/charts/area.vue'; import MonitoringStore from '~/monitoring/stores/monitoring_store'; @@ -121,13 +121,15 @@ describe('Area component', () => { }); it('formats tooltip content', () => { - expect(areaChart.vm.tooltip.content).toEqual([{ name: 'Core Usage', value: '5.556' }]); + const name = 'Core Usage'; + const value = '5.556'; + const seriesLabel = areaChart.find(GlChartSeriesLabel); + + expect(seriesLabel.vm.color).toBe(''); + expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); + expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]); expect( - shallowWrapperContainsSlotText( - areaChart.find(GlAreaChart), - 'tooltipContent', - 'Core Usage 5.556', - ), + shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value), ).toBe(true); }); }); -- cgit v1.2.1 From 3ec625275875608eb91d60fa6644afa48f23abfd Mon Sep 17 00:00:00 2001 From: Jean Date: Fri, 5 Apr 2019 22:46:48 +0000 Subject: Changes button label to Run Pipeline Button at projects/pipelines/new was 'Create pipeline' and was changed to 'Run Pipeline' --- spec/features/projects/pipelines/pipelines_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index cb14db7665d..de780f13681 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -685,7 +685,7 @@ describe 'Pipelines', :js do end it 'creates a new pipeline' do - expect { click_on 'Create pipeline' } + expect { click_on 'Run Pipeline' } .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last).to be_web @@ -698,7 +698,7 @@ describe 'Pipelines', :js do fill_in "Input variable value", with: "value" end - expect { click_on 'Create pipeline' } + expect { click_on 'Run Pipeline' } .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last.variables.map { |var| var.slice(:key, :secret_value) }) @@ -709,7 +709,7 @@ describe 'Pipelines', :js do context 'without gitlab-ci.yml' do before do - click_on 'Create pipeline' + click_on 'Run Pipeline' end it { expect(page).to have_content('Missing .gitlab-ci.yml file') } @@ -722,14 +722,14 @@ describe 'Pipelines', :js do click_link 'master' end - expect { click_on 'Create pipeline' } + expect { click_on 'Run Pipeline' } .to change { Ci::Pipeline.count }.by(1) end end end end - describe 'Create pipelines' do + describe 'Run Pipelines' do let(:project) { create(:project, :repository) } before do @@ -740,7 +740,7 @@ describe 'Pipelines', :js do it 'has field to add a new pipeline' do expect(page).to have_selector('.js-branch-select') expect(find('.js-branch-select')).to have_content project.default_branch - expect(page).to have_content('Create for') + expect(page).to have_content('Run for') end end -- cgit v1.2.1 From a8a4518099cceec5826bb9ea5d9e3198c5a10698 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Thu, 28 Feb 2019 19:15:30 +0800 Subject: Clean up `noteable_id` for notes on commits This was incorrectly set by a bug in: https://gitlab.com/gitlab-org/gitlab-ce/issues/54924 Also adds a `batch_size` option to `update_column_in_batches` --- ...ean_up_noteable_id_for_notes_on_commits_spec.rb | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb (limited to 'spec') diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb new file mode 100644 index 00000000000..572b7dfd0c8 --- /dev/null +++ b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190313092516_clean_up_noteable_id_for_notes_on_commits.rb') + +describe CleanUpNoteableIdForNotesOnCommits, :migration do + let(:notes) { table(:notes) } + + before do + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + + notes.create!(noteable_type: 'Issue', noteable_id: 1, note: 'Test') + notes.create!(noteable_type: 'MergeRequest', noteable_id: 1, note: 'Test') + notes.create!(noteable_type: 'Snippet', noteable_id: 1, note: 'Test') + end + + it 'clears noteable_id for notes on commits' do + expect { migrate! }.to change { dirty_notes_on_commits.count }.from(3).to(0) + end + + it 'does not clear noteable_id for other notes' do + expect { migrate! }.not_to change { other_notes.count } + end + + def dirty_notes_on_commits + notes.where(noteable_type: 'Commit').where('noteable_id IS NOT NULL') + end + + def other_notes + notes.where("noteable_type != 'Commit' AND noteable_id IS NOT NULL") + end +end -- cgit v1.2.1 From b77fe7db3e885edca14c862f362e2bbd43f0e498 Mon Sep 17 00:00:00 2001 From: Chris Baumbauer Date: Sat, 6 Apr 2019 02:02:39 +0000 Subject: Add Knative metrics to Prometheus --- .../serverless/functions_controller_spec.rb | 9 ++ .../features/projects/serverless/functions_spec.rb | 2 +- .../projects/serverless/functions_finder_spec.rb | 32 +++++- .../javascripts/serverless/components/area_spec.js | 121 +++++++++++++++++++++ .../serverless/components/environment_row_spec.js | 41 +++---- .../serverless/components/function_details_spec.js | 113 +++++++++++++++++++ .../serverless/components/function_row_spec.js | 13 +-- .../serverless/components/functions_spec.js | 113 ++++++++++++------- .../components/missing_prometheus_spec.js | 37 +++++++ .../serverless/components/pod_box_spec.js | 22 ++++ spec/javascripts/serverless/components/url_spec.js | 20 ++-- spec/javascripts/serverless/mock_data.js | 57 ++++++++++ spec/javascripts/serverless/store/actions_spec.js | 88 +++++++++++++++ spec/javascripts/serverless/store/getters_spec.js | 43 ++++++++ .../javascripts/serverless/store/mutations_spec.js | 86 +++++++++++++++ .../serverless/stores/serverless_store_spec.js | 36 ------ spec/javascripts/serverless/utils.js | 20 ++++ .../queries/knative_invocation_query_spec.rb | 26 +++++ spec/models/serverless/function_spec.rb | 21 ++++ spec/support/helpers/prometheus_helpers.rb | 4 + 20 files changed, 775 insertions(+), 129 deletions(-) create mode 100644 spec/javascripts/serverless/components/area_spec.js create mode 100644 spec/javascripts/serverless/components/function_details_spec.js create mode 100644 spec/javascripts/serverless/components/missing_prometheus_spec.js create mode 100644 spec/javascripts/serverless/components/pod_box_spec.js create mode 100644 spec/javascripts/serverless/store/actions_spec.js create mode 100644 spec/javascripts/serverless/store/getters_spec.js create mode 100644 spec/javascripts/serverless/store/mutations_spec.js delete mode 100644 spec/javascripts/serverless/stores/serverless_store_spec.js create mode 100644 spec/javascripts/serverless/utils.js create mode 100644 spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb create mode 100644 spec/models/serverless/function_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 276cf340962..782f5f272d9 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -76,6 +76,15 @@ describe Projects::Serverless::FunctionsController do end end + describe 'GET #metrics' do + context 'invalid data' do + it 'has a bad function name' do + get :metrics, params: params({ format: :json, environment_id: "*", id: "foo" }) + expect(response).to have_gitlab_http_status(204) + end + end + end + describe 'GET #index with data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb index aa71669de98..e14934b1672 100644 --- a/spec/features/projects/serverless/functions_spec.rb +++ b/spec/features/projects/serverless/functions_spec.rb @@ -50,7 +50,7 @@ describe 'Functions', :js do end it 'sees an empty listing of serverless functions' do - expect(page).to have_selector('.gl-responsive-table-row') + expect(page).to have_selector('.empty-state') end end end diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb index 35279906854..3ad38207da4 100644 --- a/spec/finders/projects/serverless/functions_finder_spec.rb +++ b/spec/finders/projects/serverless/functions_finder_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Projects::Serverless::FunctionsFinder do include KubernetesHelpers + include PrometheusHelpers include ReactiveCachingHelpers let(:user) { create(:user) } @@ -24,12 +25,12 @@ describe Projects::Serverless::FunctionsFinder do describe 'retrieve data from knative' do it 'does not have knative installed' do - expect(described_class.new(project.clusters).execute).to be_empty + expect(described_class.new(project).execute).to be_empty end context 'has knative installed' do let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } - let(:finder) { described_class.new(project.clusters) } + let(:finder) { described_class.new(project) } it 'there are no functions' do expect(finder.execute).to be_empty @@ -58,13 +59,36 @@ describe Projects::Serverless::FunctionsFinder do expect(result).not_to be_empty expect(result["metadata"]["name"]).to be_eql(cluster.project.name) end + + it 'has metrics', :use_clean_rails_memory_store_caching do + end + end + + context 'has prometheus' do + let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } + let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } + let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + let(:finder) { described_class.new(project) } + + before do + allow(finder).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:query).and_return(prometheus_empty_body('matrix')) + end + + it 'is available' do + expect(finder.has_prometheus?("*")).to be true + end + + it 'has query data' do + expect(finder.invocation_metrics("*", cluster.project.name)).not_to be_nil + end end end describe 'verify if knative is installed' do context 'knative is not installed' do it 'does not have knative installed' do - expect(described_class.new(project.clusters).installed?).to be false + expect(described_class.new(project).installed?).to be false end end @@ -72,7 +96,7 @@ describe Projects::Serverless::FunctionsFinder do let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } it 'does have knative installed' do - expect(described_class.new(project.clusters).installed?).to be true + expect(described_class.new(project).installed?).to be true end end end diff --git a/spec/javascripts/serverless/components/area_spec.js b/spec/javascripts/serverless/components/area_spec.js new file mode 100644 index 00000000000..2be6ac3d268 --- /dev/null +++ b/spec/javascripts/serverless/components/area_spec.js @@ -0,0 +1,121 @@ +import { shallowMount } from '@vue/test-utils'; +import Area from '~/serverless/components/area.vue'; +import { mockNormalizedMetrics } from '../mock_data'; + +describe('Area component', () => { + const mockWidgets = 'mockWidgets'; + const mockGraphData = mockNormalizedMetrics; + let areaChart; + + beforeEach(() => { + areaChart = shallowMount(Area, { + propsData: { + graphData: mockGraphData, + containerWidth: 0, + }, + slots: { + default: mockWidgets, + }, + }); + }); + + afterEach(() => { + areaChart.destroy(); + }); + + it('renders chart title', () => { + expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); + }); + + it('contains graph widgets from slot', () => { + expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); + }); + + describe('methods', () => { + describe('formatTooltipText', () => { + const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time; + const generateSeriesData = type => ({ + seriesData: [ + { + componentSubType: type, + value: [mockDate, 4], + }, + ], + value: mockDate, + }); + + describe('series is of line type', () => { + beforeEach(() => { + areaChart.vm.formatTooltipText(generateSeriesData('line')); + }); + + it('formats tooltip title', () => { + expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM'); + }); + + it('formats tooltip content', () => { + expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4'); + }); + }); + + it('verify default interval value of 1', () => { + expect(areaChart.vm.getInterval).toBe(1); + }); + }); + + describe('onResize', () => { + const mockWidth = 233; + + beforeEach(() => { + spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ + width: mockWidth, + })); + areaChart.vm.onResize(); + }); + + it('sets area chart width', () => { + expect(areaChart.vm.width).toBe(mockWidth); + }); + }); + }); + + describe('computed', () => { + describe('chartData', () => { + it('utilizes all data points', () => { + expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']); + expect(areaChart.vm.chartData.requests.length).toBe(2); + }); + + it('creates valid data', () => { + const data = areaChart.vm.chartData.requests; + + expect( + data.filter( + datum => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number', + ).length, + ).toBe(data.length); + }); + }); + + describe('generateSeries', () => { + it('utilizes correct time data', () => { + expect(areaChart.vm.generateSeries.data).toEqual([ + ['2019-02-28T11:11:38.756Z', 0], + ['2019-02-28T11:12:38.756Z', 0], + ]); + }); + }); + + describe('xAxisLabel', () => { + it('constructs a label for the chart x-axis', () => { + expect(areaChart.vm.xAxisLabel).toBe('invocations / minute'); + }); + }); + + describe('yAxisLabel', () => { + it('constructs a label for the chart y-axis', () => { + expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)'); + }); + }); + }); +}); diff --git a/spec/javascripts/serverless/components/environment_row_spec.js b/spec/javascripts/serverless/components/environment_row_spec.js index bdf7a714910..932d712dbec 100644 --- a/spec/javascripts/serverless/components/environment_row_spec.js +++ b/spec/javascripts/serverless/components/environment_row_spec.js @@ -1,81 +1,70 @@ -import Vue from 'vue'; - import environmentRowComponent from '~/serverless/components/environment_row.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import ServerlessStore from '~/serverless/stores/serverless_store'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; +import { translate } from '~/serverless/utils'; -const createComponent = (env, envName) => - mountComponent(Vue.extend(environmentRowComponent), { env, envName }); +const createComponent = (localVue, env, envName) => + shallowMount(environmentRowComponent, { localVue, propsData: { env, envName } }).vm; describe('environment row component', () => { describe('default global cluster case', () => { + let localVue; let vm; beforeEach(() => { - const store = new ServerlessStore(false, '/cluster_path', 'help_path'); - store.updateFunctionsFromServer(mockServerlessFunctions); - vm = createComponent(store.state.functions['*'], '*'); + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*'); }); + afterEach(() => vm.$destroy()); + it('has the correct envId', () => { expect(vm.envId).toEqual('env-global'); - vm.$destroy(); }); it('is open by default', () => { expect(vm.isOpenClass).toEqual({ 'is-open': true }); - vm.$destroy(); }); it('generates correct output', () => { - expect(vm.$el.querySelectorAll('li').length).toEqual(2); expect(vm.$el.id).toEqual('env-global'); expect(vm.$el.classList.contains('is-open')).toBe(true); expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); - - vm.$destroy(); }); it('opens and closes correctly', () => { expect(vm.isOpen).toBe(true); vm.toggleOpen(); - Vue.nextTick(() => { - expect(vm.isOpen).toBe(false); - }); - vm.$destroy(); + expect(vm.isOpen).toBe(false); }); }); describe('default named cluster case', () => { let vm; + let localVue; beforeEach(() => { - const store = new ServerlessStore(false, '/cluster_path', 'help_path'); - store.updateFunctionsFromServer(mockServerlessFunctionsDiffEnv); - vm = createComponent(store.state.functions.test, 'test'); + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test'); }); + afterEach(() => vm.$destroy()); + it('has the correct envId', () => { expect(vm.envId).toEqual('env-test'); - vm.$destroy(); }); it('is open by default', () => { expect(vm.isOpenClass).toEqual({ 'is-open': true }); - vm.$destroy(); }); it('generates correct output', () => { - expect(vm.$el.querySelectorAll('li').length).toEqual(1); expect(vm.$el.id).toEqual('env-test'); expect(vm.$el.classList.contains('is-open')).toBe(true); expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); - - vm.$destroy(); }); }); }); diff --git a/spec/javascripts/serverless/components/function_details_spec.js b/spec/javascripts/serverless/components/function_details_spec.js new file mode 100644 index 00000000000..a29d4a296ef --- /dev/null +++ b/spec/javascripts/serverless/components/function_details_spec.js @@ -0,0 +1,113 @@ +import Vuex from 'vuex'; + +import functionDetailsComponent from '~/serverless/components/function_details.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; + +describe('functionDetailsComponent', () => { + let localVue; + let component; + let store; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); + + store = createStore(); + }); + + afterEach(() => { + component.vm.$destroy(); + }); + + describe('Verify base functionality', () => { + const serviceStub = { + name: 'test', + description: 'a description', + environment: '*', + url: 'http://service.com/test', + namespace: 'test-ns', + podcount: 0, + metricsUrl: '/metrics', + }; + + it('has a name, description, URL, and no pods loaded', () => { + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(), + ).toContain('test'); + + expect( + component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(), + ).toContain('a description'); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain( + 'No pods loaded at this time.', + ); + }); + + it('has a pods loaded', () => { + serviceStub.podcount = 1; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); + }); + + it('has multiple pods loaded', () => { + serviceStub.podcount = 3; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); + }); + + it('can support a missing description', () => { + serviceStub.description = null; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-description').querySelector('div') + .innerHTML.length, + ).toEqual(0); + }); + }); +}); diff --git a/spec/javascripts/serverless/components/function_row_spec.js b/spec/javascripts/serverless/components/function_row_spec.js index 6933a8f6c87..3987e1753bd 100644 --- a/spec/javascripts/serverless/components/function_row_spec.js +++ b/spec/javascripts/serverless/components/function_row_spec.js @@ -1,11 +1,9 @@ -import Vue from 'vue'; - import functionRowComponent from '~/serverless/components/function_row.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; import { mockServerlessFunction } from '../mock_data'; -const createComponent = func => mountComponent(Vue.extend(functionRowComponent), { func }); +const createComponent = func => shallowMount(functionRowComponent, { propsData: { func } }).vm; describe('functionRowComponent', () => { it('Parses the function details correctly', () => { @@ -13,10 +11,7 @@ describe('functionRowComponent', () => { expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); - expect(vm.$el.querySelector('time').getAttribute('data-original-title')).not.toBe(null); - expect(vm.$el.querySelector('div.url-text-field').innerHTML).toEqual( - mockServerlessFunction.url, - ); + expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); vm.$destroy(); }); @@ -25,8 +20,6 @@ describe('functionRowComponent', () => { const vm = createComponent(mockServerlessFunction); expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row - expect(vm.checkClass(vm.$el.querySelector('svg'))).toBe(false); // check a button image - expect(vm.checkClass(vm.$el.querySelector('div.url-text-field'))).toBe(false); // check the url bar vm.$destroy(); }); diff --git a/spec/javascripts/serverless/components/functions_spec.js b/spec/javascripts/serverless/components/functions_spec.js index 85cfe71281f..c32978ea58a 100644 --- a/spec/javascripts/serverless/components/functions_spec.js +++ b/spec/javascripts/serverless/components/functions_spec.js @@ -1,68 +1,101 @@ -import Vue from 'vue'; +import Vuex from 'vuex'; import functionsComponent from '~/serverless/components/functions.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import ServerlessStore from '~/serverless/stores/serverless_store'; - +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; import { mockServerlessFunctions } from '../mock_data'; -const createComponent = ( - functions, - installed = true, - loadingData = true, - hasFunctionData = true, -) => { - const component = Vue.extend(functionsComponent); +describe('functionsComponent', () => { + let component; + let store; + let localVue; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); - return mountComponent(component, { - functions, - installed, - clustersPath: '/testClusterPath', - helpPath: '/helpPath', - loadingData, - hasFunctionData, + store = createStore(); }); -}; -describe('functionsComponent', () => { - it('should render empty state when Knative is not installed', () => { - const vm = createComponent({}, false); + afterEach(() => { + component.vm.$destroy(); + }); - expect(vm.$el.querySelector('div.row').classList.contains('js-empty-state')).toBe(true); - expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual( - 'Getting started with serverless', - ); + it('should render empty state when Knative is not installed', () => { + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: false, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); - vm.$destroy(); + expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); }); it('should render a loading component', () => { - const vm = createComponent({}); + store.dispatch('requestFunctionsLoading'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); - expect(vm.$el.querySelector('.gl-responsive-table-row')).not.toBe(null); - expect(vm.$el.querySelector('div.animation-container')).not.toBe(null); + expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); }); it('should render empty state when there is no function data', () => { - const vm = createComponent({}, true, false, false); + store.dispatch('receiveFunctionsNoDataSuccess'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); expect( - vm.$el.querySelector('.empty-state, .js-empty-state').classList.contains('js-empty-state'), + component.vm.$el + .querySelector('.empty-state, .js-empty-state') + .classList.contains('js-empty-state'), ).toBe(true); - expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual( + expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual( 'No functions available', ); - - vm.$destroy(); }); it('should render the functions list', () => { - const store = new ServerlessStore(false, '/cluster_path', 'help_path'); - store.updateFunctionsFromServer(mockServerlessFunctions); - const vm = createComponent(store.state.functions, true, false); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); - expect(vm.$el.querySelector('div.groups-list-tree-container')).not.toBe(null); - expect(vm.$el.querySelector('#env-global').classList.contains('has-children')).toBe(true); + return component.vm.$nextTick().then(() => { + expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); + }); }); }); diff --git a/spec/javascripts/serverless/components/missing_prometheus_spec.js b/spec/javascripts/serverless/components/missing_prometheus_spec.js new file mode 100644 index 00000000000..77aca03772b --- /dev/null +++ b/spec/javascripts/serverless/components/missing_prometheus_spec.js @@ -0,0 +1,37 @@ +import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = missingData => + shallowMount(missingPrometheusComponent, { + propsData: { + clustersPath: '/clusters', + helpPath: '/help', + missingData, + }, + }).vm; + +describe('missingPrometheusComponent', () => { + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + it('should render missing prometheus message', () => { + vm = createComponent(false); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Function invocation metrics require Prometheus to be installed first.', + ); + + expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); + }); + + it('should render no prometheus data message', () => { + vm = createComponent(true); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Invocation metrics loading or not available at this time.', + ); + }); +}); diff --git a/spec/javascripts/serverless/components/pod_box_spec.js b/spec/javascripts/serverless/components/pod_box_spec.js new file mode 100644 index 00000000000..69ac1a2bb5f --- /dev/null +++ b/spec/javascripts/serverless/components/pod_box_spec.js @@ -0,0 +1,22 @@ +import podBoxComponent from '~/serverless/components/pod_box.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = count => + shallowMount(podBoxComponent, { + propsData: { + count, + }, + }).vm; + +describe('podBoxComponent', () => { + it('should render three boxes', () => { + const count = 3; + const vm = createComponent(count); + const rects = vm.$el.querySelectorAll('rect'); + + expect(rects.length).toEqual(3); + expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40); + + vm.$destroy(); + }); +}); diff --git a/spec/javascripts/serverless/components/url_spec.js b/spec/javascripts/serverless/components/url_spec.js index 21a879a49bb..08c3e4146b1 100644 --- a/spec/javascripts/serverless/components/url_spec.js +++ b/spec/javascripts/serverless/components/url_spec.js @@ -1,15 +1,13 @@ import Vue from 'vue'; - import urlComponent from '~/serverless/components/url.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const createComponent = uri => { - const component = Vue.extend(urlComponent); +import { shallowMount } from '@vue/test-utils'; - return mountComponent(component, { - uri, - }); -}; +const createComponent = uri => + shallowMount(Vue.extend(urlComponent), { + propsData: { + uri, + }, + }).vm; describe('urlComponent', () => { it('should render correctly', () => { @@ -17,9 +15,7 @@ describe('urlComponent', () => { const vm = createComponent(uri); expect(vm.$el.classList.contains('clipboard-group')).toBe(true); - expect(vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text')).toEqual( - uri, - ); + expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); diff --git a/spec/javascripts/serverless/mock_data.js b/spec/javascripts/serverless/mock_data.js index ecd393b174c..a2c18616324 100644 --- a/spec/javascripts/serverless/mock_data.js +++ b/spec/javascripts/serverless/mock_data.js @@ -77,3 +77,60 @@ export const mockMultilineServerlessFunction = { description: 'testfunc1\nA test service line\\nWith additional services', image: 'knative-test-container-buildtemplate', }; + +export const mockMetrics = { + success: true, + last_update: '2019-02-28T19:11:38.926Z', + metrics: { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [[1551352298.756, '0'], [1551352358.756, '0']], + }, + ], + }, + ], + }, +}; + +export const mockNormalizedMetrics = { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [ + { + time: '2019-02-28T11:11:38.756Z', + value: 0, + }, + { + time: '2019-02-28T11:12:38.756Z', + value: 0, + }, + ], + }, + ], + }, + ], +}; diff --git a/spec/javascripts/serverless/store/actions_spec.js b/spec/javascripts/serverless/store/actions_spec.js new file mode 100644 index 00000000000..602798573e9 --- /dev/null +++ b/spec/javascripts/serverless/store/actions_spec.js @@ -0,0 +1,88 @@ +import MockAdapter from 'axios-mock-adapter'; +import statusCodes from '~/lib/utils/http_status'; +import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; +import axios from '~/lib/utils/axios_utils'; +import testAction from '../../helpers/vuex_action_helper'; +import { adjustMetricQuery } from '../utils'; + +describe('ServerlessActions', () => { + describe('fetchFunctions', () => { + it('should successfully fetch functions', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions)); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [ + { type: 'requestFunctionsLoading' }, + { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions }, + ], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully retry', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [{ type: 'requestFunctionsLoading' }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); + + describe('fetchMetrics', () => { + it('should return no prometheus', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: false }, + {}, + [], + [{ type: 'receiveMetricsNoPrometheus' }], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully fetch metrics', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics)); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: true }, + {}, + [], + [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); +}); diff --git a/spec/javascripts/serverless/store/getters_spec.js b/spec/javascripts/serverless/store/getters_spec.js new file mode 100644 index 00000000000..fb549c8f153 --- /dev/null +++ b/spec/javascripts/serverless/store/getters_spec.js @@ -0,0 +1,43 @@ +import serverlessState from '~/serverless/store/state'; +import * as getters from '~/serverless/store/getters'; +import { mockServerlessFunctions } from '../mock_data'; + +describe('Serverless Store Getters', () => { + let state; + + beforeEach(() => { + state = serverlessState; + }); + + describe('hasPrometheusMissingData', () => { + it('should return false if Prometheus is not installed', () => { + state.hasPrometheus = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return false if Prometheus is installed and there is data', () => { + state.hasPrometheusData = true; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return true if Prometheus is installed and there is no data', () => { + state.hasPrometheus = true; + state.hasPrometheusData = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(true); + }); + }); + + describe('getFunctions', () => { + it('should translate the raw function array to group the functions per environment scope', () => { + state.functions = mockServerlessFunctions; + + const funcs = getters.getFunctions(state); + + expect(Object.keys(funcs)).toContain('*'); + expect(funcs['*'].length).toEqual(2); + }); + }); +}); diff --git a/spec/javascripts/serverless/store/mutations_spec.js b/spec/javascripts/serverless/store/mutations_spec.js new file mode 100644 index 00000000000..ca3053e5c38 --- /dev/null +++ b/spec/javascripts/serverless/store/mutations_spec.js @@ -0,0 +1,86 @@ +import mutations from '~/serverless/store/mutations'; +import * as types from '~/serverless/store/mutation_types'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; + +describe('ServerlessMutations', () => { + describe('Functions List Mutations', () => { + it('should ensure loading is true', () => { + const state = {}; + + mutations[types.REQUEST_FUNCTIONS_LOADING](state); + + expect(state.isLoading).toEqual(true); + }); + + it('should set proper state once functions are loaded', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(true); + expect(state.functions).toEqual(mockServerlessFunctions); + }); + + it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + }); + + it('should ensure loading has stopped, and an error is raised', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error'); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + expect(state.error).not.toBe(undefined); + }); + }); + + describe('Function Details Metrics Mutations', () => { + it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(true); + expect(state.graphData).toEqual(mockMetrics); + }); + + it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + expect(state.graphData).toBe(undefined); + }); + + it('should properly indicate an error', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error'); + + expect(state.hasPrometheusData).toEqual(false); + expect(state.error).not.toBe(undefined); + }); + + it('should properly indicate when prometheus is installed', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state); + + expect(state.hasPrometheus).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + }); + }); +}); diff --git a/spec/javascripts/serverless/stores/serverless_store_spec.js b/spec/javascripts/serverless/stores/serverless_store_spec.js deleted file mode 100644 index 72fd903d7d1..00000000000 --- a/spec/javascripts/serverless/stores/serverless_store_spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import ServerlessStore from '~/serverless/stores/serverless_store'; -import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; - -describe('Serverless Functions Store', () => { - let store; - - beforeEach(() => { - store = new ServerlessStore(false, '/cluster_path', 'help_path'); - }); - - describe('#updateFunctionsFromServer', () => { - it('should pass an empty hash object', () => { - store.updateFunctionsFromServer(); - - expect(store.state.functions).toEqual({}); - }); - - it('should group functions to one global environment', () => { - const mockServerlessData = mockServerlessFunctions; - store.updateFunctionsFromServer(mockServerlessData); - - expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*'])); - expect(store.state.functions['*'].length).toEqual(2); - }); - - it('should group functions to multiple environments', () => { - const mockServerlessData = mockServerlessFunctionsDiffEnv; - store.updateFunctionsFromServer(mockServerlessData); - - expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*'])); - expect(store.state.functions['*'].length).toEqual(1); - expect(store.state.functions.test.length).toEqual(1); - expect(store.state.functions.test[0].name).toEqual('testfunc2'); - }); - }); -}); diff --git a/spec/javascripts/serverless/utils.js b/spec/javascripts/serverless/utils.js new file mode 100644 index 00000000000..5ce2e37d493 --- /dev/null +++ b/spec/javascripts/serverless/utils.js @@ -0,0 +1,20 @@ +export const adjustMetricQuery = data => { + const updatedMetric = data.metrics; + + const queries = data.metrics.queries.map(query => ({ + ...query, + result: query.result.map(result => ({ + ...result, + values: result.values.map(([timestamp, value]) => ({ + time: new Date(timestamp * 1000).toISOString(), + value: Number(value), + })), + })), + })); + + updatedMetric.queries = queries; + return updatedMetric; +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb new file mode 100644 index 00000000000..7f6283715f2 --- /dev/null +++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do + include PrometheusHelpers + + let(:project) { create(:project) } + let(:serverless_func) { Serverless::Function.new(project, 'test-name', 'test-ns') } + + let(:client) { double('prometheus_client') } + subject { described_class.new(client) } + + context 'verify queries' do + before do + allow(PrometheusMetric).to receive(:find_by_identifier).and_return(create(:prometheus_metric, query: prometheus_istio_query('test-name', 'test-ns'))) + allow(client).to receive(:query_range) + end + + it 'has the query, but no data' do + results = subject.query(serverless_func.id) + + expect(results.queries[0][:query_range]).to eql('floor(sum(rate(istio_revision_request_count{destination_configuration="test-name", destination_namespace="test-ns"}[1m])*30))') + end + end +end diff --git a/spec/models/serverless/function_spec.rb b/spec/models/serverless/function_spec.rb new file mode 100644 index 00000000000..1854d5f9415 --- /dev/null +++ b/spec/models/serverless/function_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Serverless::Function do + let(:project) { create(:project) } + let(:func) { described_class.new(project, 'test', 'test-ns') } + + it 'has a proper id' do + expect(func.id).to eql("#{project.id}/test/test-ns") + expect(func.name).to eql("test") + expect(func.namespace).to eql("test-ns") + end + + it 'can decode an identifier' do + f = described_class.find_by_id("#{project.id}/testfunc/dummy-ns") + + expect(f.name).to eql("testfunc") + expect(f.namespace).to eql("dummy-ns") + end +end diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index 08d1d7a6059..87f825152cf 100644 --- a/spec/support/helpers/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb @@ -7,6 +7,10 @@ module PrometheusHelpers %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100} end + def prometheus_istio_query(function_name, kube_namespace) + %{floor(sum(rate(istio_revision_request_count{destination_configuration=\"#{function_name}\", destination_namespace=\"#{kube_namespace}\"}[1m])*30))} + end + def prometheus_ping_url(prometheus_query) query = { query: prometheus_query }.to_query -- cgit v1.2.1 From 0d8e9f6ee3a49e00b7831b88486b1cbda83026cb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 6 Apr 2019 05:04:27 -0700 Subject: Expand FindCommit caching to blob and refs This enables FindCommit caching to the following actions: * BlobController#show * RefsController#logs_tree It also improves caching in CommitsController since some duplicate requests were occuring inside the before_action definitions. --- spec/controllers/projects/blob_controller_spec.rb | 2 ++ spec/controllers/projects/refs_controller_spec.rb | 4 ++++ 2 files changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 32949e0e7d6..485e3e21c4d 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -10,6 +10,8 @@ describe Projects::BlobController do context 'with file path' do before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + get(:show, params: { namespace_id: project.namespace, diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index 62f2af947e4..0d0fa5d9f45 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -44,11 +44,15 @@ describe Projects::RefsController do end it 'renders JS' do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + xhr_get(:js) expect(response).to be_success end it 'renders JSON' do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + xhr_get(:json) expect(response).to be_success -- cgit v1.2.1 From bbb17ea1ea619664b362a9c984da45b940c412a2 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Sun, 7 Apr 2019 07:51:36 +0000 Subject: Handle possible HTTP exception for Sentry client Prior this commit exceptions raised during a HTTP request weren't caught by the Sentry client and were passed to the user. In addition the Sentry client tried to catch a non-existent error `Sentry::Client::SentryError`. Now, the Sentry client catches all possible errors coming from a HTTP request. --- spec/lib/sentry/client_spec.rb | 48 ++++++++++++++++++---- .../error_tracking/list_projects_service_spec.rb | 26 +++++++++--- 2 files changed, 59 insertions(+), 15 deletions(-) (limited to 'spec') diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index 3333f8307ae..cb14204b99a 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -61,13 +61,37 @@ describe Sentry::Client do end end + shared_examples 'maps exceptions' do + exceptions = { + HTTParty::Error => 'Error when connecting to Sentry', + Net::OpenTimeout => 'Connection to Sentry timed out', + SocketError => 'Received SocketError when trying to connect to Sentry', + OpenSSL::SSL::SSLError => 'Sentry returned invalid SSL data', + Errno::ECONNREFUSED => 'Connection refused', + StandardError => 'Sentry request failed due to StandardError' + } + + exceptions.each do |exception, message| + context "#{exception}" do + before do + stub_request(:get, sentry_request_url).to_raise(exception) + end + + it do + expect { subject } + .to raise_exception(Sentry::Client::Error, message) + end + end + end + end + describe '#list_issues' do let(:issue_status) { 'unresolved' } let(:limit) { 20 } - let(:sentry_api_response) { issues_sample_response } + let(:sentry_request_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } - let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: sentry_api_response) } + let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } subject { client.list_issues(issue_status: issue_status, limit: limit) } @@ -121,16 +145,14 @@ describe Sentry::Client do # Sentry API returns 404 if there are extra slashes in the URL! context 'extra slashes in URL' do let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' } - let(:client) { described_class.new(sentry_url, token) } - let!(:valid_req_stub) do - stub_sentry_request( - 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ + let(:sentry_request_url) do + 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ 'issues/?limit=20&query=is:unresolved' - ) end it 'removes extra slashes in api url' do + expect(client.url).to eq(sentry_url) expect(Gitlab::HTTP).to receive(:get).with( URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'), anything @@ -138,7 +160,7 @@ describe Sentry::Client do subject - expect(valid_req_stub).to have_been_requested + expect(sentry_api_request).to have_been_requested end end @@ -169,6 +191,8 @@ describe Sentry::Client do expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') end end + + it_behaves_like 'maps exceptions' end describe '#list_projects' do @@ -260,12 +284,18 @@ describe Sentry::Client do expect(valid_req_stub).to have_been_requested end end + + context 'when exception is raised' do + let(:sentry_request_url) { sentry_list_projects_url } + + it_behaves_like 'maps exceptions' + end end private def stub_sentry_request(url, body: {}, status: 200, headers: {}) - WebMock.stub_request(:get, url) + stub_request(:get, url) .to_return( status: status, headers: { 'Content-Type' => 'application/json' }.merge(headers), diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb index a92d3376f7b..730fccc599e 100644 --- a/spec/services/error_tracking/list_projects_service_spec.rb +++ b/spec/services/error_tracking/list_projects_service_spec.rb @@ -51,14 +51,28 @@ describe ErrorTracking::ListProjectsService do end context 'sentry client raises exception' do - before do - expect(error_tracking_setting).to receive(:list_sentry_projects) - .and_raise(Sentry::Client::Error, 'Sentry response status code: 500') + context 'Sentry::Client::Error' do + before do + expect(error_tracking_setting).to receive(:list_sentry_projects) + .and_raise(Sentry::Client::Error, 'Sentry response status code: 500') + end + + it 'returns error response' do + expect(result[:message]).to eq('Sentry response status code: 500') + expect(result[:http_status]).to eq(:bad_request) + end end - it 'returns error response' do - expect(result[:message]).to eq('Sentry response status code: 500') - expect(result[:http_status]).to eq(:bad_request) + context 'Sentry::Client::MissingKeysError' do + before do + expect(error_tracking_setting).to receive(:list_sentry_projects) + .and_raise(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') + end + + it 'returns error response' do + expect(result[:message]).to eq('Sentry API response is missing keys. key not found: "id"') + expect(result[:http_status]).to eq(:internal_server_error) + end end end -- cgit v1.2.1 From 97b8853d617060fe38c861360181ef83321727e2 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 7 Apr 2019 03:26:58 +0100 Subject: Move karma serverless tests to jest for isolation These tests cause master failures because of an overflow in vue-test-utils sync watcher setup. --- spec/frontend/serverless/components/area_spec.js | 121 ++++++++++++++++++ .../serverless/components/environment_row_spec.js | 70 +++++++++++ .../serverless/components/function_details_spec.js | 113 +++++++++++++++++ .../serverless/components/function_row_spec.js | 26 ++++ .../serverless/components/functions_spec.js | 101 +++++++++++++++ .../components/missing_prometheus_spec.js | 37 ++++++ .../frontend/serverless/components/pod_box_spec.js | 22 ++++ spec/frontend/serverless/components/url_spec.js | 24 ++++ spec/frontend/serverless/mock_data.js | 136 +++++++++++++++++++++ spec/frontend/serverless/store/actions_spec.js | 88 +++++++++++++ spec/frontend/serverless/store/getters_spec.js | 43 +++++++ spec/frontend/serverless/store/mutations_spec.js | 86 +++++++++++++ spec/frontend/serverless/utils.js | 20 +++ .../javascripts/serverless/components/area_spec.js | 121 ------------------ .../serverless/components/environment_row_spec.js | 70 ----------- .../serverless/components/function_details_spec.js | 113 ----------------- .../serverless/components/function_row_spec.js | 26 ---- .../serverless/components/functions_spec.js | 101 --------------- .../components/missing_prometheus_spec.js | 37 ------ .../serverless/components/pod_box_spec.js | 22 ---- spec/javascripts/serverless/components/url_spec.js | 24 ---- spec/javascripts/serverless/mock_data.js | 136 --------------------- spec/javascripts/serverless/store/actions_spec.js | 88 ------------- spec/javascripts/serverless/store/getters_spec.js | 43 ------- .../javascripts/serverless/store/mutations_spec.js | 86 ------------- spec/javascripts/serverless/utils.js | 20 --- 26 files changed, 887 insertions(+), 887 deletions(-) create mode 100644 spec/frontend/serverless/components/area_spec.js create mode 100644 spec/frontend/serverless/components/environment_row_spec.js create mode 100644 spec/frontend/serverless/components/function_details_spec.js create mode 100644 spec/frontend/serverless/components/function_row_spec.js create mode 100644 spec/frontend/serverless/components/functions_spec.js create mode 100644 spec/frontend/serverless/components/missing_prometheus_spec.js create mode 100644 spec/frontend/serverless/components/pod_box_spec.js create mode 100644 spec/frontend/serverless/components/url_spec.js create mode 100644 spec/frontend/serverless/mock_data.js create mode 100644 spec/frontend/serverless/store/actions_spec.js create mode 100644 spec/frontend/serverless/store/getters_spec.js create mode 100644 spec/frontend/serverless/store/mutations_spec.js create mode 100644 spec/frontend/serverless/utils.js delete mode 100644 spec/javascripts/serverless/components/area_spec.js delete mode 100644 spec/javascripts/serverless/components/environment_row_spec.js delete mode 100644 spec/javascripts/serverless/components/function_details_spec.js delete mode 100644 spec/javascripts/serverless/components/function_row_spec.js delete mode 100644 spec/javascripts/serverless/components/functions_spec.js delete mode 100644 spec/javascripts/serverless/components/missing_prometheus_spec.js delete mode 100644 spec/javascripts/serverless/components/pod_box_spec.js delete mode 100644 spec/javascripts/serverless/components/url_spec.js delete mode 100644 spec/javascripts/serverless/mock_data.js delete mode 100644 spec/javascripts/serverless/store/actions_spec.js delete mode 100644 spec/javascripts/serverless/store/getters_spec.js delete mode 100644 spec/javascripts/serverless/store/mutations_spec.js delete mode 100644 spec/javascripts/serverless/utils.js (limited to 'spec') diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js new file mode 100644 index 00000000000..2be6ac3d268 --- /dev/null +++ b/spec/frontend/serverless/components/area_spec.js @@ -0,0 +1,121 @@ +import { shallowMount } from '@vue/test-utils'; +import Area from '~/serverless/components/area.vue'; +import { mockNormalizedMetrics } from '../mock_data'; + +describe('Area component', () => { + const mockWidgets = 'mockWidgets'; + const mockGraphData = mockNormalizedMetrics; + let areaChart; + + beforeEach(() => { + areaChart = shallowMount(Area, { + propsData: { + graphData: mockGraphData, + containerWidth: 0, + }, + slots: { + default: mockWidgets, + }, + }); + }); + + afterEach(() => { + areaChart.destroy(); + }); + + it('renders chart title', () => { + expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); + }); + + it('contains graph widgets from slot', () => { + expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); + }); + + describe('methods', () => { + describe('formatTooltipText', () => { + const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time; + const generateSeriesData = type => ({ + seriesData: [ + { + componentSubType: type, + value: [mockDate, 4], + }, + ], + value: mockDate, + }); + + describe('series is of line type', () => { + beforeEach(() => { + areaChart.vm.formatTooltipText(generateSeriesData('line')); + }); + + it('formats tooltip title', () => { + expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM'); + }); + + it('formats tooltip content', () => { + expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4'); + }); + }); + + it('verify default interval value of 1', () => { + expect(areaChart.vm.getInterval).toBe(1); + }); + }); + + describe('onResize', () => { + const mockWidth = 233; + + beforeEach(() => { + spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ + width: mockWidth, + })); + areaChart.vm.onResize(); + }); + + it('sets area chart width', () => { + expect(areaChart.vm.width).toBe(mockWidth); + }); + }); + }); + + describe('computed', () => { + describe('chartData', () => { + it('utilizes all data points', () => { + expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']); + expect(areaChart.vm.chartData.requests.length).toBe(2); + }); + + it('creates valid data', () => { + const data = areaChart.vm.chartData.requests; + + expect( + data.filter( + datum => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number', + ).length, + ).toBe(data.length); + }); + }); + + describe('generateSeries', () => { + it('utilizes correct time data', () => { + expect(areaChart.vm.generateSeries.data).toEqual([ + ['2019-02-28T11:11:38.756Z', 0], + ['2019-02-28T11:12:38.756Z', 0], + ]); + }); + }); + + describe('xAxisLabel', () => { + it('constructs a label for the chart x-axis', () => { + expect(areaChart.vm.xAxisLabel).toBe('invocations / minute'); + }); + }); + + describe('yAxisLabel', () => { + it('constructs a label for the chart y-axis', () => { + expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)'); + }); + }); + }); +}); diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js new file mode 100644 index 00000000000..932d712dbec --- /dev/null +++ b/spec/frontend/serverless/components/environment_row_spec.js @@ -0,0 +1,70 @@ +import environmentRowComponent from '~/serverless/components/environment_row.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; + +import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; +import { translate } from '~/serverless/utils'; + +const createComponent = (localVue, env, envName) => + shallowMount(environmentRowComponent, { localVue, propsData: { env, envName } }).vm; + +describe('environment row component', () => { + describe('default global cluster case', () => { + let localVue; + let vm; + + beforeEach(() => { + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*'); + }); + + afterEach(() => vm.$destroy()); + + it('has the correct envId', () => { + expect(vm.envId).toEqual('env-global'); + }); + + it('is open by default', () => { + expect(vm.isOpenClass).toEqual({ 'is-open': true }); + }); + + it('generates correct output', () => { + expect(vm.$el.id).toEqual('env-global'); + expect(vm.$el.classList.contains('is-open')).toBe(true); + expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); + }); + + it('opens and closes correctly', () => { + expect(vm.isOpen).toBe(true); + + vm.toggleOpen(); + + expect(vm.isOpen).toBe(false); + }); + }); + + describe('default named cluster case', () => { + let vm; + let localVue; + + beforeEach(() => { + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test'); + }); + + afterEach(() => vm.$destroy()); + + it('has the correct envId', () => { + expect(vm.envId).toEqual('env-test'); + }); + + it('is open by default', () => { + expect(vm.isOpenClass).toEqual({ 'is-open': true }); + }); + + it('generates correct output', () => { + expect(vm.$el.id).toEqual('env-test'); + expect(vm.$el.classList.contains('is-open')).toBe(true); + expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); + }); + }); +}); diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js new file mode 100644 index 00000000000..a29d4a296ef --- /dev/null +++ b/spec/frontend/serverless/components/function_details_spec.js @@ -0,0 +1,113 @@ +import Vuex from 'vuex'; + +import functionDetailsComponent from '~/serverless/components/function_details.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; + +describe('functionDetailsComponent', () => { + let localVue; + let component; + let store; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); + + store = createStore(); + }); + + afterEach(() => { + component.vm.$destroy(); + }); + + describe('Verify base functionality', () => { + const serviceStub = { + name: 'test', + description: 'a description', + environment: '*', + url: 'http://service.com/test', + namespace: 'test-ns', + podcount: 0, + metricsUrl: '/metrics', + }; + + it('has a name, description, URL, and no pods loaded', () => { + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(), + ).toContain('test'); + + expect( + component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(), + ).toContain('a description'); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain( + 'No pods loaded at this time.', + ); + }); + + it('has a pods loaded', () => { + serviceStub.podcount = 1; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); + }); + + it('has multiple pods loaded', () => { + serviceStub.podcount = 3; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); + }); + + it('can support a missing description', () => { + serviceStub.description = null; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-description').querySelector('div') + .innerHTML.length, + ).toEqual(0); + }); + }); +}); diff --git a/spec/frontend/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js new file mode 100644 index 00000000000..3987e1753bd --- /dev/null +++ b/spec/frontend/serverless/components/function_row_spec.js @@ -0,0 +1,26 @@ +import functionRowComponent from '~/serverless/components/function_row.vue'; +import { shallowMount } from '@vue/test-utils'; + +import { mockServerlessFunction } from '../mock_data'; + +const createComponent = func => shallowMount(functionRowComponent, { propsData: { func } }).vm; + +describe('functionRowComponent', () => { + it('Parses the function details correctly', () => { + const vm = createComponent(mockServerlessFunction); + + expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); + expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); + expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); + + vm.$destroy(); + }); + + it('handles clicks correctly', () => { + const vm = createComponent(mockServerlessFunction); + + expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row + + vm.$destroy(); + }); +}); diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js new file mode 100644 index 00000000000..c32978ea58a --- /dev/null +++ b/spec/frontend/serverless/components/functions_spec.js @@ -0,0 +1,101 @@ +import Vuex from 'vuex'; + +import functionsComponent from '~/serverless/components/functions.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; +import { mockServerlessFunctions } from '../mock_data'; + +describe('functionsComponent', () => { + let component; + let store; + let localVue; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); + + store = createStore(); + }); + + afterEach(() => { + component.vm.$destroy(); + }); + + it('should render empty state when Knative is not installed', () => { + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: false, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); + }); + + it('should render a loading component', () => { + store.dispatch('requestFunctionsLoading'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); + }); + + it('should render empty state when there is no function data', () => { + store.dispatch('receiveFunctionsNoDataSuccess'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect( + component.vm.$el + .querySelector('.empty-state, .js-empty-state') + .classList.contains('js-empty-state'), + ).toBe(true); + + expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual( + 'No functions available', + ); + }); + + it('should render the functions list', () => { + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); + + return component.vm.$nextTick().then(() => { + expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); + }); + }); +}); diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js new file mode 100644 index 00000000000..77aca03772b --- /dev/null +++ b/spec/frontend/serverless/components/missing_prometheus_spec.js @@ -0,0 +1,37 @@ +import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = missingData => + shallowMount(missingPrometheusComponent, { + propsData: { + clustersPath: '/clusters', + helpPath: '/help', + missingData, + }, + }).vm; + +describe('missingPrometheusComponent', () => { + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + it('should render missing prometheus message', () => { + vm = createComponent(false); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Function invocation metrics require Prometheus to be installed first.', + ); + + expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); + }); + + it('should render no prometheus data message', () => { + vm = createComponent(true); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Invocation metrics loading or not available at this time.', + ); + }); +}); diff --git a/spec/frontend/serverless/components/pod_box_spec.js b/spec/frontend/serverless/components/pod_box_spec.js new file mode 100644 index 00000000000..69ac1a2bb5f --- /dev/null +++ b/spec/frontend/serverless/components/pod_box_spec.js @@ -0,0 +1,22 @@ +import podBoxComponent from '~/serverless/components/pod_box.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = count => + shallowMount(podBoxComponent, { + propsData: { + count, + }, + }).vm; + +describe('podBoxComponent', () => { + it('should render three boxes', () => { + const count = 3; + const vm = createComponent(count); + const rects = vm.$el.querySelectorAll('rect'); + + expect(rects.length).toEqual(3); + expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40); + + vm.$destroy(); + }); +}); diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js new file mode 100644 index 00000000000..08c3e4146b1 --- /dev/null +++ b/spec/frontend/serverless/components/url_spec.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import urlComponent from '~/serverless/components/url.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = uri => + shallowMount(Vue.extend(urlComponent), { + propsData: { + uri, + }, + }).vm; + +describe('urlComponent', () => { + it('should render correctly', () => { + const uri = 'http://testfunc.apps.example.com'; + const vm = createComponent(uri); + + expect(vm.$el.classList.contains('clipboard-group')).toBe(true); + expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); + + expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); + + vm.$destroy(); + }); +}); diff --git a/spec/frontend/serverless/mock_data.js b/spec/frontend/serverless/mock_data.js new file mode 100644 index 00000000000..a2c18616324 --- /dev/null +++ b/spec/frontend/serverless/mock_data.js @@ -0,0 +1,136 @@ +export const mockServerlessFunctions = [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, +]; + +export const mockServerlessFunctionsDiffEnv = [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: 'test', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, +]; + +export const mockServerlessFunction = { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: '3', + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', +}; + +export const mockMultilineServerlessFunction = { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: '3', + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'testfunc1\nA test service line\\nWith additional services', + image: 'knative-test-container-buildtemplate', +}; + +export const mockMetrics = { + success: true, + last_update: '2019-02-28T19:11:38.926Z', + metrics: { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [[1551352298.756, '0'], [1551352358.756, '0']], + }, + ], + }, + ], + }, +}; + +export const mockNormalizedMetrics = { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [ + { + time: '2019-02-28T11:11:38.756Z', + value: 0, + }, + { + time: '2019-02-28T11:12:38.756Z', + value: 0, + }, + ], + }, + ], + }, + ], +}; diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js new file mode 100644 index 00000000000..602798573e9 --- /dev/null +++ b/spec/frontend/serverless/store/actions_spec.js @@ -0,0 +1,88 @@ +import MockAdapter from 'axios-mock-adapter'; +import statusCodes from '~/lib/utils/http_status'; +import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; +import axios from '~/lib/utils/axios_utils'; +import testAction from '../../helpers/vuex_action_helper'; +import { adjustMetricQuery } from '../utils'; + +describe('ServerlessActions', () => { + describe('fetchFunctions', () => { + it('should successfully fetch functions', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions)); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [ + { type: 'requestFunctionsLoading' }, + { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions }, + ], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully retry', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [{ type: 'requestFunctionsLoading' }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); + + describe('fetchMetrics', () => { + it('should return no prometheus', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: false }, + {}, + [], + [{ type: 'receiveMetricsNoPrometheus' }], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully fetch metrics', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics)); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: true }, + {}, + [], + [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); +}); diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js new file mode 100644 index 00000000000..fb549c8f153 --- /dev/null +++ b/spec/frontend/serverless/store/getters_spec.js @@ -0,0 +1,43 @@ +import serverlessState from '~/serverless/store/state'; +import * as getters from '~/serverless/store/getters'; +import { mockServerlessFunctions } from '../mock_data'; + +describe('Serverless Store Getters', () => { + let state; + + beforeEach(() => { + state = serverlessState; + }); + + describe('hasPrometheusMissingData', () => { + it('should return false if Prometheus is not installed', () => { + state.hasPrometheus = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return false if Prometheus is installed and there is data', () => { + state.hasPrometheusData = true; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return true if Prometheus is installed and there is no data', () => { + state.hasPrometheus = true; + state.hasPrometheusData = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(true); + }); + }); + + describe('getFunctions', () => { + it('should translate the raw function array to group the functions per environment scope', () => { + state.functions = mockServerlessFunctions; + + const funcs = getters.getFunctions(state); + + expect(Object.keys(funcs)).toContain('*'); + expect(funcs['*'].length).toEqual(2); + }); + }); +}); diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js new file mode 100644 index 00000000000..ca3053e5c38 --- /dev/null +++ b/spec/frontend/serverless/store/mutations_spec.js @@ -0,0 +1,86 @@ +import mutations from '~/serverless/store/mutations'; +import * as types from '~/serverless/store/mutation_types'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; + +describe('ServerlessMutations', () => { + describe('Functions List Mutations', () => { + it('should ensure loading is true', () => { + const state = {}; + + mutations[types.REQUEST_FUNCTIONS_LOADING](state); + + expect(state.isLoading).toEqual(true); + }); + + it('should set proper state once functions are loaded', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(true); + expect(state.functions).toEqual(mockServerlessFunctions); + }); + + it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + }); + + it('should ensure loading has stopped, and an error is raised', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error'); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + expect(state.error).not.toBe(undefined); + }); + }); + + describe('Function Details Metrics Mutations', () => { + it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(true); + expect(state.graphData).toEqual(mockMetrics); + }); + + it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + expect(state.graphData).toBe(undefined); + }); + + it('should properly indicate an error', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error'); + + expect(state.hasPrometheusData).toEqual(false); + expect(state.error).not.toBe(undefined); + }); + + it('should properly indicate when prometheus is installed', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state); + + expect(state.hasPrometheus).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + }); + }); +}); diff --git a/spec/frontend/serverless/utils.js b/spec/frontend/serverless/utils.js new file mode 100644 index 00000000000..5ce2e37d493 --- /dev/null +++ b/spec/frontend/serverless/utils.js @@ -0,0 +1,20 @@ +export const adjustMetricQuery = data => { + const updatedMetric = data.metrics; + + const queries = data.metrics.queries.map(query => ({ + ...query, + result: query.result.map(result => ({ + ...result, + values: result.values.map(([timestamp, value]) => ({ + time: new Date(timestamp * 1000).toISOString(), + value: Number(value), + })), + })), + })); + + updatedMetric.queries = queries; + return updatedMetric; +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/spec/javascripts/serverless/components/area_spec.js b/spec/javascripts/serverless/components/area_spec.js deleted file mode 100644 index 2be6ac3d268..00000000000 --- a/spec/javascripts/serverless/components/area_spec.js +++ /dev/null @@ -1,121 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Area from '~/serverless/components/area.vue'; -import { mockNormalizedMetrics } from '../mock_data'; - -describe('Area component', () => { - const mockWidgets = 'mockWidgets'; - const mockGraphData = mockNormalizedMetrics; - let areaChart; - - beforeEach(() => { - areaChart = shallowMount(Area, { - propsData: { - graphData: mockGraphData, - containerWidth: 0, - }, - slots: { - default: mockWidgets, - }, - }); - }); - - afterEach(() => { - areaChart.destroy(); - }); - - it('renders chart title', () => { - expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); - }); - - it('contains graph widgets from slot', () => { - expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); - }); - - describe('methods', () => { - describe('formatTooltipText', () => { - const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time; - const generateSeriesData = type => ({ - seriesData: [ - { - componentSubType: type, - value: [mockDate, 4], - }, - ], - value: mockDate, - }); - - describe('series is of line type', () => { - beforeEach(() => { - areaChart.vm.formatTooltipText(generateSeriesData('line')); - }); - - it('formats tooltip title', () => { - expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM'); - }); - - it('formats tooltip content', () => { - expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4'); - }); - }); - - it('verify default interval value of 1', () => { - expect(areaChart.vm.getInterval).toBe(1); - }); - }); - - describe('onResize', () => { - const mockWidth = 233; - - beforeEach(() => { - spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ - width: mockWidth, - })); - areaChart.vm.onResize(); - }); - - it('sets area chart width', () => { - expect(areaChart.vm.width).toBe(mockWidth); - }); - }); - }); - - describe('computed', () => { - describe('chartData', () => { - it('utilizes all data points', () => { - expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']); - expect(areaChart.vm.chartData.requests.length).toBe(2); - }); - - it('creates valid data', () => { - const data = areaChart.vm.chartData.requests; - - expect( - data.filter( - datum => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number', - ).length, - ).toBe(data.length); - }); - }); - - describe('generateSeries', () => { - it('utilizes correct time data', () => { - expect(areaChart.vm.generateSeries.data).toEqual([ - ['2019-02-28T11:11:38.756Z', 0], - ['2019-02-28T11:12:38.756Z', 0], - ]); - }); - }); - - describe('xAxisLabel', () => { - it('constructs a label for the chart x-axis', () => { - expect(areaChart.vm.xAxisLabel).toBe('invocations / minute'); - }); - }); - - describe('yAxisLabel', () => { - it('constructs a label for the chart y-axis', () => { - expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)'); - }); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/environment_row_spec.js b/spec/javascripts/serverless/components/environment_row_spec.js deleted file mode 100644 index 932d712dbec..00000000000 --- a/spec/javascripts/serverless/components/environment_row_spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import environmentRowComponent from '~/serverless/components/environment_row.vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; - -import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; -import { translate } from '~/serverless/utils'; - -const createComponent = (localVue, env, envName) => - shallowMount(environmentRowComponent, { localVue, propsData: { env, envName } }).vm; - -describe('environment row component', () => { - describe('default global cluster case', () => { - let localVue; - let vm; - - beforeEach(() => { - localVue = createLocalVue(); - vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*'); - }); - - afterEach(() => vm.$destroy()); - - it('has the correct envId', () => { - expect(vm.envId).toEqual('env-global'); - }); - - it('is open by default', () => { - expect(vm.isOpenClass).toEqual({ 'is-open': true }); - }); - - it('generates correct output', () => { - expect(vm.$el.id).toEqual('env-global'); - expect(vm.$el.classList.contains('is-open')).toBe(true); - expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); - }); - - it('opens and closes correctly', () => { - expect(vm.isOpen).toBe(true); - - vm.toggleOpen(); - - expect(vm.isOpen).toBe(false); - }); - }); - - describe('default named cluster case', () => { - let vm; - let localVue; - - beforeEach(() => { - localVue = createLocalVue(); - vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test'); - }); - - afterEach(() => vm.$destroy()); - - it('has the correct envId', () => { - expect(vm.envId).toEqual('env-test'); - }); - - it('is open by default', () => { - expect(vm.isOpenClass).toEqual({ 'is-open': true }); - }); - - it('generates correct output', () => { - expect(vm.$el.id).toEqual('env-test'); - expect(vm.$el.classList.contains('is-open')).toBe(true); - expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/function_details_spec.js b/spec/javascripts/serverless/components/function_details_spec.js deleted file mode 100644 index a29d4a296ef..00000000000 --- a/spec/javascripts/serverless/components/function_details_spec.js +++ /dev/null @@ -1,113 +0,0 @@ -import Vuex from 'vuex'; - -import functionDetailsComponent from '~/serverless/components/function_details.vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { createStore } from '~/serverless/store'; - -describe('functionDetailsComponent', () => { - let localVue; - let component; - let store; - - beforeEach(() => { - localVue = createLocalVue(); - localVue.use(Vuex); - - store = createStore(); - }); - - afterEach(() => { - component.vm.$destroy(); - }); - - describe('Verify base functionality', () => { - const serviceStub = { - name: 'test', - description: 'a description', - environment: '*', - url: 'http://service.com/test', - namespace: 'test-ns', - podcount: 0, - metricsUrl: '/metrics', - }; - - it('has a name, description, URL, and no pods loaded', () => { - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect( - component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(), - ).toContain('test'); - - expect( - component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(), - ).toContain('a description'); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain( - 'No pods loaded at this time.', - ); - }); - - it('has a pods loaded', () => { - serviceStub.podcount = 1; - - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); - }); - - it('has multiple pods loaded', () => { - serviceStub.podcount = 3; - - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); - }); - - it('can support a missing description', () => { - serviceStub.description = null; - - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect( - component.vm.$el.querySelector('.serverless-function-description').querySelector('div') - .innerHTML.length, - ).toEqual(0); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/function_row_spec.js b/spec/javascripts/serverless/components/function_row_spec.js deleted file mode 100644 index 3987e1753bd..00000000000 --- a/spec/javascripts/serverless/components/function_row_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import functionRowComponent from '~/serverless/components/function_row.vue'; -import { shallowMount } from '@vue/test-utils'; - -import { mockServerlessFunction } from '../mock_data'; - -const createComponent = func => shallowMount(functionRowComponent, { propsData: { func } }).vm; - -describe('functionRowComponent', () => { - it('Parses the function details correctly', () => { - const vm = createComponent(mockServerlessFunction); - - expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); - expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); - expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); - - vm.$destroy(); - }); - - it('handles clicks correctly', () => { - const vm = createComponent(mockServerlessFunction); - - expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row - - vm.$destroy(); - }); -}); diff --git a/spec/javascripts/serverless/components/functions_spec.js b/spec/javascripts/serverless/components/functions_spec.js deleted file mode 100644 index c32978ea58a..00000000000 --- a/spec/javascripts/serverless/components/functions_spec.js +++ /dev/null @@ -1,101 +0,0 @@ -import Vuex from 'vuex'; - -import functionsComponent from '~/serverless/components/functions.vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { createStore } from '~/serverless/store'; -import { mockServerlessFunctions } from '../mock_data'; - -describe('functionsComponent', () => { - let component; - let store; - let localVue; - - beforeEach(() => { - localVue = createLocalVue(); - localVue.use(Vuex); - - store = createStore(); - }); - - afterEach(() => { - component.vm.$destroy(); - }); - - it('should render empty state when Knative is not installed', () => { - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: false, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); - }); - - it('should render a loading component', () => { - store.dispatch('requestFunctionsLoading'); - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); - }); - - it('should render empty state when there is no function data', () => { - store.dispatch('receiveFunctionsNoDataSuccess'); - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - expect( - component.vm.$el - .querySelector('.empty-state, .js-empty-state') - .classList.contains('js-empty-state'), - ).toBe(true); - - expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual( - 'No functions available', - ); - }); - - it('should render the functions list', () => { - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); - - return component.vm.$nextTick().then(() => { - expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/missing_prometheus_spec.js b/spec/javascripts/serverless/components/missing_prometheus_spec.js deleted file mode 100644 index 77aca03772b..00000000000 --- a/spec/javascripts/serverless/components/missing_prometheus_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; -import { shallowMount } from '@vue/test-utils'; - -const createComponent = missingData => - shallowMount(missingPrometheusComponent, { - propsData: { - clustersPath: '/clusters', - helpPath: '/help', - missingData, - }, - }).vm; - -describe('missingPrometheusComponent', () => { - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - it('should render missing prometheus message', () => { - vm = createComponent(false); - - expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( - 'Function invocation metrics require Prometheus to be installed first.', - ); - - expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); - }); - - it('should render no prometheus data message', () => { - vm = createComponent(true); - - expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( - 'Invocation metrics loading or not available at this time.', - ); - }); -}); diff --git a/spec/javascripts/serverless/components/pod_box_spec.js b/spec/javascripts/serverless/components/pod_box_spec.js deleted file mode 100644 index 69ac1a2bb5f..00000000000 --- a/spec/javascripts/serverless/components/pod_box_spec.js +++ /dev/null @@ -1,22 +0,0 @@ -import podBoxComponent from '~/serverless/components/pod_box.vue'; -import { shallowMount } from '@vue/test-utils'; - -const createComponent = count => - shallowMount(podBoxComponent, { - propsData: { - count, - }, - }).vm; - -describe('podBoxComponent', () => { - it('should render three boxes', () => { - const count = 3; - const vm = createComponent(count); - const rects = vm.$el.querySelectorAll('rect'); - - expect(rects.length).toEqual(3); - expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40); - - vm.$destroy(); - }); -}); diff --git a/spec/javascripts/serverless/components/url_spec.js b/spec/javascripts/serverless/components/url_spec.js deleted file mode 100644 index 08c3e4146b1..00000000000 --- a/spec/javascripts/serverless/components/url_spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import Vue from 'vue'; -import urlComponent from '~/serverless/components/url.vue'; -import { shallowMount } from '@vue/test-utils'; - -const createComponent = uri => - shallowMount(Vue.extend(urlComponent), { - propsData: { - uri, - }, - }).vm; - -describe('urlComponent', () => { - it('should render correctly', () => { - const uri = 'http://testfunc.apps.example.com'; - const vm = createComponent(uri); - - expect(vm.$el.classList.contains('clipboard-group')).toBe(true); - expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); - - expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); - - vm.$destroy(); - }); -}); diff --git a/spec/javascripts/serverless/mock_data.js b/spec/javascripts/serverless/mock_data.js deleted file mode 100644 index a2c18616324..00000000000 --- a/spec/javascripts/serverless/mock_data.js +++ /dev/null @@ -1,136 +0,0 @@ -export const mockServerlessFunctions = [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, -]; - -export const mockServerlessFunctionsDiffEnv = [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: 'test', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, -]; - -export const mockServerlessFunction = { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: '3', - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', -}; - -export const mockMultilineServerlessFunction = { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: '3', - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'testfunc1\nA test service line\\nWith additional services', - image: 'knative-test-container-buildtemplate', -}; - -export const mockMetrics = { - success: true, - last_update: '2019-02-28T19:11:38.926Z', - metrics: { - id: 22, - title: 'Knative function invocations', - required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], - weight: 0, - y_label: 'Invocations', - queries: [ - { - query_range: - 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', - unit: 'requests', - label: 'invocations / minute', - result: [ - { - metric: {}, - values: [[1551352298.756, '0'], [1551352358.756, '0']], - }, - ], - }, - ], - }, -}; - -export const mockNormalizedMetrics = { - id: 22, - title: 'Knative function invocations', - required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], - weight: 0, - y_label: 'Invocations', - queries: [ - { - query_range: - 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', - unit: 'requests', - label: 'invocations / minute', - result: [ - { - metric: {}, - values: [ - { - time: '2019-02-28T11:11:38.756Z', - value: 0, - }, - { - time: '2019-02-28T11:12:38.756Z', - value: 0, - }, - ], - }, - ], - }, - ], -}; diff --git a/spec/javascripts/serverless/store/actions_spec.js b/spec/javascripts/serverless/store/actions_spec.js deleted file mode 100644 index 602798573e9..00000000000 --- a/spec/javascripts/serverless/store/actions_spec.js +++ /dev/null @@ -1,88 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import statusCodes from '~/lib/utils/http_status'; -import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions'; -import { mockServerlessFunctions, mockMetrics } from '../mock_data'; -import axios from '~/lib/utils/axios_utils'; -import testAction from '../../helpers/vuex_action_helper'; -import { adjustMetricQuery } from '../utils'; - -describe('ServerlessActions', () => { - describe('fetchFunctions', () => { - it('should successfully fetch functions', done => { - const endpoint = '/functions'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions)); - - testAction( - fetchFunctions, - { functionsPath: endpoint }, - {}, - [], - [ - { type: 'requestFunctionsLoading' }, - { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions }, - ], - () => { - mock.restore(); - done(); - }, - ); - }); - - it('should successfully retry', done => { - const endpoint = '/functions'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); - - testAction( - fetchFunctions, - { functionsPath: endpoint }, - {}, - [], - [{ type: 'requestFunctionsLoading' }], - () => { - mock.restore(); - done(); - }, - ); - }); - }); - - describe('fetchMetrics', () => { - it('should return no prometheus', done => { - const endpoint = '/metrics'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); - - testAction( - fetchMetrics, - { metricsPath: endpoint, hasPrometheus: false }, - {}, - [], - [{ type: 'receiveMetricsNoPrometheus' }], - () => { - mock.restore(); - done(); - }, - ); - }); - - it('should successfully fetch metrics', done => { - const endpoint = '/metrics'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics)); - - testAction( - fetchMetrics, - { metricsPath: endpoint, hasPrometheus: true }, - {}, - [], - [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }], - () => { - mock.restore(); - done(); - }, - ); - }); - }); -}); diff --git a/spec/javascripts/serverless/store/getters_spec.js b/spec/javascripts/serverless/store/getters_spec.js deleted file mode 100644 index fb549c8f153..00000000000 --- a/spec/javascripts/serverless/store/getters_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import serverlessState from '~/serverless/store/state'; -import * as getters from '~/serverless/store/getters'; -import { mockServerlessFunctions } from '../mock_data'; - -describe('Serverless Store Getters', () => { - let state; - - beforeEach(() => { - state = serverlessState; - }); - - describe('hasPrometheusMissingData', () => { - it('should return false if Prometheus is not installed', () => { - state.hasPrometheus = false; - - expect(getters.hasPrometheusMissingData(state)).toEqual(false); - }); - - it('should return false if Prometheus is installed and there is data', () => { - state.hasPrometheusData = true; - - expect(getters.hasPrometheusMissingData(state)).toEqual(false); - }); - - it('should return true if Prometheus is installed and there is no data', () => { - state.hasPrometheus = true; - state.hasPrometheusData = false; - - expect(getters.hasPrometheusMissingData(state)).toEqual(true); - }); - }); - - describe('getFunctions', () => { - it('should translate the raw function array to group the functions per environment scope', () => { - state.functions = mockServerlessFunctions; - - const funcs = getters.getFunctions(state); - - expect(Object.keys(funcs)).toContain('*'); - expect(funcs['*'].length).toEqual(2); - }); - }); -}); diff --git a/spec/javascripts/serverless/store/mutations_spec.js b/spec/javascripts/serverless/store/mutations_spec.js deleted file mode 100644 index ca3053e5c38..00000000000 --- a/spec/javascripts/serverless/store/mutations_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import mutations from '~/serverless/store/mutations'; -import * as types from '~/serverless/store/mutation_types'; -import { mockServerlessFunctions, mockMetrics } from '../mock_data'; - -describe('ServerlessMutations', () => { - describe('Functions List Mutations', () => { - it('should ensure loading is true', () => { - const state = {}; - - mutations[types.REQUEST_FUNCTIONS_LOADING](state); - - expect(state.isLoading).toEqual(true); - }); - - it('should set proper state once functions are loaded', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(true); - expect(state.functions).toEqual(mockServerlessFunctions); - }); - - it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(false); - expect(state.functions).toBe(undefined); - }); - - it('should ensure loading has stopped, and an error is raised', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error'); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(false); - expect(state.functions).toBe(undefined); - expect(state.error).not.toBe(undefined); - }); - }); - - describe('Function Details Metrics Mutations', () => { - it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics); - - expect(state.isLoading).toEqual(false); - expect(state.hasPrometheusData).toEqual(true); - expect(state.graphData).toEqual(mockMetrics); - }); - - it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state); - - expect(state.isLoading).toEqual(false); - expect(state.hasPrometheusData).toEqual(false); - expect(state.graphData).toBe(undefined); - }); - - it('should properly indicate an error', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error'); - - expect(state.hasPrometheusData).toEqual(false); - expect(state.error).not.toBe(undefined); - }); - - it('should properly indicate when prometheus is installed', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state); - - expect(state.hasPrometheus).toEqual(false); - expect(state.hasPrometheusData).toEqual(false); - }); - }); -}); diff --git a/spec/javascripts/serverless/utils.js b/spec/javascripts/serverless/utils.js deleted file mode 100644 index 5ce2e37d493..00000000000 --- a/spec/javascripts/serverless/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -export const adjustMetricQuery = data => { - const updatedMetric = data.metrics; - - const queries = data.metrics.queries.map(query => ({ - ...query, - result: query.result.map(result => ({ - ...result, - values: result.values.map(([timestamp, value]) => ({ - time: new Date(timestamp * 1000).toISOString(), - value: Number(value), - })), - })), - })); - - updatedMetric.queries = queries; - return updatedMetric; -}; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; -- cgit v1.2.1 From 083e896d5e8651c082349ed5f82597fa95d3265d Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 7 Apr 2019 04:00:28 +0100 Subject: Fix unmocked requests in serverless tests --- spec/frontend/serverless/components/area_spec.js | 2 +- spec/frontend/serverless/components/function_row_spec.js | 3 ++- spec/frontend/serverless/components/functions_spec.js | 15 ++++++++++----- spec/frontend/serverless/store/actions_spec.js | 4 +++- 4 files changed, 16 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js index 2be6ac3d268..8b6f664ae25 100644 --- a/spec/frontend/serverless/components/area_spec.js +++ b/spec/frontend/serverless/components/area_spec.js @@ -67,7 +67,7 @@ describe('Area component', () => { const mockWidth = 233; beforeEach(() => { - spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ + jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ width: mockWidth, })); areaChart.vm.onResize(); diff --git a/spec/frontend/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js index 3987e1753bd..414fdc5cd82 100644 --- a/spec/frontend/serverless/components/function_row_spec.js +++ b/spec/frontend/serverless/components/function_row_spec.js @@ -3,7 +3,8 @@ import { shallowMount } from '@vue/test-utils'; import { mockServerlessFunction } from '../mock_data'; -const createComponent = func => shallowMount(functionRowComponent, { propsData: { func } }).vm; +const createComponent = func => + shallowMount(functionRowComponent, { propsData: { func }, sync: false }).vm; describe('functionRowComponent', () => { it('Parses the function details correctly', () => { diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js index c32978ea58a..5533de1a70a 100644 --- a/spec/frontend/serverless/components/functions_spec.js +++ b/spec/frontend/serverless/components/functions_spec.js @@ -1,5 +1,6 @@ import Vuex from 'vuex'; - +import AxiosMockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import functionsComponent from '~/serverless/components/functions.vue'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createStore } from '~/serverless/store'; @@ -79,15 +80,19 @@ describe('functionsComponent', () => { ); }); - it('should render the functions list', () => { + fit('should render the functions list', () => { + const statusPath = 'statusPath'; + const axiosMock = new AxiosMockAdapter(axios); + axiosMock.onGet(statusPath).reply(200); + component = shallowMount(functionsComponent, { localVue, store, propsData: { installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', + clustersPath: 'clustersPath', + helpPath: 'helpPath', + statusPath, }, sync: false, }); diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js index 602798573e9..aac57c75a4f 100644 --- a/spec/frontend/serverless/store/actions_spec.js +++ b/spec/frontend/serverless/store/actions_spec.js @@ -32,7 +32,9 @@ describe('ServerlessActions', () => { it('should successfully retry', done => { const endpoint = '/functions'; const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + mock + .onGet(endpoint) + .reply(() => new Promise(resolve => setTimeout(() => resolve(200), Infinity))); testAction( fetchFunctions, -- cgit v1.2.1 From 2daba22b12ab1a94bf6643ab201ff6eeac2fe05f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 7 Apr 2019 04:11:14 +0100 Subject: Disable sync mode for serverless tests Sync mode by default will soon be removed. --- spec/frontend/serverless/components/area_spec.js | 1 + spec/frontend/serverless/components/environment_row_spec.js | 2 +- spec/frontend/serverless/components/function_details_spec.js | 4 ++++ spec/frontend/serverless/components/missing_prometheus_spec.js | 1 + spec/frontend/serverless/components/pod_box_spec.js | 1 + spec/frontend/serverless/components/url_spec.js | 1 + 6 files changed, 9 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js index 8b6f664ae25..62005e1981a 100644 --- a/spec/frontend/serverless/components/area_spec.js +++ b/spec/frontend/serverless/components/area_spec.js @@ -16,6 +16,7 @@ describe('Area component', () => { slots: { default: mockWidgets, }, + sync: false, }); }); diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js index 932d712dbec..161a637dd75 100644 --- a/spec/frontend/serverless/components/environment_row_spec.js +++ b/spec/frontend/serverless/components/environment_row_spec.js @@ -5,7 +5,7 @@ import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock import { translate } from '~/serverless/utils'; const createComponent = (localVue, env, envName) => - shallowMount(environmentRowComponent, { localVue, propsData: { env, envName } }).vm; + shallowMount(environmentRowComponent, { localVue, propsData: { env, envName }, sync: false }).vm; describe('environment row component', () => { describe('default global cluster case', () => { diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js index a29d4a296ef..31348ff1194 100644 --- a/spec/frontend/serverless/components/function_details_spec.js +++ b/spec/frontend/serverless/components/function_details_spec.js @@ -41,6 +41,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect( @@ -68,6 +69,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); @@ -85,6 +87,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); @@ -102,6 +105,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect( diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js index 77aca03772b..d0df6125290 100644 --- a/spec/frontend/serverless/components/missing_prometheus_spec.js +++ b/spec/frontend/serverless/components/missing_prometheus_spec.js @@ -8,6 +8,7 @@ const createComponent = missingData => helpPath: '/help', missingData, }, + sync: false, }).vm; describe('missingPrometheusComponent', () => { diff --git a/spec/frontend/serverless/components/pod_box_spec.js b/spec/frontend/serverless/components/pod_box_spec.js index 69ac1a2bb5f..d82825d8f62 100644 --- a/spec/frontend/serverless/components/pod_box_spec.js +++ b/spec/frontend/serverless/components/pod_box_spec.js @@ -6,6 +6,7 @@ const createComponent = count => propsData: { count, }, + sync: false, }).vm; describe('podBoxComponent', () => { diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js index 08c3e4146b1..d05a9bba103 100644 --- a/spec/frontend/serverless/components/url_spec.js +++ b/spec/frontend/serverless/components/url_spec.js @@ -7,6 +7,7 @@ const createComponent = uri => propsData: { uri, }, + sync: false, }).vm; describe('urlComponent', () => { -- cgit v1.2.1 From d7c1bed80d55611ca297c5c7664658d1144c5a85 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 26 Mar 2019 09:26:12 -0500 Subject: GitHub import: Run housekeeping after initial import After an initial fetch, the repository will have many loose objects and refs. Running a `git gc` will compact refs into `packed-refs` and objects into `.pack` files, which should make importing pull requests faster. Part of https://gitlab.com/gitlab-org/gitlab-ce/issues/59477 --- spec/lib/gitlab/github_import/importer/repository_importer_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 41810a8ec03..705df1f4fe7 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -197,6 +197,11 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do .to receive(:fetch_as_mirror) .with(project.import_url, refmap: Gitlab::GithubImport.refmap, forced: true, remote_name: 'github') + service = double + expect(Projects::HousekeepingService) + .to receive(:new).with(project, :gc).and_return(service) + expect(service).to receive(:execute) + expect(importer.import_repository).to eq(true) end -- cgit v1.2.1 From 55a9bff7e274646b35e7e0cee0a772b4afb490b2 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 14 Mar 2019 20:52:01 +0000 Subject: Improve project merge request settings Prioritize and simplify project settings content. --- .../projects/settings/user_manages_merge_requests_settings_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb index 84de6858d5f..b1c2bab08c0 100644 --- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb @@ -93,11 +93,13 @@ describe 'Projects > Settings > User manages merge request settings' do it 'when unchecked sets :printing_merge_request_link_enabled to false' do uncheck('project_printing_merge_request_link_enabled') within('.merge-request-settings-form') do + find('.qa-save-merge-request-changes') click_on('Save changes') end - # Wait for save to complete and page to reload + find('.flash-notice') checkbox = find_field('project_printing_merge_request_link_enabled') + expect(checkbox).not_to be_checked project.reload -- cgit v1.2.1 From 021e8790c6091994d3b3e31cbf180939608698da Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 7 Apr 2019 20:55:29 -0700 Subject: Enable Gitaly FindCommit caching for TreeController Accessing /namespace/project/tree/master appears to query FindCommit 5 times with identical parameters. This reduces the number of FindCommit queries to 1. --- spec/controllers/projects/tree_controller_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index b15a2bc84a5..78201498eaa 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -16,6 +16,8 @@ describe Projects::TreeController do render_views before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + get(:show, params: { namespace_id: project.namespace.to_param, -- cgit v1.2.1 From e82ed3f46bcc5442668e64b7080f7bb4425956c0 Mon Sep 17 00:00:00 2001 From: charlieablett Date: Mon, 8 Apr 2019 16:52:40 +1200 Subject: Abstract out method from spec to support --- spec/features/boards/sidebar_spec.rb | 1 + spec/support/helpers/filtered_search_helpers.rb | 6 ++++++ 2 files changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index dfdb8d589eb..d036699f226 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -129,6 +129,7 @@ describe 'Issue Boards', :js do click_link 'Unassigned' end + close_dropdown_menu_if_visible wait_for_requests expect(page).to have_content('No assignee') diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 6569feec39b..03057a102c5 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -149,4 +149,10 @@ module FilteredSearchHelpers loop until find('.filtered-search').value.strip == text end end + + def close_dropdown_menu_if_visible + find('.dropdown-menu-toggle', visible: :all).tap do |toggle| + toggle.click if toggle.visible? + end + end end -- cgit v1.2.1 From e0efa97c7f24bc5816ecdcb6a633150e7a6a43f6 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 3 Apr 2019 18:33:52 +0700 Subject: Prevent triggering pipelines when target branch is updated Currently, pipelines for merge requests are triggered when source or target branch is updated. However, we should create only when source branch is updated, because it runs unexpected pipelines. --- .../merge_requests/refresh_service_spec.rb | 34 +++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index bd10523bc94..5ed06df7072 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -146,7 +146,10 @@ describe MergeRequests::RefreshService do stub_ci_pipeline_yaml_file(YAML.dump(config)) end - subject { service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') } + subject { service.new(project, @user).execute(@oldrev, @newrev, ref) } + + let(:ref) { 'refs/heads/master' } + let(:project) { @project } context "when .gitlab-ci.yml has merge_requests keywords" do let(:config) do @@ -162,14 +165,17 @@ describe MergeRequests::RefreshService do it 'create detached merge request pipeline with commits' do expect { subject } .to change { @merge_request.merge_request_pipelines.count }.by(1) - .and change { @fork_merge_request.merge_request_pipelines.count }.by(1) .and change { @another_merge_request.merge_request_pipelines.count }.by(0) expect(@merge_request.has_commits?).to be_truthy - expect(@fork_merge_request.has_commits?).to be_truthy expect(@another_merge_request.has_commits?).to be_falsy end + it 'does not create detached merge request pipeline for forked project' do + expect { subject } + .not_to change { @fork_merge_request.merge_request_pipelines.count } + end + it 'create detached merge request pipeline for non-fork merge request' do subject @@ -177,11 +183,25 @@ describe MergeRequests::RefreshService do .to be_detached_merge_request_pipeline end - it 'create legacy detached merge request pipeline for fork merge request' do - subject + context 'when service is hooked by target branch' do + let(:ref) { 'refs/heads/feature' } - expect(@fork_merge_request.merge_request_pipelines.first) - .to be_legacy_detached_merge_request_pipeline + it 'does not create detached merge request pipeline' do + expect { subject } + .not_to change { @merge_request.merge_request_pipelines.count } + end + end + + context 'when service runs on forked project' do + let(:project) { @fork_project } + + it 'creates legacy detached merge request pipeline for fork merge request' do + expect { subject } + .to change { @fork_merge_request.merge_request_pipelines.count }.by(1) + + expect(@fork_merge_request.merge_request_pipelines.first) + .to be_legacy_detached_merge_request_pipeline + end end context 'when ci_use_merge_request_ref feature flag is false' do -- cgit v1.2.1 From be8997ca7c504a9f7fc0821c1b014d9260e00b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Alc=C3=A1ntara?= Date: Mon, 8 Apr 2019 07:31:27 +0000 Subject: Migrate clusters tests to jest Move cluster applications manager tests from karma to jest. Fixes some migration issues related with timeouts, and HTTP request expectations. --- spec/frontend/clusters/clusters_bundle_spec.js | 376 +++++++++++++++++++ .../clusters/components/application_row_spec.js | 406 +++++++++++++++++++++ .../clusters/components/applications_spec.js | 350 ++++++++++++++++++ spec/frontend/clusters/services/mock_data.js | 130 +++++++ .../clusters/stores/clusters_store_spec.js | 142 +++++++ spec/javascripts/clusters/clusters_bundle_spec.js | 360 ------------------ .../clusters/components/application_row_spec.js | 406 --------------------- .../clusters/components/applications_spec.js | 350 ------------------ spec/javascripts/clusters/services/mock_data.js | 130 ------- .../clusters/stores/clusters_store_spec.js | 142 ------- 10 files changed, 1404 insertions(+), 1388 deletions(-) create mode 100644 spec/frontend/clusters/clusters_bundle_spec.js create mode 100644 spec/frontend/clusters/components/application_row_spec.js create mode 100644 spec/frontend/clusters/components/applications_spec.js create mode 100644 spec/frontend/clusters/services/mock_data.js create mode 100644 spec/frontend/clusters/stores/clusters_store_spec.js delete mode 100644 spec/javascripts/clusters/clusters_bundle_spec.js delete mode 100644 spec/javascripts/clusters/components/application_row_spec.js delete mode 100644 spec/javascripts/clusters/components/applications_spec.js delete mode 100644 spec/javascripts/clusters/services/mock_data.js delete mode 100644 spec/javascripts/clusters/stores/clusters_store_spec.js (limited to 'spec') diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js new file mode 100644 index 00000000000..eea7bd87257 --- /dev/null +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -0,0 +1,376 @@ +import Clusters from '~/clusters/clusters_bundle'; +import { + REQUEST_SUBMITTED, + REQUEST_FAILURE, + APPLICATION_STATUS, + INGRESS_DOMAIN_SUFFIX, +} from '~/clusters/constants'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { loadHTMLFixture } from 'helpers/fixtures'; +import { setTestTimeout } from 'helpers/timeout'; +import $ from 'jquery'; + +describe('Clusters', () => { + setTestTimeout(500); + + let cluster; + let mock; + + const mockGetClusterStatusRequest = () => { + const { statusPath } = document.querySelector('.js-edit-cluster-form').dataset; + + mock = new MockAdapter(axios); + + mock.onGet(statusPath).reply(200); + }; + + beforeEach(() => { + loadHTMLFixture('clusters/show_cluster.html'); + }); + + beforeEach(() => { + mockGetClusterStatusRequest(); + }); + + beforeEach(() => { + cluster = new Clusters(); + }); + + afterEach(() => { + cluster.destroy(); + mock.restore(); + }); + + describe('toggle', () => { + it('should update the button and the input field on click', done => { + const toggleButton = document.querySelector( + '.js-cluster-enable-toggle-area .js-project-feature-toggle', + ); + const toggleInput = document.querySelector( + '.js-cluster-enable-toggle-area .js-project-feature-toggle-input', + ); + + $(toggleInput).one('trigger-change', () => { + expect(toggleButton.classList).not.toContain('is-checked'); + expect(toggleInput.getAttribute('value')).toEqual('false'); + done(); + }); + + toggleButton.click(); + }); + }); + + describe('showToken', () => { + it('should update token field type', () => { + cluster.showTokenButton.click(); + + expect(cluster.tokenField.getAttribute('type')).toEqual('text'); + + cluster.showTokenButton.click(); + + expect(cluster.tokenField.getAttribute('type')).toEqual('password'); + }); + + it('should update show token button text', () => { + cluster.showTokenButton.click(); + + expect(cluster.showTokenButton.textContent).toEqual('Hide'); + + cluster.showTokenButton.click(); + + expect(cluster.showTokenButton.textContent).toEqual('Show'); + }); + }); + + describe('checkForNewInstalls', () => { + const INITIAL_APP_MAP = { + helm: { status: null, title: 'Helm Tiller' }, + ingress: { status: null, title: 'Ingress' }, + runner: { status: null, title: 'GitLab Runner' }, + }; + + it('does not show alert when things transition from initial null state to something', () => { + cluster.checkForNewInstalls(INITIAL_APP_MAP, { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' }, + }); + + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + + expect(flashMessage).toBeNull(); + }); + + it('shows an alert when something gets newly installed', () => { + cluster.checkForNewInstalls( + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, + }, + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, + }, + ); + + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + + expect(flashMessage).not.toBeNull(); + expect(flashMessage.textContent.trim()).toEqual( + 'Helm Tiller was successfully installed on your Kubernetes cluster', + ); + }); + + it('shows an alert when multiple things gets newly installed', () => { + cluster.checkForNewInstalls( + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, + ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' }, + }, + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, + ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' }, + }, + ); + + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + + expect(flashMessage).not.toBeNull(); + expect(flashMessage.textContent.trim()).toEqual( + 'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster', + ); + }); + }); + + describe('updateContainer', () => { + describe('when creating cluster', () => { + it('should show the creating container', () => { + cluster.updateContainer(null, 'creating'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + + it('should continue to show `creating` banner with subsequent updates of the same status', () => { + cluster.updateContainer('creating', 'creating'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + }); + + describe('when cluster is created', () => { + it('should show the success container and fresh the page', () => { + cluster.updateContainer(null, 'created'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + + it('should not show a banner when status is already `created`', () => { + cluster.updateContainer('created', 'created'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + }); + + describe('when cluster has error', () => { + it('should show the error container', () => { + cluster.updateContainer(null, 'errored', 'this is an error'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.errorReasonContainer.textContent).toContain('this is an error'); + }); + + it('should show `error` banner when previously `creating`', () => { + cluster.updateContainer('creating', 'errored'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy(); + }); + }); + }); + + describe('installApplication', () => { + it('tries to install helm', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); + + cluster.installApplication({ id: 'helm' }); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.helm.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined); + }); + + it('tries to install ingress', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null); + + cluster.installApplication({ id: 'ingress' }); + + expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); + }); + + it('tries to install runner', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.runner.requestStatus).toEqual(null); + + cluster.installApplication({ id: 'runner' }); + + expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.runner.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); + }); + + it('tries to install jupyter', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null); + cluster.installApplication({ + id: 'jupyter', + params: { hostname: cluster.store.state.applications.jupyter.hostname }, + }); + + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { + hostname: cluster.store.state.applications.jupyter.hostname, + }); + }); + + it('sets error request status when the request fails', () => { + jest + .spyOn(cluster.service, 'installApplication') + .mockRejectedValueOnce(new Error('STUBBED ERROR')); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); + + const promise = cluster.installApplication({ id: 'helm' }); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.helm.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalled(); + + return promise.then(() => { + expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE); + expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); + }); + }); + }); + + describe('handleSuccess', () => { + beforeEach(() => { + jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis(); + jest.spyOn(cluster, 'toggleIngressDomainHelpText').mockReturnThis(); + jest.spyOn(cluster, 'checkForNewInstalls').mockReturnThis(); + jest.spyOn(cluster, 'updateContainer').mockReturnThis(); + + cluster.handleSuccess({ data: {} }); + }); + + it('updates clusters store', () => { + expect(cluster.store.updateStateFromServer).toHaveBeenCalled(); + }); + + it('checks for new installable apps', () => { + expect(cluster.checkForNewInstalls).toHaveBeenCalled(); + }); + + it('toggles ingress domain help text', () => { + expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled(); + }); + + it('updates message containers', () => { + expect(cluster.updateContainer).toHaveBeenCalled(); + }); + }); + + describe('toggleIngressDomainHelpText', () => { + const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS; + let ingressPreviousState; + let ingressNewState; + + beforeEach(() => { + ingressPreviousState = { status: INSTALLABLE }; + ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + }); + + describe(`when ingress application new status is ${INSTALLED}`, () => { + beforeEach(() => { + ingressNewState.status = INSTALLED; + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + }); + + it('displays custom domain help text', () => { + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false); + }); + + it('updates ingress external ip address', () => { + expect(cluster.ingressDomainSnippet.textContent).toEqual( + `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`, + ); + }); + }); + + describe(`when ingress application new status is different from ${INSTALLED}`, () => { + it('hides custom domain help text', () => { + ingressNewState.status = NOT_INSTALLABLE; + cluster.ingressDomainHelpText.classList.remove('hide'); + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + + describe('when ingress application new status and old status are the same', () => { + it('does not display custom domain help text', () => { + ingressPreviousState.status = INSTALLED; + ingressNewState.status = ingressPreviousState.status; + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + + describe(`when ingress new status is ${INSTALLED} and there isn’t an ip assigned`, () => { + it('does not display custom domain help text', () => { + ingressNewState.externalIp = null; + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + }); +}); diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js new file mode 100644 index 00000000000..b28d0075d06 --- /dev/null +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -0,0 +1,406 @@ +import Vue from 'vue'; +import eventHub from '~/clusters/event_hub'; +import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '~/clusters/constants'; +import applicationRow from '~/clusters/components/application_row.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { DEFAULT_APPLICATION_STATE } from '../services/mock_data'; + +describe('Application Row', () => { + let vm; + let ApplicationRow; + + beforeEach(() => { + ApplicationRow = Vue.extend(applicationRow); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('Title', () => { + it('shows title', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + titleLink: null, + }); + const title = vm.$el.querySelector('.js-cluster-application-title'); + + expect(title.tagName).toEqual('SPAN'); + expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title); + }); + + it('shows title link', () => { + expect(DEFAULT_APPLICATION_STATE.titleLink).toBeDefined(); + + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + }); + const title = vm.$el.querySelector('.js-cluster-application-title'); + + expect(title.tagName).toEqual('A'); + expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title); + }); + }); + + describe('Install button', () => { + it('has indeterminate state on page load', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: null, + }); + + expect(vm.installButtonLabel).toBeUndefined(); + }); + + it('has install button', () => { + const installationBtn = vm.$el.querySelector('.js-cluster-application-install-button'); + + expect(installationBtn).not.toBe(null); + }); + + it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.NOT_INSTALLABLE, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(false); + }); + + it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.SCHEDULED, + }); + + expect(vm.installButtonLabel).toEqual('Installing'); + expect(vm.installButtonLoading).toEqual(true); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLING, + }); + + expect(vm.installButtonLabel).toEqual('Installing'); + expect(vm.installButtonLoading).toEqual(true); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has loading "Installing" when REQUEST_SUBMITTED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + requestStatus: REQUEST_SUBMITTED, + }); + + expect(vm.installButtonLabel).toEqual('Installing'); + expect(vm.installButtonLoading).toEqual(true); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLED, + }); + + expect(vm.installButtonLabel).toEqual('Installed'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has disabled "Installed" when APPLICATION_STATUS.UPDATING', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATING, + }); + + expect(vm.installButtonLabel).toEqual('Installed'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.ERROR, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(false); + }); + + it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + requestStatus: REQUEST_FAILURE, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(false); + }); + + it('clicking install button emits event', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + installButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: {}, + }); + }); + + it('clicking install button when installApplicationRequestParams are provided emits event', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + installApplicationRequestParams: { hostname: 'jupyter' }, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + installButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: { hostname: 'jupyter' }, + }); + }); + + it('clicking disabled install button emits nothing', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLING, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + expect(vm.installButtonDisabled).toEqual(true); + + installButton.click(); + + expect(eventHub.$emit).not.toHaveBeenCalled(); + }); + }); + + describe('Upgrade button', () => { + it('has indeterminate state on page load', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: null, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).toBe(null); + }); + + it('has enabled "Upgrade" when "upgradeAvailable" is true', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + upgradeAvailable: true, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).not.toBe(null); + expect(upgradeBtn.innerHTML).toContain('Upgrade'); + }); + + it('has enabled "Retry update" when APPLICATION_STATUS.UPDATE_ERRORED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATE_ERRORED, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).not.toBe(null); + expect(vm.upgradeFailed).toBe(true); + expect(upgradeBtn.innerHTML).toContain('Retry update'); + }); + + it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATING, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).not.toBe(null); + expect(vm.isUpgrading).toBe(true); + expect(upgradeBtn.innerHTML).toContain('Updating'); + }); + + it('clicking upgrade button emits event', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATE_ERRORED, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + upgradeBtn.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: {}, + }); + }); + + it('clicking disabled upgrade button emits nothing', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATING, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + upgradeBtn.click(); + + expect(eventHub.$emit).not.toHaveBeenCalled(); + }); + + it('displays an error message if application upgrade failed', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + title: 'GitLab Runner', + status: APPLICATION_STATUS.UPDATE_ERRORED, + }); + const failureMessage = vm.$el.querySelector( + '.js-cluster-application-upgrade-failure-message', + ); + + expect(failureMessage).not.toBe(null); + expect(failureMessage.innerHTML).toContain( + 'Update failed. Please check the logs and try again.', + ); + }); + }); + + describe('Version', () => { + it('displays a version number if application has been upgraded', () => { + const version = '0.1.45'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATED, + version, + }); + const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + + expect(upgradeDetails.innerHTML).toContain('Upgraded'); + expect(versionEl).not.toBe(null); + expect(versionEl.innerHTML).toContain(version); + }); + + it('contains a link to the chart repo if application has been upgraded', () => { + const version = '0.1.45'; + const chartRepo = 'https://gitlab.com/charts/gitlab-runner'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATED, + chartRepo, + version, + }); + const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + + expect(versionEl.href).toEqual(chartRepo); + expect(versionEl.target).toEqual('_blank'); + }); + + it('does not display a version number if application upgrade failed', () => { + const version = '0.1.45'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATE_ERRORED, + version, + }); + const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + + expect(upgradeDetails.innerHTML).toContain('failed'); + expect(versionEl).toBe(null); + }); + }); + + describe('Error block', () => { + it('does not show error block when there is no error', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: null, + requestStatus: null, + }); + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); + + expect(generalErrorMessage).toBeNull(); + }); + + it('shows status reason when APPLICATION_STATUS.ERROR', () => { + const statusReason = 'We broke it 0.0'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.ERROR, + statusReason, + }); + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); + const statusErrorMessage = vm.$el.querySelector( + '.js-cluster-application-status-error-message', + ); + + expect(generalErrorMessage.textContent.trim()).toEqual( + `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, + ); + + expect(statusErrorMessage.textContent.trim()).toEqual(statusReason); + }); + + it('shows request reason when REQUEST_FAILURE', () => { + const requestReason = 'We broke thre request 0.0'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + requestStatus: REQUEST_FAILURE, + requestReason, + }); + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); + const requestErrorMessage = vm.$el.querySelector( + '.js-cluster-application-request-error-message', + ); + + expect(generalErrorMessage.textContent.trim()).toEqual( + `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, + ); + + expect(requestErrorMessage.textContent.trim()).toEqual(requestReason); + }); + }); +}); diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js new file mode 100644 index 00000000000..7c54a27d950 --- /dev/null +++ b/spec/frontend/clusters/components/applications_spec.js @@ -0,0 +1,350 @@ +import Vue from 'vue'; +import applications from '~/clusters/components/applications.vue'; +import { CLUSTER_TYPE } from '~/clusters/constants'; +import eventHub from '~/clusters/event_hub'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { APPLICATIONS_MOCK_STATE } from '../services/mock_data'; + +describe('Applications', () => { + let vm; + let Applications; + + beforeEach(() => { + Applications = Vue.extend(applications); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('Project cluster applications', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + applications: APPLICATIONS_MOCK_STATE, + type: CLUSTER_TYPE.PROJECT, + }); + }); + + it('renders a row for Helm Tiller', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull(); + }); + + it('renders a row for Ingress', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull(); + }); + + it('renders a row for Cert-Manager', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); + }); + + it('renders a row for Prometheus', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull(); + }); + + it('renders a row for GitLab Runner', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); + }); + + it('renders a row for Jupyter', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull(); + }); + + it('renders a row for Knative', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull(); + }); + }); + + describe('Group cluster applications', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + type: CLUSTER_TYPE.GROUP, + applications: APPLICATIONS_MOCK_STATE, + }); + }); + + it('renders a row for Helm Tiller', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull(); + }); + + it('renders a row for Ingress', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull(); + }); + + it('renders a row for Cert-Manager', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); + }); + + it('renders a row for Prometheus', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeNull(); + }); + + it('renders a row for GitLab Runner', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); + }); + + it('renders a row for Jupyter', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull(); + }); + + it('renders a row for Knative', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull(); + }); + }); + + describe('Ingress application', () => { + describe('when installed', () => { + describe('with ip address', () => { + it('renders ip address with a clipboard button', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { + title: 'Ingress', + status: 'installed', + externalIp: '0.0.0.0', + }, + }, + }); + + expect(vm.$el.querySelector('.js-endpoint').value).toEqual('0.0.0.0'); + + expect( + vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), + ).toEqual('0.0.0.0'); + }); + }); + + describe('with hostname', () => { + it('renders hostname with a clipboard button', () => { + vm = mountComponent(Applications, { + applications: { + ingress: { + title: 'Ingress', + status: 'installed', + externalHostname: 'localhost.localdomain', + }, + helm: { title: 'Helm Tiller' }, + cert_manager: { title: 'Cert-Manager' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, + knative: { title: 'Knative', hostname: '' }, + }, + }); + + expect(vm.$el.querySelector('.js-endpoint').value).toEqual('localhost.localdomain'); + + expect( + vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), + ).toEqual('localhost.localdomain'); + }); + }); + + describe('without ip address', () => { + it('renders an input text with a loading icon and an alert text', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { + title: 'Ingress', + status: 'installed', + }, + }, + }); + + expect(vm.$el.querySelector('.js-ingress-ip-loading-icon')).not.toBe(null); + expect(vm.$el.querySelector('.js-no-endpoint-message')).not.toBe(null); + }); + }); + }); + + describe('before installing', () => { + it('does not render the IP address', () => { + vm = mountComponent(Applications, { + applications: APPLICATIONS_MOCK_STATE, + }); + + expect(vm.$el.textContent).not.toContain('Ingress IP Address'); + expect(vm.$el.querySelector('.js-endpoint')).toBe(null); + }); + }); + + describe('Cert-Manager application', () => { + describe('when not installed', () => { + it('renders email & allows editing', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + cert_manager: { + title: 'Cert-Manager', + email: 'before@example.com', + status: 'installable', + }, + }, + }); + + expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com'); + expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null); + }); + }); + + describe('when installed', () => { + it('renders email in readonly', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + cert_manager: { + title: 'Cert-Manager', + email: 'after@example.com', + status: 'installed', + }, + }, + }); + + expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com'); + expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly'); + }); + }); + }); + + describe('Jupyter application', () => { + describe('with ingress installed with ip & jupyter installable', () => { + it('renders hostname active input', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { + title: 'Ingress', + status: 'installed', + externalIp: '1.1.1.1', + }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null); + }); + }); + + describe('with ingress installed without external ip', () => { + it('does not render hostname input', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { title: 'Ingress', status: 'installed' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname')).toBe(null); + }); + }); + + describe('with ingress & jupyter installed', () => { + it('renders readonly input', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly'); + }); + }); + + describe('without ingress installed', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + applications: APPLICATIONS_MOCK_STATE, + }); + }); + + it('does not render input', () => { + expect(vm.$el.querySelector('.js-hostname')).toBe(null); + }); + + it('renders disabled install button', () => { + expect( + vm.$el + .querySelector( + '.js-cluster-application-row-jupyter .js-cluster-application-install-button', + ) + .getAttribute('disabled'), + ).toEqual('disabled'); + }); + }); + }); + }); + + describe('Knative application', () => { + describe('when installed', () => { + describe('with ip address', () => { + const props = { + applications: { + ...APPLICATIONS_MOCK_STATE, + knative: { + title: 'Knative', + hostname: 'example.com', + status: 'installed', + externalIp: '1.1.1.1', + }, + }, + }; + it('renders ip address with a clipboard button', () => { + vm = mountComponent(Applications, props); + + expect(vm.$el.querySelector('.js-knative-endpoint').value).toEqual('1.1.1.1'); + + expect( + vm.$el + .querySelector('.js-knative-endpoint-clipboard-btn') + .getAttribute('data-clipboard-text'), + ).toEqual('1.1.1.1'); + }); + + it('renders domain & allows editing', () => { + expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com'); + expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe( + null, + ); + }); + + it('renders an update/save Knative domain button', () => { + expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null); + }); + + it('emits event when clicking Save changes button', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(Applications, props); + + const saveButton = vm.$el.querySelector('.js-knative-save-domain-button'); + + saveButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { + id: 'knative', + params: { hostname: 'example.com' }, + }); + }); + }); + + describe('without ip address', () => { + it('renders an input text with a loading icon and an alert text', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + knative: { + title: 'Knative', + hostname: 'example.com', + status: 'installed', + }, + }, + }); + + expect(vm.$el.querySelector('.js-knative-ip-loading-icon')).not.toBe(null); + expect(vm.$el.querySelector('.js-no-knative-endpoint-message')).not.toBe(null); + }); + }); + }); + }); +}); diff --git a/spec/frontend/clusters/services/mock_data.js b/spec/frontend/clusters/services/mock_data.js new file mode 100644 index 00000000000..b4d1bb710e0 --- /dev/null +++ b/spec/frontend/clusters/services/mock_data.js @@ -0,0 +1,130 @@ +import { APPLICATION_STATUS } from '~/clusters/constants'; + +const CLUSTERS_MOCK_DATA = { + GET: { + '/gitlab-org/gitlab-shell/clusters/1/status.json': { + data: { + status: 'errored', + status_reason: 'Failed to request to CloudPlatform.', + applications: [ + { + name: 'helm', + status: APPLICATION_STATUS.INSTALLABLE, + status_reason: null, + }, + { + name: 'ingress', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + external_ip: null, + external_hostname: null, + }, + { + name: 'runner', + status: APPLICATION_STATUS.INSTALLING, + status_reason: null, + }, + { + name: 'prometheus', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + }, + { + name: 'jupyter', + status: APPLICATION_STATUS.INSTALLING, + status_reason: 'Cannot connect', + }, + { + name: 'knative', + status: APPLICATION_STATUS.INSTALLING, + status_reason: 'Cannot connect', + }, + { + name: 'cert_manager', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + email: 'test@example.com', + }, + ], + }, + }, + '/gitlab-org/gitlab-shell/clusters/2/status.json': { + data: { + status: 'errored', + status_reason: 'Failed to request to CloudPlatform.', + applications: [ + { + name: 'helm', + status: APPLICATION_STATUS.INSTALLED, + status_reason: null, + }, + { + name: 'ingress', + status: APPLICATION_STATUS.INSTALLED, + status_reason: 'Cannot connect', + external_ip: '1.1.1.1', + external_hostname: null, + }, + { + name: 'runner', + status: APPLICATION_STATUS.INSTALLING, + status_reason: null, + }, + { + name: 'prometheus', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + }, + { + name: 'jupyter', + status: APPLICATION_STATUS.INSTALLABLE, + status_reason: 'Cannot connect', + }, + { + name: 'knative', + status: APPLICATION_STATUS.INSTALLABLE, + status_reason: 'Cannot connect', + }, + { + name: 'cert_manager', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + email: 'test@example.com', + }, + ], + }, + }, + }, + POST: { + '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/knative': {}, + }, +}; + +const DEFAULT_APPLICATION_STATE = { + id: 'some-app', + title: 'My App', + titleLink: 'https://about.gitlab.com/', + description: 'Some description about this interesting application!', + status: null, + statusReason: null, + requestStatus: null, + requestReason: null, +}; + +const APPLICATIONS_MOCK_STATE = { + helm: { title: 'Helm Tiller', status: 'installable' }, + ingress: { title: 'Ingress', status: 'installable' }, + cert_manager: { title: 'Cert-Manager', status: 'installable' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' }, + knative: { title: 'Knative ', status: 'installable', hostname: '' }, +}; + +export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE }; diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js new file mode 100644 index 00000000000..161722ec571 --- /dev/null +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -0,0 +1,142 @@ +import ClustersStore from '~/clusters/stores/clusters_store'; +import { APPLICATION_STATUS } from '~/clusters/constants'; +import { CLUSTERS_MOCK_DATA } from '../services/mock_data'; + +describe('Clusters Store', () => { + let store; + + beforeEach(() => { + store = new ClustersStore(); + }); + + describe('updateStatus', () => { + it('should store new status', () => { + expect(store.state.status).toEqual(null); + + const newStatus = 'errored'; + store.updateStatus(newStatus); + + expect(store.state.status).toEqual(newStatus); + }); + }); + + describe('updateStatusReason', () => { + it('should store new reason', () => { + expect(store.state.statusReason).toEqual(null); + + const newReason = 'Something went wrong!'; + store.updateStatusReason(newReason); + + expect(store.state.statusReason).toEqual(newReason); + }); + }); + + describe('updateAppProperty', () => { + it('should store new request status', () => { + expect(store.state.applications.helm.requestStatus).toEqual(null); + + const newStatus = APPLICATION_STATUS.INSTALLING; + store.updateAppProperty('helm', 'requestStatus', newStatus); + + expect(store.state.applications.helm.requestStatus).toEqual(newStatus); + }); + + it('should store new request reason', () => { + expect(store.state.applications.helm.requestReason).toEqual(null); + + const newReason = 'We broke it.'; + store.updateAppProperty('helm', 'requestReason', newReason); + + expect(store.state.applications.helm.requestReason).toEqual(newReason); + }); + }); + + describe('updateStateFromServer', () => { + it('should store new polling data from server', () => { + const mockResponseData = + CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data; + store.updateStateFromServer(mockResponseData); + + expect(store.state).toEqual({ + helpPath: null, + ingressHelpPath: null, + status: mockResponseData.status, + statusReason: mockResponseData.status_reason, + rbac: false, + applications: { + helm: { + title: 'Helm Tiller', + status: mockResponseData.applications[0].status, + statusReason: mockResponseData.applications[0].status_reason, + requestStatus: null, + requestReason: null, + }, + ingress: { + title: 'Ingress', + status: mockResponseData.applications[1].status, + statusReason: mockResponseData.applications[1].status_reason, + requestStatus: null, + requestReason: null, + externalIp: null, + externalHostname: null, + }, + runner: { + title: 'GitLab Runner', + status: mockResponseData.applications[2].status, + statusReason: mockResponseData.applications[2].status_reason, + requestStatus: null, + requestReason: null, + version: mockResponseData.applications[2].version, + upgradeAvailable: mockResponseData.applications[2].update_available, + chartRepo: 'https://gitlab.com/charts/gitlab-runner', + }, + prometheus: { + title: 'Prometheus', + status: mockResponseData.applications[3].status, + statusReason: mockResponseData.applications[3].status_reason, + requestStatus: null, + requestReason: null, + }, + jupyter: { + title: 'JupyterHub', + status: mockResponseData.applications[4].status, + statusReason: mockResponseData.applications[4].status_reason, + requestStatus: null, + requestReason: null, + hostname: '', + }, + knative: { + title: 'Knative', + status: mockResponseData.applications[5].status, + statusReason: mockResponseData.applications[5].status_reason, + requestStatus: null, + requestReason: null, + hostname: null, + isEditingHostName: false, + externalIp: null, + externalHostname: null, + }, + cert_manager: { + title: 'Cert-Manager', + status: mockResponseData.applications[6].status, + statusReason: mockResponseData.applications[6].status_reason, + requestStatus: null, + requestReason: null, + email: mockResponseData.applications[6].email, + }, + }, + }); + }); + + it('sets default hostname for jupyter when ingress has a ip address', () => { + const mockResponseData = + CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data; + + store.updateStateFromServer(mockResponseData); + + expect(store.state.applications.jupyter.hostname).toEqual( + `jupyter.${store.state.applications.ingress.externalIp}.nip.io`, + ); + }); + }); +}); diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js deleted file mode 100644 index 0a98df45b5d..00000000000 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ /dev/null @@ -1,360 +0,0 @@ -import Clusters from '~/clusters/clusters_bundle'; -import { - REQUEST_SUBMITTED, - REQUEST_FAILURE, - APPLICATION_STATUS, - INGRESS_DOMAIN_SUFFIX, -} from '~/clusters/constants'; -import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; - -describe('Clusters', () => { - let cluster; - preloadFixtures('clusters/show_cluster.html'); - - beforeEach(() => { - loadFixtures('clusters/show_cluster.html'); - cluster = new Clusters(); - }); - - afterEach(() => { - cluster.destroy(); - }); - - describe('toggle', () => { - it('should update the button and the input field on click', done => { - const toggleButton = document.querySelector( - '.js-cluster-enable-toggle-area .js-project-feature-toggle', - ); - const toggleInput = document.querySelector( - '.js-cluster-enable-toggle-area .js-project-feature-toggle-input', - ); - - toggleButton.click(); - - getSetTimeoutPromise() - .then(() => { - expect(toggleButton.classList).not.toContain('is-checked'); - - expect(toggleInput.getAttribute('value')).toEqual('false'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('showToken', () => { - it('should update token field type', () => { - cluster.showTokenButton.click(); - - expect(cluster.tokenField.getAttribute('type')).toEqual('text'); - - cluster.showTokenButton.click(); - - expect(cluster.tokenField.getAttribute('type')).toEqual('password'); - }); - - it('should update show token button text', () => { - cluster.showTokenButton.click(); - - expect(cluster.showTokenButton.textContent).toEqual('Hide'); - - cluster.showTokenButton.click(); - - expect(cluster.showTokenButton.textContent).toEqual('Show'); - }); - }); - - describe('checkForNewInstalls', () => { - const INITIAL_APP_MAP = { - helm: { status: null, title: 'Helm Tiller' }, - ingress: { status: null, title: 'Ingress' }, - runner: { status: null, title: 'GitLab Runner' }, - }; - - it('does not show alert when things transition from initial null state to something', () => { - cluster.checkForNewInstalls(INITIAL_APP_MAP, { - ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' }, - }); - - const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); - - expect(flashMessage).toBeNull(); - }); - - it('shows an alert when something gets newly installed', () => { - cluster.checkForNewInstalls( - { - ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, - }, - { - ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, - }, - ); - - const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); - - expect(flashMessage).not.toBeNull(); - expect(flashMessage.textContent.trim()).toEqual( - 'Helm Tiller was successfully installed on your Kubernetes cluster', - ); - }); - - it('shows an alert when multiple things gets newly installed', () => { - cluster.checkForNewInstalls( - { - ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, - ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' }, - }, - { - ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, - ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' }, - }, - ); - - const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); - - expect(flashMessage).not.toBeNull(); - expect(flashMessage.textContent.trim()).toEqual( - 'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster', - ); - }); - }); - - describe('updateContainer', () => { - describe('when creating cluster', () => { - it('should show the creating container', () => { - cluster.updateContainer(null, 'creating'); - - expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); - - expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); - }); - - it('should continue to show `creating` banner with subsequent updates of the same status', () => { - cluster.updateContainer('creating', 'creating'); - - expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); - - expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); - }); - }); - - describe('when cluster is created', () => { - it('should show the success container and fresh the page', () => { - cluster.updateContainer(null, 'created'); - - expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy(); - - expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); - }); - - it('should not show a banner when status is already `created`', () => { - cluster.updateContainer('created', 'created'); - - expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); - }); - }); - - describe('when cluster has error', () => { - it('should show the error container', () => { - cluster.updateContainer(null, 'errored', 'this is an error'); - - expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy(); - - expect(cluster.errorReasonContainer.textContent).toContain('this is an error'); - }); - - it('should show `error` banner when previously `creating`', () => { - cluster.updateContainer('creating', 'errored'); - - expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); - - expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy(); - }); - }); - }); - - describe('installApplication', () => { - it('tries to install helm', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); - - expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); - - cluster.installApplication({ id: 'helm' }); - - expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); - expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined); - }); - - it('tries to install ingress', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); - - expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null); - - cluster.installApplication({ id: 'ingress' }); - - expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_SUBMITTED); - expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); - }); - - it('tries to install runner', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); - - expect(cluster.store.state.applications.runner.requestStatus).toEqual(null); - - cluster.installApplication({ id: 'runner' }); - - expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_SUBMITTED); - expect(cluster.store.state.applications.runner.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); - }); - - it('tries to install jupyter', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); - - expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null); - cluster.installApplication({ - id: 'jupyter', - params: { hostname: cluster.store.state.applications.jupyter.hostname }, - }); - - expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUBMITTED); - expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { - hostname: cluster.store.state.applications.jupyter.hostname, - }); - }); - - it('sets error request status when the request fails', done => { - spyOn(cluster.service, 'installApplication').and.returnValue( - Promise.reject(new Error('STUBBED ERROR')), - ); - - expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); - - cluster.installApplication({ id: 'helm' }); - - expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); - expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalled(); - - getSetTimeoutPromise() - .then(() => { - expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE); - expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('handleSuccess', () => { - beforeEach(() => { - spyOn(cluster.store, 'updateStateFromServer'); - spyOn(cluster, 'toggleIngressDomainHelpText'); - spyOn(cluster, 'checkForNewInstalls'); - spyOn(cluster, 'updateContainer'); - - cluster.handleSuccess({ data: {} }); - }); - - it('updates clusters store', () => { - expect(cluster.store.updateStateFromServer).toHaveBeenCalled(); - }); - - it('checks for new installable apps', () => { - expect(cluster.checkForNewInstalls).toHaveBeenCalled(); - }); - - it('toggles ingress domain help text', () => { - expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled(); - }); - - it('updates message containers', () => { - expect(cluster.updateContainer).toHaveBeenCalled(); - }); - }); - - describe('toggleIngressDomainHelpText', () => { - const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS; - let ingressPreviousState; - let ingressNewState; - - beforeEach(() => { - ingressPreviousState = { status: INSTALLABLE }; - ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; - }); - - describe(`when ingress application new status is ${INSTALLED}`, () => { - beforeEach(() => { - ingressNewState.status = INSTALLED; - cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); - }); - - it('displays custom domain help text', () => { - expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false); - }); - - it('updates ingress external ip address', () => { - expect(cluster.ingressDomainSnippet.textContent).toEqual( - `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`, - ); - }); - }); - - describe(`when ingress application new status is different from ${INSTALLED}`, () => { - it('hides custom domain help text', () => { - ingressNewState.status = NOT_INSTALLABLE; - cluster.ingressDomainHelpText.classList.remove('hide'); - - cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); - - expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); - }); - }); - - describe('when ingress application new status and old status are the same', () => { - it('does not display custom domain help text', () => { - ingressPreviousState.status = INSTALLED; - ingressNewState.status = ingressPreviousState.status; - - cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); - - expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); - }); - }); - - describe(`when ingress new status is ${INSTALLED} and there isn’t an ip assigned`, () => { - it('does not display custom domain help text', () => { - ingressNewState.externalIp = null; - - cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); - - expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); - }); - }); - }); -}); diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js deleted file mode 100644 index a2dd4e93daf..00000000000 --- a/spec/javascripts/clusters/components/application_row_spec.js +++ /dev/null @@ -1,406 +0,0 @@ -import Vue from 'vue'; -import eventHub from '~/clusters/event_hub'; -import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '~/clusters/constants'; -import applicationRow from '~/clusters/components/application_row.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { DEFAULT_APPLICATION_STATE } from '../services/mock_data'; - -describe('Application Row', () => { - let vm; - let ApplicationRow; - - beforeEach(() => { - ApplicationRow = Vue.extend(applicationRow); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('Title', () => { - it('shows title', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - titleLink: null, - }); - const title = vm.$el.querySelector('.js-cluster-application-title'); - - expect(title.tagName).toEqual('SPAN'); - expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title); - }); - - it('shows title link', () => { - expect(DEFAULT_APPLICATION_STATE.titleLink).toBeDefined(); - - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - }); - const title = vm.$el.querySelector('.js-cluster-application-title'); - - expect(title.tagName).toEqual('A'); - expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title); - }); - }); - - describe('Install button', () => { - it('has indeterminate state on page load', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: null, - }); - - expect(vm.installButtonLabel).toBeUndefined(); - }); - - it('has install button', () => { - const installationBtn = vm.$el.querySelector('.js-cluster-application-install-button'); - - expect(installationBtn).not.toBe(null); - }); - - it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.NOT_INSTALLABLE, - }); - - expect(vm.installButtonLabel).toEqual('Install'); - expect(vm.installButtonLoading).toEqual(false); - expect(vm.installButtonDisabled).toEqual(true); - }); - - it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - }); - - expect(vm.installButtonLabel).toEqual('Install'); - expect(vm.installButtonLoading).toEqual(false); - expect(vm.installButtonDisabled).toEqual(false); - }); - - it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.SCHEDULED, - }); - - expect(vm.installButtonLabel).toEqual('Installing'); - expect(vm.installButtonLoading).toEqual(true); - expect(vm.installButtonDisabled).toEqual(true); - }); - - it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLING, - }); - - expect(vm.installButtonLabel).toEqual('Installing'); - expect(vm.installButtonLoading).toEqual(true); - expect(vm.installButtonDisabled).toEqual(true); - }); - - it('has loading "Installing" when REQUEST_SUBMITTED', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - requestStatus: REQUEST_SUBMITTED, - }); - - expect(vm.installButtonLabel).toEqual('Installing'); - expect(vm.installButtonLoading).toEqual(true); - expect(vm.installButtonDisabled).toEqual(true); - }); - - it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLED, - }); - - expect(vm.installButtonLabel).toEqual('Installed'); - expect(vm.installButtonLoading).toEqual(false); - expect(vm.installButtonDisabled).toEqual(true); - }); - - it('has disabled "Installed" when APPLICATION_STATUS.UPDATING', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATING, - }); - - expect(vm.installButtonLabel).toEqual('Installed'); - expect(vm.installButtonLoading).toEqual(false); - expect(vm.installButtonDisabled).toEqual(true); - }); - - it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.ERROR, - }); - - expect(vm.installButtonLabel).toEqual('Install'); - expect(vm.installButtonLoading).toEqual(false); - expect(vm.installButtonDisabled).toEqual(false); - }); - - it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - requestStatus: REQUEST_FAILURE, - }); - - expect(vm.installButtonLabel).toEqual('Install'); - expect(vm.installButtonLoading).toEqual(false); - expect(vm.installButtonDisabled).toEqual(false); - }); - - it('clicking install button emits event', () => { - spyOn(eventHub, '$emit'); - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - }); - const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); - - installButton.click(); - - expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { - id: DEFAULT_APPLICATION_STATE.id, - params: {}, - }); - }); - - it('clicking install button when installApplicationRequestParams are provided emits event', () => { - spyOn(eventHub, '$emit'); - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - installApplicationRequestParams: { hostname: 'jupyter' }, - }); - const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); - - installButton.click(); - - expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { - id: DEFAULT_APPLICATION_STATE.id, - params: { hostname: 'jupyter' }, - }); - }); - - it('clicking disabled install button emits nothing', () => { - spyOn(eventHub, '$emit'); - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLING, - }); - const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); - - expect(vm.installButtonDisabled).toEqual(true); - - installButton.click(); - - expect(eventHub.$emit).not.toHaveBeenCalled(); - }); - }); - - describe('Upgrade button', () => { - it('has indeterminate state on page load', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: null, - }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); - - expect(upgradeBtn).toBe(null); - }); - - it('has enabled "Upgrade" when "upgradeAvailable" is true', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - upgradeAvailable: true, - }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); - - expect(upgradeBtn).not.toBe(null); - expect(upgradeBtn.innerHTML).toContain('Upgrade'); - }); - - it('has enabled "Retry update" when APPLICATION_STATUS.UPDATE_ERRORED', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATE_ERRORED, - }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); - - expect(upgradeBtn).not.toBe(null); - expect(vm.upgradeFailed).toBe(true); - expect(upgradeBtn.innerHTML).toContain('Retry update'); - }); - - it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATING, - }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); - - expect(upgradeBtn).not.toBe(null); - expect(vm.isUpgrading).toBe(true); - expect(upgradeBtn.innerHTML).toContain('Updating'); - }); - - it('clicking upgrade button emits event', () => { - spyOn(eventHub, '$emit'); - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATE_ERRORED, - }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); - - upgradeBtn.click(); - - expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', { - id: DEFAULT_APPLICATION_STATE.id, - params: {}, - }); - }); - - it('clicking disabled upgrade button emits nothing', () => { - spyOn(eventHub, '$emit'); - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATING, - }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); - - upgradeBtn.click(); - - expect(eventHub.$emit).not.toHaveBeenCalled(); - }); - - it('displays an error message if application upgrade failed', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - title: 'GitLab Runner', - status: APPLICATION_STATUS.UPDATE_ERRORED, - }); - const failureMessage = vm.$el.querySelector( - '.js-cluster-application-upgrade-failure-message', - ); - - expect(failureMessage).not.toBe(null); - expect(failureMessage.innerHTML).toContain( - 'Update failed. Please check the logs and try again.', - ); - }); - }); - - describe('Version', () => { - it('displays a version number if application has been upgraded', () => { - const version = '0.1.45'; - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATED, - version, - }); - const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); - - expect(upgradeDetails.innerHTML).toContain('Upgraded'); - expect(versionEl).not.toBe(null); - expect(versionEl.innerHTML).toContain(version); - }); - - it('contains a link to the chart repo if application has been upgraded', () => { - const version = '0.1.45'; - const chartRepo = 'https://gitlab.com/charts/gitlab-runner'; - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATED, - chartRepo, - version, - }); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); - - expect(versionEl.href).toEqual(chartRepo); - expect(versionEl.target).toEqual('_blank'); - }); - - it('does not display a version number if application upgrade failed', () => { - const version = '0.1.45'; - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATE_ERRORED, - version, - }); - const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); - - expect(upgradeDetails.innerHTML).toContain('failed'); - expect(versionEl).toBe(null); - }); - }); - - describe('Error block', () => { - it('does not show error block when there is no error', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: null, - requestStatus: null, - }); - const generalErrorMessage = vm.$el.querySelector( - '.js-cluster-application-general-error-message', - ); - - expect(generalErrorMessage).toBeNull(); - }); - - it('shows status reason when APPLICATION_STATUS.ERROR', () => { - const statusReason = 'We broke it 0.0'; - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.ERROR, - statusReason, - }); - const generalErrorMessage = vm.$el.querySelector( - '.js-cluster-application-general-error-message', - ); - const statusErrorMessage = vm.$el.querySelector( - '.js-cluster-application-status-error-message', - ); - - expect(generalErrorMessage.textContent.trim()).toEqual( - `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, - ); - - expect(statusErrorMessage.textContent.trim()).toEqual(statusReason); - }); - - it('shows request reason when REQUEST_FAILURE', () => { - const requestReason = 'We broke thre request 0.0'; - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - requestStatus: REQUEST_FAILURE, - requestReason, - }); - const generalErrorMessage = vm.$el.querySelector( - '.js-cluster-application-general-error-message', - ); - const requestErrorMessage = vm.$el.querySelector( - '.js-cluster-application-request-error-message', - ); - - expect(generalErrorMessage.textContent.trim()).toEqual( - `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, - ); - - expect(requestErrorMessage.textContent.trim()).toEqual(requestReason); - }); - }); -}); diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js deleted file mode 100644 index 0f8153ad493..00000000000 --- a/spec/javascripts/clusters/components/applications_spec.js +++ /dev/null @@ -1,350 +0,0 @@ -import Vue from 'vue'; -import applications from '~/clusters/components/applications.vue'; -import { CLUSTER_TYPE } from '~/clusters/constants'; -import eventHub from '~/clusters/event_hub'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { APPLICATIONS_MOCK_STATE } from '../services/mock_data'; - -describe('Applications', () => { - let vm; - let Applications; - - beforeEach(() => { - Applications = Vue.extend(applications); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('Project cluster applications', () => { - beforeEach(() => { - vm = mountComponent(Applications, { - applications: APPLICATIONS_MOCK_STATE, - type: CLUSTER_TYPE.PROJECT, - }); - }); - - it('renders a row for Helm Tiller', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull(); - }); - - it('renders a row for Ingress', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull(); - }); - - it('renders a row for Cert-Manager', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); - }); - - it('renders a row for Prometheus', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull(); - }); - - it('renders a row for GitLab Runner', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); - }); - - it('renders a row for Jupyter', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull(); - }); - - it('renders a row for Knative', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull(); - }); - }); - - describe('Group cluster applications', () => { - beforeEach(() => { - vm = mountComponent(Applications, { - type: CLUSTER_TYPE.GROUP, - applications: APPLICATIONS_MOCK_STATE, - }); - }); - - it('renders a row for Helm Tiller', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull(); - }); - - it('renders a row for Ingress', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull(); - }); - - it('renders a row for Cert-Manager', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); - }); - - it('renders a row for Prometheus', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeNull(); - }); - - it('renders a row for GitLab Runner', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); - }); - - it('renders a row for Jupyter', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull(); - }); - - it('renders a row for Knative', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull(); - }); - }); - - describe('Ingress application', () => { - describe('when installed', () => { - describe('with ip address', () => { - it('renders ip address with a clipboard button', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - ingress: { - title: 'Ingress', - status: 'installed', - externalIp: '0.0.0.0', - }, - }, - }); - - expect(vm.$el.querySelector('.js-endpoint').value).toEqual('0.0.0.0'); - - expect( - vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), - ).toEqual('0.0.0.0'); - }); - }); - - describe('with hostname', () => { - it('renders hostname with a clipboard button', () => { - vm = mountComponent(Applications, { - applications: { - ingress: { - title: 'Ingress', - status: 'installed', - externalHostname: 'localhost.localdomain', - }, - helm: { title: 'Helm Tiller' }, - cert_manager: { title: 'Cert-Manager' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', hostname: '' }, - knative: { title: 'Knative', hostname: '' }, - }, - }); - - expect(vm.$el.querySelector('.js-endpoint').value).toEqual('localhost.localdomain'); - - expect( - vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), - ).toEqual('localhost.localdomain'); - }); - }); - - describe('without ip address', () => { - it('renders an input text with a loading icon and an alert text', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - ingress: { - title: 'Ingress', - status: 'installed', - }, - }, - }); - - expect(vm.$el.querySelector('.js-ingress-ip-loading-icon')).not.toBe(null); - expect(vm.$el.querySelector('.js-no-endpoint-message')).not.toBe(null); - }); - }); - }); - - describe('before installing', () => { - it('does not render the IP address', () => { - vm = mountComponent(Applications, { - applications: APPLICATIONS_MOCK_STATE, - }); - - expect(vm.$el.textContent).not.toContain('Ingress IP Address'); - expect(vm.$el.querySelector('.js-endpoint')).toBe(null); - }); - }); - - describe('Cert-Manager application', () => { - describe('when not installed', () => { - it('renders email & allows editing', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - cert_manager: { - title: 'Cert-Manager', - email: 'before@example.com', - status: 'installable', - }, - }, - }); - - expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com'); - expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null); - }); - }); - - describe('when installed', () => { - it('renders email in readonly', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - cert_manager: { - title: 'Cert-Manager', - email: 'after@example.com', - status: 'installed', - }, - }, - }); - - expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com'); - expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly'); - }); - }); - }); - - describe('Jupyter application', () => { - describe('with ingress installed with ip & jupyter installable', () => { - it('renders hostname active input', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - ingress: { - title: 'Ingress', - status: 'installed', - externalIp: '1.1.1.1', - }, - }, - }); - - expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null); - }); - }); - - describe('with ingress installed without external ip', () => { - it('does not render hostname input', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - ingress: { title: 'Ingress', status: 'installed' }, - }, - }); - - expect(vm.$el.querySelector('.js-hostname')).toBe(null); - }); - }); - - describe('with ingress & jupyter installed', () => { - it('renders readonly input', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, - jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' }, - }, - }); - - expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly'); - }); - }); - - describe('without ingress installed', () => { - beforeEach(() => { - vm = mountComponent(Applications, { - applications: APPLICATIONS_MOCK_STATE, - }); - }); - - it('does not render input', () => { - expect(vm.$el.querySelector('.js-hostname')).toBe(null); - }); - - it('renders disabled install button', () => { - expect( - vm.$el - .querySelector( - '.js-cluster-application-row-jupyter .js-cluster-application-install-button', - ) - .getAttribute('disabled'), - ).toEqual('disabled'); - }); - }); - }); - }); - - describe('Knative application', () => { - describe('when installed', () => { - describe('with ip address', () => { - const props = { - applications: { - ...APPLICATIONS_MOCK_STATE, - knative: { - title: 'Knative', - hostname: 'example.com', - status: 'installed', - externalIp: '1.1.1.1', - }, - }, - }; - it('renders ip address with a clipboard button', () => { - vm = mountComponent(Applications, props); - - expect(vm.$el.querySelector('.js-knative-endpoint').value).toEqual('1.1.1.1'); - - expect( - vm.$el - .querySelector('.js-knative-endpoint-clipboard-btn') - .getAttribute('data-clipboard-text'), - ).toEqual('1.1.1.1'); - }); - - it('renders domain & allows editing', () => { - expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com'); - expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe( - null, - ); - }); - - it('renders an update/save Knative domain button', () => { - expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null); - }); - - it('emits event when clicking Save changes button', () => { - spyOn(eventHub, '$emit'); - vm = mountComponent(Applications, props); - - const saveButton = vm.$el.querySelector('.js-knative-save-domain-button'); - - saveButton.click(); - - expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { - id: 'knative', - params: { hostname: 'example.com' }, - }); - }); - }); - - describe('without ip address', () => { - it('renders an input text with a loading icon and an alert text', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - knative: { - title: 'Knative', - hostname: 'example.com', - status: 'installed', - }, - }, - }); - - expect(vm.$el.querySelector('.js-knative-ip-loading-icon')).not.toBe(null); - expect(vm.$el.querySelector('.js-no-knative-endpoint-message')).not.toBe(null); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js deleted file mode 100644 index b4d1bb710e0..00000000000 --- a/spec/javascripts/clusters/services/mock_data.js +++ /dev/null @@ -1,130 +0,0 @@ -import { APPLICATION_STATUS } from '~/clusters/constants'; - -const CLUSTERS_MOCK_DATA = { - GET: { - '/gitlab-org/gitlab-shell/clusters/1/status.json': { - data: { - status: 'errored', - status_reason: 'Failed to request to CloudPlatform.', - applications: [ - { - name: 'helm', - status: APPLICATION_STATUS.INSTALLABLE, - status_reason: null, - }, - { - name: 'ingress', - status: APPLICATION_STATUS.ERROR, - status_reason: 'Cannot connect', - external_ip: null, - external_hostname: null, - }, - { - name: 'runner', - status: APPLICATION_STATUS.INSTALLING, - status_reason: null, - }, - { - name: 'prometheus', - status: APPLICATION_STATUS.ERROR, - status_reason: 'Cannot connect', - }, - { - name: 'jupyter', - status: APPLICATION_STATUS.INSTALLING, - status_reason: 'Cannot connect', - }, - { - name: 'knative', - status: APPLICATION_STATUS.INSTALLING, - status_reason: 'Cannot connect', - }, - { - name: 'cert_manager', - status: APPLICATION_STATUS.ERROR, - status_reason: 'Cannot connect', - email: 'test@example.com', - }, - ], - }, - }, - '/gitlab-org/gitlab-shell/clusters/2/status.json': { - data: { - status: 'errored', - status_reason: 'Failed to request to CloudPlatform.', - applications: [ - { - name: 'helm', - status: APPLICATION_STATUS.INSTALLED, - status_reason: null, - }, - { - name: 'ingress', - status: APPLICATION_STATUS.INSTALLED, - status_reason: 'Cannot connect', - external_ip: '1.1.1.1', - external_hostname: null, - }, - { - name: 'runner', - status: APPLICATION_STATUS.INSTALLING, - status_reason: null, - }, - { - name: 'prometheus', - status: APPLICATION_STATUS.ERROR, - status_reason: 'Cannot connect', - }, - { - name: 'jupyter', - status: APPLICATION_STATUS.INSTALLABLE, - status_reason: 'Cannot connect', - }, - { - name: 'knative', - status: APPLICATION_STATUS.INSTALLABLE, - status_reason: 'Cannot connect', - }, - { - name: 'cert_manager', - status: APPLICATION_STATUS.ERROR, - status_reason: 'Cannot connect', - email: 'test@example.com', - }, - ], - }, - }, - }, - POST: { - '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {}, - '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {}, - '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {}, - '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {}, - '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {}, - '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {}, - '/gitlab-org/gitlab-shell/clusters/1/applications/knative': {}, - }, -}; - -const DEFAULT_APPLICATION_STATE = { - id: 'some-app', - title: 'My App', - titleLink: 'https://about.gitlab.com/', - description: 'Some description about this interesting application!', - status: null, - statusReason: null, - requestStatus: null, - requestReason: null, -}; - -const APPLICATIONS_MOCK_STATE = { - helm: { title: 'Helm Tiller', status: 'installable' }, - ingress: { title: 'Ingress', status: 'installable' }, - cert_manager: { title: 'Cert-Manager', status: 'installable' }, - runner: { title: 'GitLab Runner' }, - prometheus: { title: 'Prometheus' }, - jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' }, - knative: { title: 'Knative ', status: 'installable', hostname: '' }, -}; - -export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE }; diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js deleted file mode 100644 index 161722ec571..00000000000 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ /dev/null @@ -1,142 +0,0 @@ -import ClustersStore from '~/clusters/stores/clusters_store'; -import { APPLICATION_STATUS } from '~/clusters/constants'; -import { CLUSTERS_MOCK_DATA } from '../services/mock_data'; - -describe('Clusters Store', () => { - let store; - - beforeEach(() => { - store = new ClustersStore(); - }); - - describe('updateStatus', () => { - it('should store new status', () => { - expect(store.state.status).toEqual(null); - - const newStatus = 'errored'; - store.updateStatus(newStatus); - - expect(store.state.status).toEqual(newStatus); - }); - }); - - describe('updateStatusReason', () => { - it('should store new reason', () => { - expect(store.state.statusReason).toEqual(null); - - const newReason = 'Something went wrong!'; - store.updateStatusReason(newReason); - - expect(store.state.statusReason).toEqual(newReason); - }); - }); - - describe('updateAppProperty', () => { - it('should store new request status', () => { - expect(store.state.applications.helm.requestStatus).toEqual(null); - - const newStatus = APPLICATION_STATUS.INSTALLING; - store.updateAppProperty('helm', 'requestStatus', newStatus); - - expect(store.state.applications.helm.requestStatus).toEqual(newStatus); - }); - - it('should store new request reason', () => { - expect(store.state.applications.helm.requestReason).toEqual(null); - - const newReason = 'We broke it.'; - store.updateAppProperty('helm', 'requestReason', newReason); - - expect(store.state.applications.helm.requestReason).toEqual(newReason); - }); - }); - - describe('updateStateFromServer', () => { - it('should store new polling data from server', () => { - const mockResponseData = - CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data; - store.updateStateFromServer(mockResponseData); - - expect(store.state).toEqual({ - helpPath: null, - ingressHelpPath: null, - status: mockResponseData.status, - statusReason: mockResponseData.status_reason, - rbac: false, - applications: { - helm: { - title: 'Helm Tiller', - status: mockResponseData.applications[0].status, - statusReason: mockResponseData.applications[0].status_reason, - requestStatus: null, - requestReason: null, - }, - ingress: { - title: 'Ingress', - status: mockResponseData.applications[1].status, - statusReason: mockResponseData.applications[1].status_reason, - requestStatus: null, - requestReason: null, - externalIp: null, - externalHostname: null, - }, - runner: { - title: 'GitLab Runner', - status: mockResponseData.applications[2].status, - statusReason: mockResponseData.applications[2].status_reason, - requestStatus: null, - requestReason: null, - version: mockResponseData.applications[2].version, - upgradeAvailable: mockResponseData.applications[2].update_available, - chartRepo: 'https://gitlab.com/charts/gitlab-runner', - }, - prometheus: { - title: 'Prometheus', - status: mockResponseData.applications[3].status, - statusReason: mockResponseData.applications[3].status_reason, - requestStatus: null, - requestReason: null, - }, - jupyter: { - title: 'JupyterHub', - status: mockResponseData.applications[4].status, - statusReason: mockResponseData.applications[4].status_reason, - requestStatus: null, - requestReason: null, - hostname: '', - }, - knative: { - title: 'Knative', - status: mockResponseData.applications[5].status, - statusReason: mockResponseData.applications[5].status_reason, - requestStatus: null, - requestReason: null, - hostname: null, - isEditingHostName: false, - externalIp: null, - externalHostname: null, - }, - cert_manager: { - title: 'Cert-Manager', - status: mockResponseData.applications[6].status, - statusReason: mockResponseData.applications[6].status_reason, - requestStatus: null, - requestReason: null, - email: mockResponseData.applications[6].email, - }, - }, - }); - }); - - it('sets default hostname for jupyter when ingress has a ip address', () => { - const mockResponseData = - CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data; - - store.updateStateFromServer(mockResponseData); - - expect(store.state.applications.jupyter.hostname).toEqual( - `jupyter.${store.state.applications.ingress.externalIp}.nip.io`, - ); - }); - }); -}); -- cgit v1.2.1 From 4e95aa3de59220d32211f608f66400a1f067d77a Mon Sep 17 00:00:00 2001 From: charlieablett Date: Mon, 8 Apr 2019 19:38:34 +1200 Subject: Add helper reference --- spec/features/boards/sidebar_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index d036699f226..b358c6b9c34 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' describe 'Issue Boards', :js do include BoardHelpers + include FilteredSearchHelpers let(:user) { create(:user) } let(:user2) { create(:user) } -- cgit v1.2.1 From 6cb58cdb0aed3c9825d4a9a9f50daa9a305f4e11 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 13:49:43 +0300 Subject: Extract create merge request quick action spec --- .../issues/user_uses_quick_actions_spec.rb | 61 +--------------------- ...e_merge_request_quick_action_shared_examples.rb | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 59 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 9938a4e781c..ea474759547 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -42,7 +42,7 @@ describe 'Issues > User uses quick actions', :js do describe 'issue-only commands' do let(:user) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } before do @@ -59,6 +59,7 @@ describe 'Issues > User uses quick actions', :js do it_behaves_like 'confidential quick action' it_behaves_like 'remove_due_date quick action' it_behaves_like 'duplicate quick action' + it_behaves_like 'create_merge_request quick action' describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } @@ -207,63 +208,5 @@ describe 'Issues > User uses quick actions', :js do end end end - - describe 'create a merge request starting from an issue' do - let(:project) { create(:project, :public, :repository) } - let(:issue) { create(:issue, project: project) } - - def expect_mr_quickaction(success) - expect(page).to have_content 'Commands applied' - - if success - expect(page).to have_content 'created merge request' - else - expect(page).not_to have_content 'created merge request' - end - end - - it "doesn't create a merge request when the branch name is invalid" do - add_note("/create_merge_request invalid branch name") - - wait_for_requests - - expect_mr_quickaction(false) - end - - it "doesn't create a merge request when a branch with that name already exists" do - add_note("/create_merge_request feature") - - wait_for_requests - - expect_mr_quickaction(false) - end - - it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do - add_note("/create_merge_request") - - wait_for_requests - - expect_mr_quickaction(true) - - created_mr = project.merge_requests.last - expect(created_mr.source_branch).to eq(issue.to_branch_name) - - visit project_merge_request_path(project, created_mr) - expect(page).to have_content %{WIP: Resolve "#{issue.title}"} - end - - it 'creates a merge request using the given branch name' do - branch_name = '1-feature' - add_note("/create_merge_request #{branch_name}") - - expect_mr_quickaction(true) - - created_mr = project.merge_requests.last - expect(created_mr.source_branch).to eq(branch_name) - - visit project_merge_request_path(project, created_mr) - expect(page).to have_content %{WIP: Resolve "#{issue.title}"} - end - end end end diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb index 5bfc3bb222f..34dba5dbc31 100644 --- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb @@ -1,4 +1,58 @@ # frozen_string_literal: true shared_examples 'create_merge_request quick action' do + context 'create a merge request starting from an issue' do + def expect_mr_quickaction(success) + expect(page).to have_content 'Commands applied' + + if success + expect(page).to have_content 'created merge request' + else + expect(page).not_to have_content 'created merge request' + end + end + + it "doesn't create a merge request when the branch name is invalid" do + add_note("/create_merge_request invalid branch name") + + wait_for_requests + + expect_mr_quickaction(false) + end + + it "doesn't create a merge request when a branch with that name already exists" do + add_note("/create_merge_request feature") + + wait_for_requests + + expect_mr_quickaction(false) + end + + it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do + add_note("/create_merge_request") + + wait_for_requests + + expect_mr_quickaction(true) + + created_mr = project.merge_requests.last + expect(created_mr.source_branch).to eq(issue.to_branch_name) + + visit project_merge_request_path(project, created_mr) + expect(page).to have_content %{WIP: Resolve "#{issue.title}"} + end + + it 'creates a merge request using the given branch name' do + branch_name = '1-feature' + add_note("/create_merge_request #{branch_name}") + + expect_mr_quickaction(true) + + created_mr = project.merge_requests.last + expect(created_mr.source_branch).to eq(branch_name) + + visit project_merge_request_path(project, created_mr) + expect(page).to have_content %{WIP: Resolve "#{issue.title}"} + end + end end -- cgit v1.2.1 From 8fdfd521b27e6332ee8bd55d5beecea2cbae9ce0 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 3 Apr 2019 14:23:58 +0300 Subject: Extract due quick action to shared example --- .../issues/user_uses_quick_actions_spec.rb | 29 +--------------- .../issue/due_quick_action_shared_examples.rb | 40 ++++++++++++++-------- 2 files changed, 26 insertions(+), 43 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index ea474759547..68af8303c2f 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -60,34 +60,7 @@ describe 'Issues > User uses quick actions', :js do it_behaves_like 'remove_due_date quick action' it_behaves_like 'duplicate quick action' it_behaves_like 'create_merge_request quick action' - - describe 'adding a due date from note' do - let(:issue) { create(:issue, project: project) } - - it_behaves_like 'due quick action available and date can be added' - - context 'when the current user cannot update the due date' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it_behaves_like 'due quick action not available' - end - end - - describe 'toggling the WIP prefix from the title from note' do - let(:issue) { create(:issue, project: project) } - - it 'does not recognize the command nor create a note' do - add_note("/wip") - - expect(page).not_to have_content '/wip' - end - end + it_behaves_like 'due quick action' describe 'move the issue to another project' do let(:issue) { create(:issue, project: project) } diff --git a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb index db3ecccc339..ae78cd86cd5 100644 --- a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb @@ -1,25 +1,35 @@ # frozen_string_literal: true -shared_examples 'due quick action not available' do - it 'does not set the due date' do - add_note('/due 2016-08-28') +shared_examples 'due quick action' do + context 'due quick action available and date can be added' do + it 'sets the due date accordingly' do + add_note('/due 2016-08-28') - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content '/due 2016-08-28' - end -end + expect(page).not_to have_content '/due 2016-08-28' + expect(page).to have_content 'Commands applied' + + visit project_issue_path(project, issue) -shared_examples 'due quick action available and date can be added' do - it 'sets the due date accordingly' do - add_note('/due 2016-08-28') + page.within '.due_date' do + expect(page).to have_content 'Aug 28, 2016' + end + end + end - expect(page).not_to have_content '/due 2016-08-28' - expect(page).to have_content 'Commands applied' + context 'due quick action not available' do + let(:guest) { create(:user) } + before do + project.add_guest(guest) + gitlab_sign_out + sign_in(guest) + visit project_issue_path(project, issue) + end - visit project_issue_path(project, issue) + it 'does not set the due date' do + add_note('/due 2016-08-28') - page.within '.due_date' do - expect(page).to have_content 'Aug 28, 2016' + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content '/due 2016-08-28' end end end -- cgit v1.2.1 From f3ad51f8a57df96bcc69b0821355ef29c3df2ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 8 Apr 2019 14:41:05 +0200 Subject: Improve performance of PR import This removes unneeded `.reload` call which makes AR to load ALL objects, and create its in-memory representation. --- .../gitlab/import/merge_request_helpers_spec.rb | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 spec/lib/gitlab/import/merge_request_helpers_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/import/merge_request_helpers_spec.rb b/spec/lib/gitlab/import/merge_request_helpers_spec.rb new file mode 100644 index 00000000000..cc0f2baf905 --- /dev/null +++ b/spec/lib/gitlab/import/merge_request_helpers_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Import::MergeRequestHelpers, type: :helper do + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + + describe '.create_merge_request_without_hooks' do + let(:iid) { 42 } + + let(:attributes) do + { + iid: iid, + title: 'My Pull Request', + description: 'This is my pull request', + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'master-42', + target_branch: 'master', + state: :merged, + author_id: user.id, + assignee_id: user.id + } + end + + subject { helper.create_merge_request_without_hooks(project, attributes, iid) } + + context 'when merge request does not exist' do + it 'returns a new object' do + expect(subject.first).not_to be_nil + expect(subject.second).to eq(false) + end + + it 'does load all existing objects' do + 5.times do |iid| + MergeRequest.create!( + attributes.merge(iid: iid, source_branch: iid.to_s)) + end + + # does ensure that we only load object twice + # 1. by #insert_and_return_id + # 2. by project.merge_requests.find + expect_any_instance_of(MergeRequest).to receive(:attributes) + .twice.times.and_call_original + + expect(subject.first).not_to be_nil + expect(subject.second).to eq(false) + end + end + + context 'when merge request does exist' do + before do + MergeRequest.create!(attributes) + end + + it 'returns an existing object' do + expect(subject.first).not_to be_nil + expect(subject.second).to eq(true) + end + end + + context 'when project is deleted' do + before do + project.delete + end + + it 'returns an existing object' do + expect(subject.first).to be_nil + end + end + end +end -- cgit v1.2.1 From 4317a2a3a2e39e4c2594b0b28abf7a8cc694eeab Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 8 Apr 2019 15:33:30 +0000 Subject: Fix `updated_at` doesn't apply to `state_event` updates of issues via API --- spec/services/note_summary_spec.rb | 10 ++++++--- spec/services/system_note_service_spec.rb | 34 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb index a6cc2251e48..f6ee15f750c 100644 --- a/spec/services/note_summary_spec.rb +++ b/spec/services/note_summary_spec.rb @@ -21,16 +21,20 @@ describe NoteSummary do describe '#note' do it 'returns note hash' do - expect(create_note_summary.note).to eq(noteable: noteable, project: project, author: user, note: 'note') + Timecop.freeze do + expect(create_note_summary.note).to eq(noteable: noteable, project: project, author: user, note: 'note', + created_at: Time.now) + end end context 'when noteable is a commit' do - let(:noteable) { build(:commit) } + let(:noteable) { build(:commit, system_note_timestamp: Time.at(43)) } it 'returns note hash specific to commit' do expect(create_note_summary.note).to eq( noteable: nil, project: project, author: user, note: 'note', - noteable_type: 'Commit', commit_id: noteable.id + noteable_type: 'Commit', commit_id: noteable.id, + created_at: Time.at(43) ) end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index b917de14b2e..8d446d1c9d5 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -11,6 +11,14 @@ describe SystemNoteService do let(:noteable) { create(:issue, project: project) } let(:issue) { noteable } + shared_examples_for 'a note with overridable created_at' do + let(:noteable) { create(:issue, project: project, system_note_timestamp: Time.at(42)) } + + it 'the note has the correct time' do + expect(subject.created_at).to eq Time.at(42) + end + end + shared_examples_for 'a system note' do let(:expected_noteable) { noteable } let(:commit_count) { nil } @@ -137,6 +145,8 @@ describe SystemNoteService do end context 'when assignee added' do + it_behaves_like 'a note with overridable created_at' + it 'sets the note text' do expect(subject.note).to eq "assigned to @#{assignee.username}" end @@ -145,6 +155,8 @@ describe SystemNoteService do context 'when assignee removed' do let(:assignee) { nil } + it_behaves_like 'a note with overridable created_at' + it 'sets the note text' do expect(subject.note).to eq 'removed assignee' end @@ -168,6 +180,8 @@ describe SystemNoteService do described_class.change_issue_assignees(issue, project, author, old_assignees).note end + it_behaves_like 'a note with overridable created_at' + it 'builds a correct phrase when an assignee is added to a non-assigned issue' do expect(build_note([], [assignee1])).to eq "assigned to @#{assignee1.username}" end @@ -213,6 +227,8 @@ describe SystemNoteService do expect(subject.note).to eq "changed milestone to #{reference}" end + + it_behaves_like 'a note with overridable created_at' end context 'when milestone removed' do @@ -221,6 +237,8 @@ describe SystemNoteService do it 'sets the note text' do expect(subject.note).to eq 'removed milestone' end + + it_behaves_like 'a note with overridable created_at' end end @@ -237,6 +255,8 @@ describe SystemNoteService do it 'sets the note text to use the milestone name' do expect(subject.note).to eq "changed milestone to #{milestone.to_reference(format: :name)}" end + + it_behaves_like 'a note with overridable created_at' end context 'when milestone removed' do @@ -245,6 +265,8 @@ describe SystemNoteService do it 'sets the note text' do expect(subject.note).to eq 'removed milestone' end + + it_behaves_like 'a note with overridable created_at' end end end @@ -254,6 +276,8 @@ describe SystemNoteService do let(:due_date) { Date.today } + it_behaves_like 'a note with overridable created_at' + it_behaves_like 'a system note' do let(:action) { 'due_date' } end @@ -280,6 +304,8 @@ describe SystemNoteService do let(:status) { 'reopened' } let(:source) { nil } + it_behaves_like 'a note with overridable created_at' + it_behaves_like 'a system note' do let(:action) { 'opened' } end @@ -289,6 +315,8 @@ describe SystemNoteService do let(:status) { 'opened' } let(:source) { double('commit', gfm_reference: 'commit 123456') } + it_behaves_like 'a note with overridable created_at' + it 'sets the note text' do expect(subject.note).to eq "#{status} via commit 123456" end @@ -338,6 +366,8 @@ describe SystemNoteService do let(:action) { 'title' } end + it_behaves_like 'a note with overridable created_at' + it 'sets the note text' do expect(subject.note) .to eq "changed title from **{-Old title-}** to **{+Lorem ipsum+}**" @@ -353,6 +383,8 @@ describe SystemNoteService do let(:action) { 'description' } end + it_behaves_like 'a note with overridable created_at' + it 'sets the note text' do expect(subject.note).to eq('changed the description') end @@ -478,6 +510,8 @@ describe SystemNoteService do let(:action) { 'cross_reference' } end + it_behaves_like 'a note with overridable created_at' + describe 'note_body' do context 'cross-project' do let(:project2) { create(:project, :repository) } -- cgit v1.2.1 From 26fdcf7b6103aa47943271a5f6358d9779d5a9b3 Mon Sep 17 00:00:00 2001 From: Daniel Wyatt Date: Mon, 8 Apr 2019 10:12:39 -0400 Subject: Fix GitHub project import visibility --- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 3d4240fa4ba..8c56622e0ba 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -47,12 +47,12 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do end context 'when GitHub project is public' do - it 'sets project visibility to public' do + it 'sets project visibility to namespace visibility level' do repo.private = false project = service.execute - expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + expect(project.visibility_level).to eq(namespace.visibility_level) end end -- cgit v1.2.1 From e86a2e7eb27468de8357f27752a9f86ee926807e Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Fri, 5 Apr 2019 12:30:10 -0500 Subject: Increase GraphQL complexity An IntrospectionQuery required more complexity points. --- spec/fixtures/api/graphql/introspection.graphql | 92 +++++++++++++++++++++++++ spec/requests/api/graphql/gitlab_schema_spec.rb | 16 ++++- 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/api/graphql/introspection.graphql (limited to 'spec') diff --git a/spec/fixtures/api/graphql/introspection.graphql b/spec/fixtures/api/graphql/introspection.graphql new file mode 100644 index 00000000000..7b712068fcd --- /dev/null +++ b/spec/fixtures/api/graphql/introspection.graphql @@ -0,0 +1,92 @@ +# pulled from GraphiQL query +query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index 708a000532b..f95f460fd14 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -3,14 +3,24 @@ require 'spec_helper' describe 'GitlabSchema configurations' do include GraphqlHelpers - let(:project) { create(:project, :repository) } - let!(:query) { graphql_query_for('project', 'fullPath' => project.full_path) } + it 'shows an error if complexity is too high' do + project = create(:project, :repository) + query = graphql_query_for('project', { 'fullPath' => project.full_path }, "id\nname\ndescription") - it 'shows an error if complexity it too high' do allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 post_graphql(query, current_user: nil) expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') end + + context 'when IntrospectionQuery' do + it 'is not too complex' do + query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) + + post_graphql(query, current_user: nil) + + expect(graphql_errors).to be_nil + end + end end -- cgit v1.2.1 From d3257e2cbc047de5da69cabd210d2b885092f5d2 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 19:51:24 +0000 Subject: Enable ESLint rule jest/no-focused-tests --- spec/frontend/.eslintrc.yml | 2 ++ spec/frontend/serverless/components/functions_spec.js | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml index 054dc27cda6..30b559c2363 100644 --- a/spec/frontend/.eslintrc.yml +++ b/spec/frontend/.eslintrc.yml @@ -12,3 +12,5 @@ globals: loadFixtures: false preloadFixtures: false setFixtures: false +rules: + jest/no-focused-tests: error diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js index 5533de1a70a..7af33ceaadc 100644 --- a/spec/frontend/serverless/components/functions_spec.js +++ b/spec/frontend/serverless/components/functions_spec.js @@ -4,14 +4,21 @@ import axios from '~/lib/utils/axios_utils'; import functionsComponent from '~/serverless/components/functions.vue'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createStore } from '~/serverless/store'; +import { TEST_HOST } from 'helpers/test_constants'; import { mockServerlessFunctions } from '../mock_data'; describe('functionsComponent', () => { + const statusPath = `${TEST_HOST}/statusPath`; + let component; let store; let localVue; + let axiosMock; beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + axiosMock.onGet(statusPath).reply(200); + localVue = createLocalVue(); localVue.use(Vuex); @@ -20,6 +27,7 @@ describe('functionsComponent', () => { afterEach(() => { component.vm.$destroy(); + axiosMock.restore(); }); it('should render empty state when Knative is not installed', () => { @@ -80,11 +88,7 @@ describe('functionsComponent', () => { ); }); - fit('should render the functions list', () => { - const statusPath = 'statusPath'; - const axiosMock = new AxiosMockAdapter(axios); - axiosMock.onGet(statusPath).reply(200); - + it('should render the functions list', () => { component = shallowMount(functionsComponent, { localVue, store, -- cgit v1.2.1 From 61d08f575c0ecc0be00ff2db5509c38ae67bb65c Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 19:34:39 +0200 Subject: Enable ESLint rule jest/no-identical-title --- spec/frontend/.eslintrc.yml | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml index 30b559c2363..b4bba8df833 100644 --- a/spec/frontend/.eslintrc.yml +++ b/spec/frontend/.eslintrc.yml @@ -13,4 +13,5 @@ globals: preloadFixtures: false setFixtures: false rules: + jest/no-identical-title: error jest/no-focused-tests: error -- cgit v1.2.1 From f23737a498e41a9ef2ae84fa45dbbdaaf524a174 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 19:54:15 +0200 Subject: Refactor diff_spec.js to use table-based tests --- spec/frontend/ide/lib/diff/diff_spec.js | 93 ++++++++++++++++----------------- 1 file changed, 45 insertions(+), 48 deletions(-) (limited to 'spec') diff --git a/spec/frontend/ide/lib/diff/diff_spec.js b/spec/frontend/ide/lib/diff/diff_spec.js index 57f3ac3d365..d9b088e2c12 100644 --- a/spec/frontend/ide/lib/diff/diff_spec.js +++ b/spec/frontend/ide/lib/diff/diff_spec.js @@ -9,60 +9,57 @@ describe('Multi-file editor library diff calculator', () => { }); describe('modified', () => { - it('', () => { - const diff = computeDiff('123', '1234')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeUndefined(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeUndefined(); - expect(diff.lineNumber).toBe(2); - }); + it.each` + originalContent | newContent | lineNumber + ${'123'} | ${'1234'} | ${1} + ${'123\n123\n123'} | ${'123\n1234\n123'} | ${2} + `( + 'marks line $lineNumber as added and modified but not removed', + ({ originalContent, newContent, lineNumber }) => { + const diff = computeDiff(originalContent, newContent)[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeTruthy(); + expect(diff.removed).toBeUndefined(); + expect(diff.lineNumber).toBe(lineNumber); + }, + ); }); describe('added', () => { - it('', () => { - const diff = computeDiff('123', '123\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeUndefined(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeUndefined(); - expect(diff.lineNumber).toBe(3); - }); + it.each` + originalContent | newContent | lineNumber + ${'123'} | ${'123\n123'} | ${1} + ${'123\n123\n123'} | ${'123\n123\n1234\n123'} | ${3} + `( + 'marks line $lineNumber as added but not modified and not removed', + ({ originalContent, newContent, lineNumber }) => { + const diff = computeDiff(originalContent, newContent)[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeUndefined(); + expect(diff.removed).toBeUndefined(); + expect(diff.lineNumber).toBe(lineNumber); + }, + ); }); describe('removed', () => { - it('', () => { - const diff = computeDiff('123', '')[0]; - - expect(diff.added).toBeUndefined(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeTruthy(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n123')[0]; - - expect(diff.added).toBeUndefined(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeTruthy(); - expect(diff.lineNumber).toBe(2); - }); + it.each` + originalContent | newContent | lineNumber | modified + ${'123'} | ${''} | ${1} | ${undefined} + ${'123\n123\n123'} | ${'123\n123'} | ${2} | ${true} + `( + 'marks line $lineNumber as removed', + ({ originalContent, newContent, lineNumber, modified }) => { + const diff = computeDiff(originalContent, newContent)[0]; + + expect(diff.added).toBeUndefined(); + expect(diff.modified).toBe(modified); + expect(diff.removed).toBeTruthy(); + expect(diff.lineNumber).toBe(lineNumber); + }, + ); }); it('includes line number of change', () => { -- cgit v1.2.1 From aa352a95df665ded5178c1b26d4492433e47714e Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Fri, 29 Mar 2019 14:07:03 +1300 Subject: Support merge request create with push options To create a new merge request: git push -u origin -o merge_request.create To create a new merge request setting target branch: git push -u origin -o merge_request.create \ -o merge_request.target=123 To update an existing merge request with a new target branch: git push -u origin -o merge_request.target=123 A new Gitlab::PushOptions class handles parsing and validating the push options array. This can be the start of the standard of GitLab accepting push options that follow namespacing rules. Rules are discussed in issue https://gitlab.com/gitlab-org/gitlab-ce/issues/43263. E.g. these push options: -o merge_request.create -o merge_request.target=123 Become parsed as: { merge_request: { create: true, target: '123', } } And are fetched with the class via: push_options.get(:merge_request) push_options.get(:merge_request, :create) push_options.get(:merge_request, :target) A new MergeRequests::PushOptionsHandlerService takes the `merge_request` namespaced push options and handles creating and updating merge requests. Any errors encountered are passed to the existing `output` Hash in Api::Internal's `post_receive` endpoint, and passed to gitlab-shell where they're output to the user. Issue https://gitlab.com/gitlab-org/gitlab-ce/issues/43263 --- spec/lib/gitlab/push_options_spec.rb | 91 +++++++ spec/requests/api/internal_spec.rb | 61 +++++ .../push_options_handler_service_spec.rb | 291 +++++++++++++++++++++ 3 files changed, 443 insertions(+) create mode 100644 spec/lib/gitlab/push_options_spec.rb create mode 100644 spec/services/merge_requests/push_options_handler_service_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/push_options_spec.rb b/spec/lib/gitlab/push_options_spec.rb new file mode 100644 index 00000000000..68b64863c21 --- /dev/null +++ b/spec/lib/gitlab/push_options_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::PushOptions do + describe 'namespace and key validation' do + it 'ignores unrecognised namespaces' do + options = described_class.new(['invalid.key=value']) + + expect(options.get(:invalid)).to eq(nil) + end + + it 'ignores unrecognised keys' do + options = described_class.new(['merge_request.key=value']) + + expect(options.get(:merge_request)).to eq(nil) + end + + it 'ignores blank keys' do + options = described_class.new(['merge_request']) + + expect(options.get(:merge_request)).to eq(nil) + end + + it 'parses recognised namespace and key pairs' do + options = described_class.new(['merge_request.target=value']) + + expect(options.get(:merge_request)).to include({ + target: 'value' + }) + end + end + + describe '#get' do + it 'can emulate Hash#dig' do + options = described_class.new(['merge_request.target=value']) + + expect(options.get(:merge_request, :target)).to eq('value') + end + end + + it 'can parse multiple push options' do + options = described_class.new([ + 'merge_request.create', + 'merge_request.target=value' + ]) + + expect(options.get(:merge_request)).to include({ + create: true, + target: 'value' + }) + expect(options.get(:merge_request, :create)).to eq(true) + expect(options.get(:merge_request, :target)).to eq('value') + end + + it 'stores options internally as a HashWithIndifferentAccess' do + options = described_class.new([ + 'merge_request.create' + ]) + + expect(options.get('merge_request', 'create')).to eq(true) + expect(options.get(:merge_request, :create)).to eq(true) + end + + it 'selects the last option when options contain duplicate namespace and key pairs' do + options = described_class.new([ + 'merge_request.target=value1', + 'merge_request.target=value2' + ]) + + expect(options.get(:merge_request, :target)).to eq('value2') + end + + it 'defaults values to true' do + options = described_class.new(['merge_request.create']) + + expect(options.get(:merge_request, :create)).to eq(true) + end + + it 'expands aliases' do + options = described_class.new(['mr.target=value']) + + expect(options.get(:merge_request, :target)).to eq('value') + end + + it 'forgives broken push options' do + options = described_class.new(['merge_request . target = value']) + + expect(options.get(:merge_request, :target)).to eq('value') + end +end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 0919540e4ba..62ba4281df5 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -938,6 +938,67 @@ describe API::Internal do expect(json_response['merge_request_urls']).to eq([]) end + it 'does not invoke MergeRequests::PushOptionsHandlerService' do + expect(MergeRequests::PushOptionsHandlerService).not_to receive(:new) + + post api("/internal/post_receive"), params: valid_params + end + + context 'when there are merge_request push options' do + before do + valid_params[:push_options] = ['merge_request.create'] + end + + it 'invokes MergeRequests::PushOptionsHandlerService' do + expect(MergeRequests::PushOptionsHandlerService).to receive(:new) + + post api("/internal/post_receive"), params: valid_params + end + + it 'links to the newly created merge request' do + post api("/internal/post_receive"), params: valid_params + + expect(json_response['merge_request_urls']).to match [{ + "branch_name" => "new_branch", + "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1", + "new_merge_request" => false + }] + end + + it 'adds errors raised from MergeRequests::PushOptionsHandlerService to warnings' do + expect(MergeRequests::PushOptionsHandlerService).to receive(:new).and_raise( + MergeRequests::PushOptionsHandlerService::Error, 'my warning' + ) + + post api("/internal/post_receive"), params: valid_params + + expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my warning') + end + + it 'adds errors on the service instance to warnings' do + expect_any_instance_of( + MergeRequests::PushOptionsHandlerService + ).to receive(:errors).at_least(:once).and_return(['my error']) + + post api("/internal/post_receive"), params: valid_params + + expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') + end + + it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do + invalid_merge_request = MergeRequest.new + invalid_merge_request.errors.add(:base, 'my error') + + expect_any_instance_of( + MergeRequests::CreateService + ).to receive(:execute).and_return(invalid_merge_request) + + post api("/internal/post_receive"), params: valid_params + + expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') + end + end + context 'broadcast message exists' do let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb new file mode 100644 index 00000000000..96becfcae26 --- /dev/null +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -0,0 +1,291 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::PushOptionsHandlerService do + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let(:service) { described_class.new(project, user, changes, push_options) } + let(:source_branch) { 'test' } + let(:target_branch) { 'feature' } + let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } + let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } + let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" } + let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{project.default_branch}" } + + before do + project.add_developer(user) + end + + shared_examples_for 'a service that can create a merge request' do + subject(:last_mr) { MergeRequest.last } + + it 'creates a merge request' do + expect { service.execute }.to change { MergeRequest.count }.by(1) + end + + it 'sets the correct target branch' do + branch = push_options[:target] || project.default_branch + + service.execute + + expect(last_mr.target_branch).to eq(branch) + end + + it 'assigns the MR to the user' do + service.execute + + expect(last_mr.assignee).to eq(user) + end + + it 'sets the title and description from the first non-merge commit' do + commits = project.repository.commits('master', limit: 5) + + expect(Gitlab::Git::Commit).to receive(:between).at_least(:once).and_return(commits) + + service.execute + + merge_commit = commits.first + non_merge_commit = commits.second + + expect(merge_commit.merge_commit?).to eq(true) + expect(non_merge_commit.merge_commit?).to eq(false) + + expect(last_mr.title).to eq(non_merge_commit.title) + expect(last_mr.description).to eq(non_merge_commit.description) + end + end + + shared_examples_for 'a service that can set the target of a merge request' do + subject(:last_mr) { MergeRequest.last } + + it 'sets the target_branch' do + service.execute + + expect(last_mr.target_branch).to eq(target_branch) + end + end + + shared_examples_for 'a service that does not create a merge request' do + it do + expect { service.execute }.not_to change { MergeRequest.count } + end + end + + shared_examples_for 'a service that does not update a merge request' do + it do + expect { service.execute }.not_to change { MergeRequest.maximum(:updated_at) } + end + end + + shared_examples_for 'a service that does nothing' do + include_examples 'a service that does not create a merge request' + include_examples 'a service that does not update a merge request' + end + + describe '`create` push option' do + let(:push_options) { { create: true } } + + context 'with a new branch' do + let(:changes) { new_branch_changes } + + it_behaves_like 'a service that can create a merge request' + end + + context 'with an existing branch but no open MR' do + let(:changes) { existing_branch_changes } + + it_behaves_like 'a service that can create a merge request' + end + + context 'with an existing branch that has a merge request open' do + let(:changes) { existing_branch_changes } + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)} + + it_behaves_like 'a service that does not create a merge request' + end + + context 'with a deleted branch' do + let(:changes) { deleted_branch_changes } + + it_behaves_like 'a service that does nothing' + end + + context 'with the project default branch' do + let(:changes) { default_branch_changes } + + it_behaves_like 'a service that does nothing' + end + end + + describe '`target` push option' do + let(:push_options) { { target: target_branch } } + + context 'with a new branch' do + let(:changes) { new_branch_changes } + + it_behaves_like 'a service that does not create a merge request' + + it 'adds an error to the service' do + error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}" + + service.execute + + expect(service.errors).to include(error) + end + + context 'when coupled with the `create` push option' do + let(:push_options) { { create: true, target: target_branch } } + + it_behaves_like 'a service that can create a merge request' + it_behaves_like 'a service that can set the target of a merge request' + end + end + + context 'with an existing branch but no open MR' do + let(:changes) { existing_branch_changes } + + it_behaves_like 'a service that does not create a merge request' + + it 'adds an error to the service' do + error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}" + + service.execute + + expect(service.errors).to include(error) + end + + context 'when coupled with the `create` push option' do + let(:push_options) { { create: true, target: target_branch } } + + it_behaves_like 'a service that can create a merge request' + it_behaves_like 'a service that can set the target of a merge request' + end + end + + context 'with an existing branch that has a merge request open' do + let(:changes) { existing_branch_changes } + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)} + + it_behaves_like 'a service that does not create a merge request' + it_behaves_like 'a service that can set the target of a merge request' + end + + context 'with a deleted branch' do + let(:changes) { deleted_branch_changes } + + it_behaves_like 'a service that does nothing' + end + + context 'with the project default branch' do + let(:changes) { default_branch_changes } + + it_behaves_like 'a service that does nothing' + end + end + + describe 'multiple pushed branches' do + let(:push_options) { { create: true } } + let(:changes) do + [ + new_branch_changes, + "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/second-branch" + ] + end + + it 'creates a merge request per branch' do + expect { service.execute }.to change { MergeRequest.count }.by(2) + end + + context 'when there are too many pushed branches' do + let(:limit) { MergeRequests::PushOptionsHandlerService::LIMIT } + let(:changes) do + TestEnv::BRANCH_SHA.to_a[0..limit].map do |x| + "#{Gitlab::Git::BLANK_SHA} #{x.first} refs/heads/#{x.last}" + end + end + + it 'throws an error' do + expect { service.execute }.to raise_error( + MergeRequests::PushOptionsHandlerService::Error, + "Too many branches pushed (#{limit + 1} were pushed, limit is #{limit})" + ) + end + end + end + + describe 'no push options' do + let(:push_options) { {} } + let(:changes) { new_branch_changes } + + it_behaves_like 'a service that does nothing' + end + + describe 'no user' do + let(:user) { nil } + let(:push_options) { { create: true } } + let(:changes) { new_branch_changes } + + it 'throws an error' do + expect { service.execute }.to raise_error( + MergeRequests::PushOptionsHandlerService::Error, + 'User is required' + ) + end + end + + describe 'unauthorized user' do + let(:push_options) { { create: true } } + let(:changes) { new_branch_changes } + + it 'throws an error' do + Members::DestroyService.new(user).execute(ProjectMember.find_by!(user_id: user.id)) + + expect { service.execute }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + describe 'when target is not a valid branch name' do + let(:push_options) { { create: true, target: 'my-branch' } } + let(:changes) { new_branch_changes } + + it 'throws an error' do + expect { service.execute }.to raise_error( + MergeRequests::PushOptionsHandlerService::Error, + 'Branch my-branch does not exist' + ) + end + end + + describe 'when MRs are not enabled' do + let(:push_options) { { create: true } } + let(:changes) { new_branch_changes } + + it 'throws an error' do + expect(project).to receive(:merge_requests_enabled?).and_return(false) + + expect { service.execute }.to raise_error( + MergeRequests::PushOptionsHandlerService::Error, + 'Merge requests are not enabled for project' + ) + end + end + + describe 'when MR has ActiveRecord errors' do + let(:push_options) { { create: true } } + let(:changes) { new_branch_changes } + + it 'adds the error to its errors property' do + invalid_merge_request = MergeRequest.new + invalid_merge_request.errors.add(:base, 'my error') + + expect_any_instance_of( + MergeRequests::CreateService + ).to receive(:execute).and_return(invalid_merge_request) + + service.execute + + expect(service.errors).to eq(['my error']) + end + end +end -- cgit v1.2.1 From 867ac4d1f733254de1b4c44f314e585e6b2720e8 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Tue, 2 Apr 2019 14:30:40 +1300 Subject: Change double quote strings to single quotes --- spec/requests/api/internal_spec.rb | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 62ba4281df5..3cc0cceeab1 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -907,7 +907,7 @@ describe API::Internal do expect(PostReceive).to receive(:perform_async) .with(gl_repository, identifier, changes, push_options) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params end it 'decreases the reference counter and returns the result' do @@ -915,13 +915,13 @@ describe API::Internal do .and_return(reference_counter) expect(reference_counter).to receive(:decrease).and_return(true) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(json_response['reference_counter_decreased']).to be(true) end it 'returns link to create new merge request' do - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(json_response['merge_request_urls']).to match [{ "branch_name" => "new_branch", @@ -933,7 +933,7 @@ describe API::Internal do it 'returns empty array if printing_merge_request_link_enabled is false' do project.update!(printing_merge_request_link_enabled: false) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(json_response['merge_request_urls']).to eq([]) end @@ -941,7 +941,7 @@ describe API::Internal do it 'does not invoke MergeRequests::PushOptionsHandlerService' do expect(MergeRequests::PushOptionsHandlerService).not_to receive(:new) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params end context 'when there are merge_request push options' do @@ -952,16 +952,16 @@ describe API::Internal do it 'invokes MergeRequests::PushOptionsHandlerService' do expect(MergeRequests::PushOptionsHandlerService).to receive(:new) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params end it 'links to the newly created merge request' do - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(json_response['merge_request_urls']).to match [{ - "branch_name" => "new_branch", - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1", - "new_merge_request" => false + 'branch_name' => 'new_branch', + 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1", + 'new_merge_request' => false }] end @@ -970,7 +970,7 @@ describe API::Internal do MergeRequests::PushOptionsHandlerService::Error, 'my warning' ) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my warning') end @@ -980,7 +980,7 @@ describe API::Internal do MergeRequests::PushOptionsHandlerService ).to receive(:errors).at_least(:once).and_return(['my error']) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') end @@ -993,7 +993,7 @@ describe API::Internal do MergeRequests::CreateService ).to receive(:execute).and_return(invalid_merge_request) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') end @@ -1003,7 +1003,7 @@ describe API::Internal do let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } it 'returns one broadcast message' do - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(broadcast_message.message) @@ -1012,7 +1012,7 @@ describe API::Internal do context 'broadcast message does not exist' do it 'returns empty string' do - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(nil) @@ -1023,7 +1023,7 @@ describe API::Internal do it 'returns empty string' do allow(BroadcastMessage).to receive(:current).and_return(nil) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(nil) @@ -1035,7 +1035,7 @@ describe API::Internal do project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'http', 'foo/baz') project_moved.add_message - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response["redirected_message"]).to be_present @@ -1048,7 +1048,7 @@ describe API::Internal do project_created = Gitlab::Checks::ProjectCreated.new(project, user, 'http') project_created.add_message - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) expect(json_response["project_created_message"]).to be_present @@ -1060,7 +1060,7 @@ describe API::Internal do it 'does not try to notify that project moved' do allow_any_instance_of(Gitlab::Identifier).to receive(:identify).and_return(nil) - post api("/internal/post_receive"), params: valid_params + post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) end -- cgit v1.2.1 From ca884980ee8e6fe1269f5abdb803519d51aa09c0 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Sun, 7 Apr 2019 15:35:16 -0300 Subject: [CE] Support multiple assignees for merge requests Backports https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10161 (code out of ee/ folder). --- .../projects/merge_requests_controller_spec.rb | 6 +- spec/features/dashboard/issuables_counter_spec.rb | 4 +- spec/features/dashboard/merge_requests_spec.rb | 4 +- spec/features/groups/merge_requests_spec.rb | 2 +- spec/features/issues/form_spec.rb | 4 +- .../user_creates_merge_request_spec.rb | 4 +- .../features/merge_request/user_creates_mr_spec.rb | 15 +- spec/features/merge_request/user_edits_mr_spec.rb | 18 ++- .../user_filters_by_assignees_spec.rb | 2 +- .../user_filters_by_multiple_criteria_spec.rb | 2 +- .../user_lists_merge_requests_spec.rb | 4 +- .../merge_requests/user_mass_updates_spec.rb | 3 +- .../search/user_uses_header_search_field_spec.rb | 4 +- spec/finders/issues_finder_spec.rb | 64 +++------ spec/finders/merge_requests_finder_spec.rb | 49 +++++-- .../api/schemas/entities/merge_request_basic.json | 12 +- .../api/schemas/public_api/v4/merge_request.json | 5 + spec/javascripts/sidebar/assignees_spec.js | 85 +++++++++++ spec/lib/gitlab/hook_data/issuable_builder_spec.rb | 6 +- .../gitlab/hook_data/merge_request_builder_spec.rb | 1 + spec/lib/gitlab/import_export/all_models.yml | 4 + .../gitlab/import_export/safe_model_attributes.yml | 4 + spec/lib/gitlab/issuable_metadata_spec.rb | 4 +- spec/mailers/notify_spec.rb | 18 +-- spec/models/ci/pipeline_spec.rb | 8 +- spec/models/concerns/deprecated_assignee_spec.rb | 160 +++++++++++++++++++++ spec/models/concerns/issuable_spec.rb | 7 +- spec/models/event_spec.rb | 2 +- spec/models/merge_request_spec.rb | 60 ++------ spec/models/user_spec.rb | 6 +- spec/requests/api/events_spec.rb | 4 +- spec/requests/api/merge_requests_spec.rb | 124 +++++++++++++--- spec/services/issuable/bulk_update_service_spec.rb | 16 +-- spec/services/issuable/destroy_service_spec.rb | 2 +- spec/services/members/destroy_service_spec.rb | 4 +- spec/services/merge_requests/close_service_spec.rb | 2 +- .../create_from_issue_service_spec.rb | 2 +- .../services/merge_requests/create_service_spec.rb | 30 ++-- .../merge_requests/ff_merge_service_spec.rb | 2 +- spec/services/merge_requests/merge_service_spec.rb | 4 +- .../merge_requests/merge_to_ref_service_spec.rb | 2 +- .../merge_requests/post_merge_service_spec.rb | 2 +- .../services/merge_requests/reopen_service_spec.rb | 2 +- .../services/merge_requests/update_service_spec.rb | 42 +++--- spec/services/notification_service_spec.rb | 55 +++---- .../quick_actions/interpret_service_spec.rb | 4 +- spec/services/system_note_service_spec.rb | 6 +- spec/services/todo_service_spec.rb | 110 +++++++------- spec/services/users/destroy_service_spec.rb | 4 +- .../merge_requests_finder_shared_contexts.rb | 6 +- .../shared_contexts/merge_request_create.rb | 26 ++++ spec/support/shared_contexts/merge_request_edit.rb | 28 ++++ .../creatable_merge_request_shared_examples.rb | 31 +--- .../editable_merge_request_shared_examples.rb | 28 +--- .../multiple_assignees_mr_shared_examples.rb | 47 ++++++ .../finders/assignees_filter_spec.rb | 49 +++++++ .../projects/merge_requests/edit.html.haml_spec.rb | 6 +- .../projects/merge_requests/show.html.haml_spec.rb | 13 -- 58 files changed, 810 insertions(+), 408 deletions(-) create mode 100644 spec/models/concerns/deprecated_assignee_spec.rb create mode 100644 spec/support/shared_contexts/merge_request_create.rb create mode 100644 spec/support/shared_contexts/merge_request_edit.rb create mode 100644 spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb create mode 100644 spec/support/shared_examples/finders/assignees_filter_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 017162519d8..a125e470522 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -238,11 +238,11 @@ describe Projects::MergeRequestsController do assignee = create(:user) project.add_developer(assignee) - update_merge_request({ assignee_id: assignee.id }, format: :json) + update_merge_request({ assignee_ids: [assignee.id] }, format: :json) + body = JSON.parse(response.body) - expect(body['assignee'].keys) - .to match_array(%w(name username avatar_url id state web_url)) + expect(body['assignees']).to all(include(*%w(name username avatar_url id state web_url))) end end diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index fbc2e5cc3d3..50b71368e13 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -8,7 +8,7 @@ describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do before do issue.assignees = [user] - merge_request.update(assignee: user) + merge_request.update(assignees: [user]) sign_in(user) end @@ -33,7 +33,7 @@ describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do expect_counters('merge_requests', '1') - merge_request.update(assignee: nil) + merge_request.update(assignees: []) user.invalidate_cache_counts diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 4965770605a..0c6713f623c 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -48,14 +48,14 @@ describe 'Dashboard Merge Requests' do let!(:assigned_merge_request) do create(:merge_request, - assignee: current_user, + assignees: [current_user], source_project: project, author: create(:user)) end let!(:assigned_merge_request_from_fork) do create(:merge_request, - source_branch: 'markdown', assignee: current_user, + source_branch: 'markdown', assignees: [current_user], target_project: public_project, source_project: forked_project, author: create(:user)) end diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index 54a8016c157..e1bc4eca619 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -38,7 +38,7 @@ describe 'Group merge requests page' do context 'when merge request assignee to user' do before do - issuable.update!(assignee: user) + issuable.update!(assignees: [user]) visit path end diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 26c781350e5..6fa2ad8711f 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -30,8 +30,8 @@ describe 'New/edit issue', :js do # the original method, resulting in infinite recursion when called. # This is likely a bug with helper modules included into dynamically generated view classes. # To work around this, we have to hold on to and call to the original implementation manually. - original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options) - allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args| + original_issue_dropdown_options = FormHelper.instance_method(:assignees_dropdown_options) + allow_any_instance_of(FormHelper).to receive(:assignees_dropdown_options).and_wrap_original do |original, *args| options = original_issue_dropdown_options.bind(original.receiver).call(*args) options[:data][:per_page] = 2 diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb index ea2bb1503bb..bcc11217389 100644 --- a/spec/features/merge_request/user_creates_merge_request_spec.rb +++ b/spec/features/merge_request/user_creates_merge_request_spec.rb @@ -68,15 +68,15 @@ describe "User creates a merge request", :js do fill_in("Title", with: title) end - click_button("Assignee") - expect(find(".js-assignee-search")["data-project-id"]).to eq(project.id.to_s) + find('.js-assignee-search').click page.within(".dropdown-menu-user") do expect(page).to have_content("Unassigned") .and have_content(user.name) .and have_content(project.users.first.name) end + find('.js-assignee-search').click click_button("Submit merge request") diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb index c169a68cd1c..c9dedab048a 100644 --- a/spec/features/merge_request/user_creates_mr_spec.rb +++ b/spec/features/merge_request/user_creates_mr_spec.rb @@ -1,11 +1,18 @@ require 'rails_helper' describe 'Merge request > User creates MR' do - it_behaves_like 'a creatable merge request' + include ProjectForksHelper - context 'from a forked project' do - include ProjectForksHelper + before do + stub_licensed_features(multiple_merge_request_assignees: false) + end + context 'non-fork merge request' do + include_context 'merge request create context' + it_behaves_like 'a creatable merge request' + end + + context 'from a forked project' do let(:canonical_project) { create(:project, :public, :repository) } let(:source_project) do @@ -15,6 +22,7 @@ describe 'Merge request > User creates MR' do end context 'to canonical project' do + include_context 'merge request create context' it_behaves_like 'a creatable merge request' end @@ -25,6 +33,7 @@ describe 'Merge request > User creates MR' do namespace: user.namespace) end + include_context 'merge request create context' it_behaves_like 'a creatable merge request' end end diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb index 3152707136c..25979513ead 100644 --- a/spec/features/merge_request/user_edits_mr_spec.rb +++ b/spec/features/merge_request/user_edits_mr_spec.rb @@ -1,13 +1,21 @@ -require 'rails_helper' +require 'spec_helper' describe 'Merge request > User edits MR' do include ProjectForksHelper - it_behaves_like 'an editable merge request' + before do + stub_licensed_features(multiple_merge_request_assignees: false) + end + + context 'non-fork merge request' do + include_context 'merge request edit context' + it_behaves_like 'an editable merge request' + end context 'for a forked project' do - it_behaves_like 'an editable merge request' do - let(:source_project) { fork_project(target_project, nil, repository: true) } - end + let(:source_project) { fork_project(target_project, nil, repository: true) } + + include_context 'merge request edit context' + it_behaves_like 'an editable merge request' end end diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb index d6c770c93f1..0cbf1bcae30 100644 --- a/spec/features/merge_requests/user_filters_by_assignees_spec.rb +++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb @@ -7,7 +7,7 @@ describe 'Merge Requests > User filters by assignees', :js do let(:user) { project.creator } before do - create(:merge_request, assignee: user, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') + create(:merge_request, assignees: [user], title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') create(:merge_request, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2') sign_in(user) diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb index 1615899a047..4627931f26a 100644 --- a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb +++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb @@ -10,7 +10,7 @@ describe 'Merge requests > User filters by multiple criteria', :js do before do sign_in(user) - mr = create(:merge_request, title: 'Bugfix2', author: user, assignee: user, source_project: project, target_project: project, milestone: milestone) + mr = create(:merge_request, title: 'Bugfix2', author: user, assignees: [user], source_project: project, target_project: project, milestone: milestone) mr.labels << wontfix visit project_merge_requests_path(project) diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index c691011b9ca..bd91fae1453 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -12,7 +12,7 @@ describe 'Merge requests > User lists merge requests' do title: 'fix', source_project: project, source_branch: 'fix', - assignee: user, + assignees: [user], milestone: create(:milestone, project: project, due_date: '2013-12-11'), created_at: 1.minute.ago, updated_at: 1.minute.ago) @@ -20,7 +20,7 @@ describe 'Merge requests > User lists merge requests' do title: 'markdown', source_project: project, source_branch: 'markdown', - assignee: user, + assignees: [user], milestone: create(:milestone, project: project, due_date: '2013-12-12'), created_at: 2.minutes.ago, updated_at: 2.minutes.ago) diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb index e535c7e5811..c2dd105324d 100644 --- a/spec/features/merge_requests/user_mass_updates_spec.rb +++ b/spec/features/merge_requests/user_mass_updates_spec.rb @@ -54,8 +54,7 @@ describe 'Merge requests > User mass updates', :js do describe 'remove assignee' do before do - merge_request.assignee = user - merge_request.save + merge_request.assignees = [user] visit project_merge_requests_path(project) end diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 444de26733f..1cc47cd6bd1 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -36,7 +36,7 @@ describe 'User uses header search field' do end context 'when clicking merge requests' do - let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) } + let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) } it 'shows assigned merge requests' do find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me') @@ -100,7 +100,7 @@ describe 'User uses header search field' do end context 'when clicking merge requests' do - let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) } + let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) } it 'shows assigned merge requests' do find('.dropdown-menu').click_link('Merge requests assigned to me') diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index fe53fabe54c..6e6dc334354 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -13,60 +13,32 @@ describe IssuesFinder do expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) end - context 'filtering by assignee ID' do - let(:params) { { assignee_id: user.id } } + context 'assignee filtering' do + let(:issuables) { issues } - it 'returns issues assigned to that user' do - expect(issues).to contain_exactly(issue1, issue2) - end - end - - context 'filtering by assignee usernames' do - set(:user3) { create(:user) } - let(:params) { { assignee_username: [user2.username, user3.username] } } - - before do - project2.add_developer(user3) - - issue3.assignees = [user2, user3] - end - - it 'returns issues assigned to those users' do - expect(issues).to contain_exactly(issue3) - end - end - - context 'filtering by no assignee' do - let(:params) { { assignee_id: 'None' } } - - it 'returns issues not assigned to any assignee' do - expect(issues).to contain_exactly(issue4) - end - - it 'returns issues not assigned to any assignee' do - params[:assignee_id] = 0 - - expect(issues).to contain_exactly(issue4) + it_behaves_like 'assignee ID filter' do + let(:params) { { assignee_id: user.id } } + let(:expected_issuables) { [issue1, issue2] } end - it 'returns issues not assigned to any assignee' do - params[:assignee_id] = 'none' + it_behaves_like 'assignee username filter' do + before do + project2.add_developer(user3) + issue3.assignees = [user2, user3] + end - expect(issues).to contain_exactly(issue4) + set(:user3) { create(:user) } + let(:params) { { assignee_username: [user2.username, user3.username] } } + let(:expected_issuables) { [issue3] } end - end - context 'filtering by any assignee' do - let(:params) { { assignee_id: 'Any' } } - - it 'returns issues assigned to any assignee' do - expect(issues).to contain_exactly(issue1, issue2, issue3) + it_behaves_like 'no assignee filter' do + set(:user3) { create(:user) } + let(:expected_issuables) { [issue4] } end - it 'returns issues assigned to any assignee' do - params[:assignee_id] = 'any' - - expect(issues).to contain_exactly(issue1, issue2, issue3) + it_behaves_like 'any assignee filter' do + let(:expected_issuables) { [issue1, issue2, issue3] } end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index f508b9bdb6f..ef002125635 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -136,21 +136,50 @@ describe MergeRequestsFinder do end end - context 'filtering by group milestone' do - let(:group_milestone) { create(:milestone, group: group) } + context 'assignee filtering' do + let(:issuables) { described_class.new(user, params).execute } - before do - project2.update(namespace: group) - merge_request2.update(milestone: group_milestone) - merge_request3.update(milestone: group_milestone) + it_behaves_like 'assignee ID filter' do + let(:params) { { assignee_id: user.id } } + let(:expected_issuables) { [merge_request1, merge_request2] } end - it 'returns merge requests assigned to that group milestone' do - params = { milestone_title: group_milestone.title } + it_behaves_like 'assignee username filter' do + before do + project2.add_developer(user3) + merge_request3.assignees = [user2, user3] + end - merge_requests = described_class.new(user, params).execute + set(:user3) { create(:user) } + let(:params) { { assignee_username: [user2.username, user3.username] } } + let(:expected_issuables) { [merge_request3] } + end + + it_behaves_like 'no assignee filter' do + set(:user3) { create(:user) } + let(:expected_issuables) { [merge_request4, merge_request5] } + end + + it_behaves_like 'any assignee filter' do + let(:expected_issuables) { [merge_request1, merge_request2, merge_request3] } + end + + context 'filtering by group milestone' do + let(:group_milestone) { create(:milestone, group: group) } + + before do + project2.update(namespace: group) + merge_request2.update(milestone: group_milestone) + merge_request3.update(milestone: group_milestone) + end + + it 'returns merge requests assigned to that group milestone' do + params = { milestone_title: group_milestone.title } - expect(merge_requests).to contain_exactly(merge_request2, merge_request3) + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(merge_request2, merge_request3) + end end end diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json index 3006b482d41..88a600398b1 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_basic.json +++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json @@ -6,14 +6,14 @@ "source_branch_exists": { "type": "boolean" }, "merge_error": { "type": ["string", "null"] }, "rebase_in_progress": { "type": "boolean" }, - "assignee_id": { "type": ["integer", "null"] }, "allow_collaboration": { "type": "boolean"}, "allow_maintainer_to_push": { "type": "boolean"}, - "assignee": { - "oneOf": [ - { "type": "null" }, - { "$ref": "user.json" } - ] + "assignees": { + "type": ["array"], + "items": { + "type": "object", + "$ref": "../public_api/v4/user/basic.json" + } }, "milestone": { "type": [ "object", "null" ] diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_request.json b/spec/fixtures/api/schemas/public_api/v4/merge_request.json index 918f2c4b47d..a423bf70b69 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_request.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_request.json @@ -64,6 +64,11 @@ }, "additionalProperties": false }, + "assignees": { + "items": { + "$ref": "./merge_request.json" + } + }, "source_project_id": { "type": "integer" }, "target_project_id": { "type": "integer" }, "labels": { diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js index 57b16b12cb0..47fee5d2b21 100644 --- a/spec/javascripts/sidebar/assignees_spec.js +++ b/spec/javascripts/sidebar/assignees_spec.js @@ -132,9 +132,94 @@ describe('Assignee component', () => { -1, ); }); + + it('has correct "cannot merge" tooltip when user cannot merge', () => { + const user = Object.assign({}, UsersMock.user, { can_merge: false }); + + component = new AssigneeComponent({ + propsData: { + rootPath: 'http://localhost:3000/', + users: [user], + editable: true, + issuableType: 'merge_request', + }, + }).$mount(); + + expect(component.mergeNotAllowedTooltipMessage).toEqual('Cannot merge'); + }); }); describe('Two or more assignees/users', () => { + it('has correct "cannot merge" tooltip when one user can merge', () => { + const users = UsersMockHelper.createNumberRandomUsers(3); + users[0].can_merge = true; + users[1].can_merge = false; + users[2].can_merge = false; + + component = new AssigneeComponent({ + propsData: { + rootPath: 'http://localhost:3000/', + users, + editable: true, + issuableType: 'merge_request', + }, + }).$mount(); + + expect(component.mergeNotAllowedTooltipMessage).toEqual('1/3 can merge'); + }); + + it('has correct "cannot merge" tooltip when no user can merge', () => { + const users = UsersMockHelper.createNumberRandomUsers(2); + users[0].can_merge = false; + users[1].can_merge = false; + + component = new AssigneeComponent({ + propsData: { + rootPath: 'http://localhost:3000/', + users, + editable: true, + issuableType: 'merge_request', + }, + }).$mount(); + + expect(component.mergeNotAllowedTooltipMessage).toEqual('No one can merge'); + }); + + it('has correct "cannot merge" tooltip when more than one user can merge', () => { + const users = UsersMockHelper.createNumberRandomUsers(3); + users[0].can_merge = false; + users[1].can_merge = true; + users[2].can_merge = true; + + component = new AssigneeComponent({ + propsData: { + rootPath: 'http://localhost:3000/', + users, + editable: true, + issuableType: 'merge_request', + }, + }).$mount(); + + expect(component.mergeNotAllowedTooltipMessage).toEqual('2/3 can merge'); + }); + + it('has no "cannot merge" tooltip when every user can merge', () => { + const users = UsersMockHelper.createNumberRandomUsers(2); + users[0].can_merge = true; + users[1].can_merge = true; + + component = new AssigneeComponent({ + propsData: { + rootPath: 'http://localhost:3000/', + users, + editable: true, + issuableType: 'merge_request', + }, + }).$mount(); + + expect(component.mergeNotAllowedTooltipMessage).toEqual(null); + }); + it('displays two assignee icons when collapsed', () => { const users = UsersMockHelper.createNumberRandomUsers(2); component = new AssigneeComponent({ diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb index 26529c4759d..569d5dcc757 100644 --- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb @@ -97,13 +97,13 @@ describe Gitlab::HookData::IssuableBuilder do end context 'merge_request is assigned' do - let(:merge_request) { create(:merge_request, assignee: user) } + let(:merge_request) { create(:merge_request, assignees: [user]) } let(:data) { described_class.new(merge_request).build(user: user) } it 'returns correct hook data' do expect(data[:object_attributes]['assignee_id']).to eq(user.id) - expect(data[:assignee]).to eq(user.hook_attrs) - expect(data).not_to have_key(:assignees) + expect(data[:assignees].first).to eq(user.hook_attrs) + expect(data).not_to have_key(:assignee) end end end diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index 9ce697adbba..39f80f92fa6 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -10,6 +10,7 @@ describe Gitlab::HookData::MergeRequestBuilder do it 'includes safe attribute' do %w[ assignee_id + assignee_ids author_id created_at description diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index e418516569a..ed557ffd4e3 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -102,6 +102,7 @@ merge_requests: - merge_request_pipelines - merge_request_assignees - suggestions +- assignees merge_request_diff: - merge_request - merge_request_diff_commits @@ -336,6 +337,9 @@ push_event_payload: issue_assignees: - issue - assignee +merge_request_assignees: +- merge_request +- assignee lfs_file_locks: - user project_badges: diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index d0ed588f05f..06995604a24 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -621,3 +621,7 @@ Suggestion: - outdated - lines_above - lines_below +MergeRequestAssignee: +- id +- user_id +- merge_request_id diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb index 6ec86163233..916f3876a8e 100644 --- a/spec/lib/gitlab/issuable_metadata_spec.rb +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::IssuableMetadata do let!(:closed_issue) { create(:issue, state: :closed, author: user, project: project) } let!(:downvote) { create(:award_emoji, :downvote, awardable: closed_issue) } let!(:upvote) { create(:award_emoji, :upvote, awardable: issue) } - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, title: "Test") } let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) } it 'aggregates stats on issues' do @@ -39,7 +39,7 @@ describe Gitlab::IssuableMetadata do end context 'merge requests' do - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, title: "Test") } let!(:merge_request_closed) { create(:merge_request, state: "closed", source_project: project, target_project: project, title: "Closed Test") } let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) } let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 5fa1369c00a..fee1d701e3a 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -19,7 +19,7 @@ describe Notify do create(:merge_request, source_project: project, target_project: project, author: current_user, - assignee: assignee, + assignees: [assignee], description: 'Awesome description') end @@ -275,7 +275,7 @@ describe Notify do context 'for merge requests' do describe 'that are new' do - subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) } + subject { described_class.new_merge_request_email(merge_request.assignee_ids.first, merge_request.id) } it_behaves_like 'an assignee email' it_behaves_like 'an email starting a new thread with reply-by-email enabled' do @@ -300,7 +300,7 @@ describe Notify do end context 'when sent with a reason' do - subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id, NotificationReason::ASSIGNED) } + subject { described_class.new_merge_request_email(merge_request.assignee_ids.first, merge_request.id, NotificationReason::ASSIGNED) } it_behaves_like 'appearance header and footer enabled' it_behaves_like 'appearance header and footer not enabled' @@ -324,7 +324,7 @@ describe Notify do describe 'that are reassigned' do let(:previous_assignee) { create(:user, name: 'Previous Assignee') } - subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } + subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id) } it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do @@ -351,7 +351,7 @@ describe Notify do end context 'when sent with a reason' do - subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::ASSIGNED) } + subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) } it_behaves_like 'appearance header and footer enabled' it_behaves_like 'appearance header and footer not enabled' @@ -364,11 +364,11 @@ describe Notify do text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::ASSIGNED) is_expected.to have_body_text(text) - new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::MENTIONED) + new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::MENTIONED) text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::MENTIONED) expect(new_subject).to have_body_text(text) - new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, nil) + new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, nil) text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(nil) expect(new_subject).to have_body_text(text) end @@ -376,7 +376,7 @@ describe Notify do end describe 'that are new with a description' do - subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) } + subject { described_class.new_merge_request_email(merge_request.assignee_ids.first, merge_request.id) } it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like "an unsubscribeable thread" @@ -476,7 +476,7 @@ describe Notify do source_project: project, target_project: project, author: current_user, - assignee: assignee, + assignees: [assignee], description: 'Awesome description') end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 83b0f172f03..f3e78630c1b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -684,12 +684,12 @@ describe Ci::Pipeline, :mailer do source_branch: 'feature', target_project: project, target_branch: 'master', - assignee: assignee, + assignees: assignees, milestone: milestone, labels: labels) end - let(:assignee) { create(:user) } + let(:assignees) { create_list(:user, 2) } let(:milestone) { create(:milestone, project: project) } let(:labels) { create_list(:label, 2) } @@ -710,7 +710,7 @@ describe Ci::Pipeline, :mailer do 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s, 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s, 'CI_MERGE_REQUEST_TITLE' => merge_request.title, - 'CI_MERGE_REQUEST_ASSIGNEES' => assignee.username, + 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list, 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(',')) end @@ -730,7 +730,7 @@ describe Ci::Pipeline, :mailer do end context 'without assignee' do - let(:assignee) { nil } + let(:assignees) { [] } it 'does not expose assignee variable' do expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES') diff --git a/spec/models/concerns/deprecated_assignee_spec.rb b/spec/models/concerns/deprecated_assignee_spec.rb new file mode 100644 index 00000000000..e394de0aa34 --- /dev/null +++ b/spec/models/concerns/deprecated_assignee_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DeprecatedAssignee do + let(:user) { create(:user) } + + describe '#assignee_id=' do + it 'creates the merge_request_assignees relation' do + merge_request = create(:merge_request, assignee_id: user.id) + + merge_request.reload + + expect(merge_request.merge_request_assignees.count).to eq(1) + end + + it 'nullifies the assignee_id column' do + merge_request = create(:merge_request, assignee_id: user.id) + + merge_request.reload + + expect(merge_request.read_attribute(:assignee_id)).to be_nil + end + + context 'when relation already exists' do + it 'overwrites existing assignees' do + other_user = create(:user) + merge_request = create(:merge_request, assignee_id: nil) + merge_request.merge_request_assignees.create!(user_id: user.id) + merge_request.merge_request_assignees.create!(user_id: other_user.id) + + expect { merge_request.update!(assignee_id: other_user.id) } + .to change { merge_request.reload.merge_request_assignees.count } + .from(2).to(1) + end + end + end + + describe '#assignee=' do + it 'creates the merge_request_assignees relation' do + merge_request = create(:merge_request, assignee: user) + + merge_request.reload + + expect(merge_request.merge_request_assignees.count).to eq(1) + end + + it 'nullifies the assignee_id column' do + merge_request = create(:merge_request, assignee: user) + + merge_request.reload + + expect(merge_request.read_attribute(:assignee_id)).to be_nil + end + + context 'when relation already exists' do + it 'overwrites existing assignees' do + other_user = create(:user) + merge_request = create(:merge_request, assignee: nil) + merge_request.merge_request_assignees.create!(user_id: user.id) + merge_request.merge_request_assignees.create!(user_id: other_user.id) + + expect { merge_request.update!(assignee: other_user) } + .to change { merge_request.reload.merge_request_assignees.count } + .from(2).to(1) + end + end + end + + describe '#assignee_id' do + it 'returns the first assignee ID' do + other_user = create(:user) + merge_request = create(:merge_request, assignees: [user, other_user]) + + merge_request.reload + + expect(merge_request.assignee_id).to eq(merge_request.assignee_ids.first) + end + end + + describe '#assignees' do + context 'when assignee_id exists and there is no relation' do + it 'creates the relation' do + merge_request = create(:merge_request, assignee_id: nil) + merge_request.update_column(:assignee_id, user.id) + + expect { merge_request.assignees }.to change { merge_request.merge_request_assignees.count }.from(0).to(1) + end + + it 'nullifies the assignee_id' do + merge_request = create(:merge_request, assignee_id: nil) + merge_request.update_column(:assignee_id, user.id) + + expect { merge_request.assignees } + .to change { merge_request.read_attribute(:assignee_id) } + .from(user.id).to(nil) + end + end + + context 'when DB is read-only' do + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + it 'returns a users relation' do + merge_request = create(:merge_request, assignee_id: user.id) + + expect(merge_request.assignees).to be_a(ActiveRecord::Relation) + expect(merge_request.assignees).to eq([user]) + end + + it 'returns an empty relation if no assignee_id is set' do + merge_request = create(:merge_request, assignee_id: nil) + + expect(merge_request.assignees).to be_a(ActiveRecord::Relation) + expect(merge_request.assignees).to eq([]) + end + end + end + + describe '#assignee_ids' do + context 'when assignee_id exists and there is no relation' do + it 'creates the relation' do + merge_request = create(:merge_request, assignee_id: nil) + merge_request.update_column(:assignee_id, user.id) + + expect { merge_request.assignee_ids }.to change { merge_request.merge_request_assignees.count }.from(0).to(1) + end + + it 'nullifies the assignee_id' do + merge_request = create(:merge_request, assignee_id: nil) + merge_request.update_column(:assignee_id, user.id) + + expect { merge_request.assignee_ids } + .to change { merge_request.read_attribute(:assignee_id) } + .from(user.id).to(nil) + end + end + + context 'when DB is read-only' do + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + it 'returns a list of user IDs' do + merge_request = create(:merge_request, assignee_id: user.id) + + expect(merge_request.assignee_ids).to be_a(Array) + expect(merge_request.assignee_ids).to eq([user.id]) + end + + it 'returns an empty relation if no assignee_id is set' do + merge_request = create(:merge_request, assignee_id: nil) + + expect(merge_request.assignee_ids).to be_a(Array) + expect(merge_request.assignee_ids).to eq([]) + end + end + end +end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 27ed298ae08..64f02978d79 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -502,8 +502,8 @@ describe Issuable do let(:user2) { create(:user) } before do - merge_request.update(assignee: user) - merge_request.update(assignee: user2) + merge_request.update(assignees: [user]) + merge_request.update(assignees: [user, user2]) expect(Gitlab::HookData::IssuableBuilder) .to receive(:new).with(merge_request).and_return(builder) end @@ -512,8 +512,7 @@ describe Issuable do expect(builder).to receive(:build).with( user: user, changes: hash_including( - 'assignee_id' => [user.id, user2.id], - 'assignee' => [user.hook_attrs, user2.hook_attrs] + 'assignees' => [[user.hook_attrs], [user.hook_attrs, user2.hook_attrs]] )) merge_request.to_hook_data(user, old_associations: { assignees: [user] }) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index d192fe70506..e91b5c4c86f 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -263,7 +263,7 @@ describe Event do context 'merge request diff note event' do let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) } + let(:merge_request) { create(:merge_request, source_project: project, author: author, assignees: [assignee]) } let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6f34ef9c1bc..f61857ea5ff 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -13,7 +13,7 @@ describe MergeRequest do it { is_expected.to belong_to(:target_project).class_name('Project') } it { is_expected.to belong_to(:source_project).class_name('Project') } it { is_expected.to belong_to(:merge_user).class_name("User") } - it { is_expected.to belong_to(:assignee) } + it { is_expected.to have_many(:assignees).through(:merge_request_assignees) } it { is_expected.to have_many(:merge_request_diffs) } context 'for forks' do @@ -181,31 +181,6 @@ describe MergeRequest do expect(MergeRequest::Metrics.count).to eq(1) end end - - describe '#refresh_merge_request_assignees' do - set(:user) { create(:user) } - - it 'creates merge request assignees relation upon MR creation' do - merge_request = create(:merge_request, assignee: nil) - - expect(merge_request.merge_request_assignees).to be_empty - - expect { merge_request.update!(assignee: user) } - .to change { merge_request.reload.merge_request_assignees.count } - .from(0).to(1) - end - - it 'updates merge request assignees relation upon MR assignee change' do - another_user = create(:user) - merge_request = create(:merge_request, assignee: user) - - expect { merge_request.update!(assignee: another_user) } - .to change { merge_request.reload.merge_request_assignees.first.assignee } - .from(user).to(another_user) - - expect(merge_request.merge_request_assignees.count).to eq(1) - end - end end describe 'respond to' do @@ -337,34 +312,18 @@ describe MergeRequest do describe '#card_attributes' do it 'includes the author name' do allow(subject).to receive(:author).and_return(double(name: 'Robert')) - allow(subject).to receive(:assignee).and_return(nil) + allow(subject).to receive(:assignees).and_return([]) expect(subject.card_attributes) - .to eq({ 'Author' => 'Robert', 'Assignee' => nil }) + .to eq({ 'Author' => 'Robert', 'Assignee' => "" }) end - it 'includes the assignee name' do + it 'includes the assignees name' do allow(subject).to receive(:author).and_return(double(name: 'Robert')) - allow(subject).to receive(:assignee).and_return(double(name: 'Douwe')) + allow(subject).to receive(:assignees).and_return([double(name: 'Douwe'), double(name: 'Robert')]) expect(subject.card_attributes) - .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' }) - end - end - - describe '#assignee_ids' do - it 'returns an array of the assigned user id' do - subject.assignee_id = 123 - - expect(subject.assignee_ids).to eq([123]) - end - end - - describe '#assignee_ids=' do - it 'sets assignee_id to the last id in the array' do - subject.assignee_ids = [123, 456] - - expect(subject.assignee_id).to eq(456) + .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe and Robert' }) end end @@ -372,7 +331,7 @@ describe MergeRequest do let(:user) { create(:user) } it 'returns true for a user that is assigned to a merge request' do - subject.assignee = user + subject.assignees = [user] expect(subject.assignee_or_author?(user)).to eq(true) end @@ -1949,15 +1908,14 @@ describe MergeRequest do it 'updates when assignees change' do user1 = create(:user) user2 = create(:user) - mr = create(:merge_request, assignee: user1) + mr = create(:merge_request, assignees: [user1]) mr.project.add_developer(user1) mr.project.add_developer(user2) expect(user1.assigned_open_merge_requests_count).to eq(1) expect(user2.assigned_open_merge_requests_count).to eq(0) - mr.assignee = user2 - mr.save + mr.assignees = [user2] expect(user1.assigned_open_merge_requests_count).to eq(0) expect(user2.assigned_open_merge_requests_count).to eq(1) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a45a2737b13..d1338e34bb8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2816,9 +2816,9 @@ describe User do project = create(:project, :public) archived_project = create(:project, :public, :archived) - create(:merge_request, source_project: project, author: user, assignee: user) - create(:merge_request, :closed, source_project: project, author: user, assignee: user) - create(:merge_request, source_project: archived_project, author: user, assignee: user) + create(:merge_request, source_project: project, author: user, assignees: [user]) + create(:merge_request, :closed, source_project: project, author: user, assignees: [user]) + create(:merge_request, source_project: archived_project, author: user, assignees: [user]) expect(user.assigned_open_merge_requests_count(force: true)).to eq 1 end diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index 0ac23505de7..065b16c6221 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -270,8 +270,8 @@ describe API::Events do end context 'when exists some events' do - let(:merge_request1) { create(:merge_request, :closed, author: user, assignee: user, source_project: private_project, title: 'Test') } - let(:merge_request2) { create(:merge_request, :closed, author: user, assignee: user, source_project: private_project, title: 'Test') } + let(:merge_request1) { create(:merge_request, :closed, author: user, assignees: [user], source_project: private_project, title: 'Test') } + let(:merge_request2) { create(:merge_request, :closed, author: user, assignees: [user], source_project: private_project, title: 'Test') } before do create_event(merge_request1) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 7ffa365c651..45818edbf68 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -5,14 +5,15 @@ describe API::MergeRequests do let(:base_time) { Time.now } set(:user) { create(:user) } + set(:user2) { create(:user) } set(:admin) { create(:user, :admin) } let(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) } let(:milestone) { create(:milestone, title: '1.0.0', project: project) } let(:milestone1) { create(:milestone, title: '0.9', project: project) } - let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) } - let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } - let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } - let!(:merge_request_locked) { create(:merge_request, state: "locked", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Locked test", created_at: base_time + 1.second) } + let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Test", created_at: base_time) } + let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } + let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignees: [user], source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } + let!(:merge_request_locked) { create(:merge_request, state: "locked", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Locked test", created_at: base_time + 1.second) } let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } let(:label) { create(:label, title: 'label', color: '#FFAABB', project: project) } @@ -20,6 +21,9 @@ describe API::MergeRequests do before do project.add_reporter(user) + project.add_reporter(user2) + + stub_licensed_features(multiple_merge_request_assignees: false) end shared_context 'with labels' do @@ -45,9 +49,9 @@ describe API::MergeRequests do get api(endpoint_path, user) end - create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time) + create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: 'Test', created_at: base_time) - merge_request = create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time) + merge_request = create(:merge_request, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: 'Test', created_at: base_time) merge_request.metrics.update!(merged_by: user, latest_closed_by: user, @@ -333,7 +337,7 @@ describe API::MergeRequests do state: 'closed', milestone: milestone1, author: user, - assignee: user, + assignees: [user], source_project: project, target_project: project, title: "Test", @@ -451,7 +455,7 @@ describe API::MergeRequests do context 'when authenticated' do let!(:project2) { create(:project, :public, namespace: user.namespace) } - let!(:merge_request2) { create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2) } + let!(:merge_request2) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project2, target_project: project2) } let(:user2) { create(:user) } it 'returns an array of all merge requests except unauthorized ones' do @@ -494,7 +498,7 @@ describe API::MergeRequests do end it 'returns an array of merge requests created by current user if no scope is given' do - merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch') get api('/merge_requests', user2) @@ -502,7 +506,7 @@ describe API::MergeRequests do end it 'returns an array of merge requests authored by the given user' do - merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch') get api('/merge_requests', user), params: { author_id: user2.id, scope: :all } @@ -510,7 +514,7 @@ describe API::MergeRequests do end it 'returns an array of merge requests assigned to the given user' do - merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, source_branch: 'other-branch') get api('/merge_requests', user), params: { assignee_id: user2.id, scope: :all } @@ -535,7 +539,7 @@ describe API::MergeRequests do end it 'returns an array of merge requests assigned to me' do - merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, source_branch: 'other-branch') get api('/merge_requests', user2), params: { scope: 'assigned_to_me' } @@ -543,7 +547,7 @@ describe API::MergeRequests do end it 'returns an array of merge requests assigned to me (kebab-case)' do - merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, source_branch: 'other-branch') get api('/merge_requests', user2), params: { scope: 'assigned-to-me' } @@ -551,7 +555,7 @@ describe API::MergeRequests do end it 'returns an array of merge requests created by me' do - merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch') get api('/merge_requests', user2), params: { scope: 'created_by_me' } @@ -559,7 +563,7 @@ describe API::MergeRequests do end it 'returns an array of merge requests created by me (kebab-case)' do - merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch') get api('/merge_requests', user2), params: { scope: 'created-by-me' } @@ -567,7 +571,7 @@ describe API::MergeRequests do end it 'returns merge requests reacted by the authenticated user by the given emoji' do - merge_request3 = create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + merge_request3 = create(:merge_request, :simple, author: user, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch') award_emoji = create(:award_emoji, awardable: merge_request3, user: user2, name: 'star') get api('/merge_requests', user2), params: { my_reaction_emoji: award_emoji.name, scope: 'all' } @@ -700,7 +704,7 @@ describe API::MergeRequests do get api("/projects/#{project.id}/merge_requests", user) end.count - create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, created_at: base_time) + create(:merge_request, author: user, assignees: [user], source_project: project, target_project: project, created_at: base_time) expect do get api("/projects/#{project.id}/merge_requests", user) @@ -730,7 +734,7 @@ describe API::MergeRequests do describe "GET /projects/:id/merge_requests/:merge_request_iid" do it 'matches json schema' do - merge_request = create(:merge_request, :with_test_reports, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) + merge_request = create(:merge_request, :with_test_reports, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Test", created_at: base_time) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user) expect(response).to have_gitlab_http_status(200) @@ -851,7 +855,7 @@ describe API::MergeRequests do end context 'Work in Progress' do - let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) } + let!(:merge_request_wip) { create(:merge_request, author: user, assignees: [user], source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) } it "returns merge request" do get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user) @@ -867,7 +871,7 @@ describe API::MergeRequests do merge_request_overflow = create(:merge_request, :simple, author: user, - assignee: user, + assignees: [user], source_project: project, source_branch: 'expand-collapse-files', target_project: project, @@ -1005,6 +1009,71 @@ describe API::MergeRequests do end describe 'POST /projects/:id/merge_requests' do + context 'support for deprecated assignee_id' do + let(:params) do + { + title: 'Test merge request', + source_branch: 'feature_conflict', + target_branch: 'master', + author_id: user.id, + assignee_id: user2.id + } + end + + it 'creates a new merge request' do + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('Test merge request') + expect(json_response['assignee']['name']).to eq(user2.name) + expect(json_response['assignees'].first['name']).to eq(user2.name) + end + + it 'creates a new merge request when assignee_id is empty' do + params[:assignee_id] = '' + + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('Test merge request') + expect(json_response['assignee']).to be_nil + end + + it 'filters assignee_id of unauthorized user' do + private_project = create(:project, :private, :repository) + another_user = create(:user) + private_project.add_maintainer(user) + params[:assignee_id] = another_user.id + + post api("/projects/#{private_project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['assignee']).to be_nil + end + end + + context 'single assignee restrictions' do + let(:params) do + { + title: 'Test merge request', + source_branch: 'feature_conflict', + target_branch: 'master', + author_id: user.id, + assignee_ids: [user.id, user2.id] + } + end + + it 'creates a new project merge request with no more than one assignee' do + post api("/projects/#{project.id}/merge_requests", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('Test merge request') + expect(json_response['assignees'].count).to eq(1) + expect(json_response['assignees'].first['name']).to eq(user.name) + expect(json_response.dig('assignee', 'name')).to eq(user.name) + end + end + context 'between branches projects' do context 'different labels' do let(:params) do @@ -1574,6 +1643,19 @@ describe API::MergeRequests do expect(json_response['force_remove_source_branch']).to be_truthy end + it 'filters assignee_id of unauthorized user' do + private_project = create(:project, :private, :repository) + mr = create(:merge_request, source_project: private_project, target_project: private_project) + another_user = create(:user) + private_project.add_maintainer(user) + params = { assignee_id: another_user.id } + + put api("/projects/#{private_project.id}/merge_requests/#{mr.iid}", user), params: params + + expect(response).to have_gitlab_http_status(200) + expect(json_response['assignee']).to be_nil + end + context 'when updating labels' do it 'allows special label names' do put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), @@ -1728,7 +1810,7 @@ describe API::MergeRequests do issue = create(:issue, project: jira_project) description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}" merge_request = create(:merge_request, - :simple, author: user, assignee: user, source_project: jira_project, description: description) + :simple, author: user, assignees: [user], source_project: jira_project, description: description) get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user) diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index ca366cdf1df..363b7266940 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -76,14 +76,14 @@ describe Issuable::BulkUpdateService do end describe 'updating merge request assignee' do - let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignee: user) } + let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) } context 'when the new assignee ID is a valid user' do it 'succeeds' do new_assignee = create(:user) project.add_developer(new_assignee) - result = bulk_update(merge_request, assignee_id: new_assignee.id) + result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id]) expect(result[:success]).to be_truthy expect(result[:count]).to eq(1) @@ -93,22 +93,22 @@ describe Issuable::BulkUpdateService do assignee = create(:user) project.add_developer(assignee) - expect { bulk_update(merge_request, assignee_id: assignee.id) } - .to change { merge_request.reload.assignee }.from(user).to(assignee) + expect { bulk_update(merge_request, assignee_ids: [assignee.id]) } + .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id]) end end context "when the new assignee ID is #{IssuableFinder::NONE}" do it 'unassigns the issues' do - expect { bulk_update(merge_request, assignee_id: IssuableFinder::NONE) } - .to change { merge_request.reload.assignee }.to(nil) + expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) } + .to change { merge_request.reload.assignee_ids }.to([]) end end context 'when the new assignee ID is not present' do it 'does not unassign' do - expect { bulk_update(merge_request, assignee_id: nil) } - .not_to change { merge_request.reload.assignee } + expect { bulk_update(merge_request, assignee_ids: []) } + .not_to change { merge_request.reload.assignee_ids } end end end diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb index 8ccbba7fa58..15d1bb73ca3 100644 --- a/spec/services/issuable/destroy_service_spec.rb +++ b/spec/services/issuable/destroy_service_spec.rb @@ -34,7 +34,7 @@ describe Issuable::DestroyService do end context 'when issuable is a merge request' do - let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignee: user) } + let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignees: [user]) } it 'destroys the merge request' do expect { service.execute(merge_request) }.to change { project.merge_requests.count }.by(-1) diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index d37ca13ebd2..91bf4dccd77 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -43,9 +43,9 @@ describe Members::DestroyService do shared_examples 'a service destroying a member with access' do it_behaves_like 'a service destroying a member' - it 'invalidates cached counts for todos and assigned issues and merge requests', :aggregate_failures do + it 'invalidates cached counts for assigned issues and merge requests', :aggregate_failures do create(:issue, project: group_project, assignees: [member_user]) - create(:merge_request, source_project: group_project, assignee: member_user) + create(:merge_request, source_project: group_project, assignees: [member_user]) create(:todo, :pending, project: group_project, user: member_user) create(:todo, :done, project: group_project, user: member_user) diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 433ffbd97f0..706bcea8199 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -4,7 +4,7 @@ describe MergeRequests::CloseService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:guest) { create(:user) } - let(:merge_request) { create(:merge_request, assignee: user2, author: create(:user)) } + let(:merge_request) { create(:merge_request, assignees: [user2], author: create(:user)) } let(:project) { merge_request.project } let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) } diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb index 393299cce00..20bf1cbb8b6 100644 --- a/spec/services/merge_requests/create_from_issue_service_spec.rb +++ b/spec/services/merge_requests/create_from_issue_service_spec.rb @@ -118,7 +118,7 @@ describe MergeRequests::CreateFromIssueService do result = service.execute - expect(result[:merge_request].assignee).to eq(user) + expect(result[:merge_request].assignees).to eq([user]) end context 'when ref branch is set' do diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index dc5d1cf2f04..30271e04c8e 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -32,7 +32,7 @@ describe MergeRequests::CreateService do expect(merge_request).to be_valid expect(merge_request.work_in_progress?).to be(false) expect(merge_request.title).to eq('Awesome merge_request') - expect(merge_request.assignee).to be_nil + expect(merge_request.assignees).to be_empty expect(merge_request.merge_params['force_remove_source_branch']).to eq('1') end @@ -73,7 +73,7 @@ describe MergeRequests::CreateService do description: "well this is not done yet\n/wip", source_branch: 'feature', target_branch: 'master', - assignee: assignee + assignees: [assignee] } end @@ -89,7 +89,7 @@ describe MergeRequests::CreateService do description: "well this is not done yet\n/wip", source_branch: 'feature', target_branch: 'master', - assignee: assignee + assignees: [assignee] } end @@ -106,11 +106,11 @@ describe MergeRequests::CreateService do description: 'please fix', source_branch: 'feature', target_branch: 'master', - assignee: assignee + assignees: [assignee] } end - it { expect(merge_request.assignee).to eq assignee } + it { expect(merge_request.assignees).to eq([assignee]) } it 'creates a todo for new assignee' do attributes = { @@ -301,7 +301,7 @@ describe MergeRequests::CreateService do let(:opts) do { - assignee_id: create(:user).id, + assignee_ids: create(:user).id, milestone_id: 1, title: 'Title', description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}"), @@ -317,7 +317,7 @@ describe MergeRequests::CreateService do it 'assigns and sets milestone to issuable from command' do expect(merge_request).to be_persisted - expect(merge_request.assignee).to eq(assignee) + expect(merge_request.assignees).to eq([assignee]) expect(merge_request.milestone).to eq(milestone) end end @@ -332,28 +332,28 @@ describe MergeRequests::CreateService do end it 'removes assignee_id when user id is invalid' do - opts = { title: 'Title', description: 'Description', assignee_id: -1 } + opts = { title: 'Title', description: 'Description', assignee_ids: [-1] } merge_request = described_class.new(project, user, opts).execute - expect(merge_request.assignee_id).to be_nil + expect(merge_request.assignee_ids).to be_empty end it 'removes assignee_id when user id is 0' do - opts = { title: 'Title', description: 'Description', assignee_id: 0 } + opts = { title: 'Title', description: 'Description', assignee_ids: [0] } merge_request = described_class.new(project, user, opts).execute - expect(merge_request.assignee_id).to be_nil + expect(merge_request.assignee_ids).to be_empty end it 'saves assignee when user id is valid' do project.add_maintainer(assignee) - opts = { title: 'Title', description: 'Description', assignee_id: assignee.id } + opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] } merge_request = described_class.new(project, user, opts).execute - expect(merge_request.assignee).to eq(assignee) + expect(merge_request.assignees).to eq([assignee]) end context 'when assignee is set' do @@ -361,7 +361,7 @@ describe MergeRequests::CreateService do { title: 'Title', description: 'Description', - assignee_id: assignee.id, + assignee_ids: [assignee.id], source_branch: 'feature', target_branch: 'master' } @@ -387,7 +387,7 @@ describe MergeRequests::CreateService do levels.each do |level| it "removes not authorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do project.update(visibility_level: level) - opts = { title: 'Title', description: 'Description', assignee_id: assignee.id } + opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] } merge_request = described_class.new(project, user, opts).execute diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index 1430e12a07e..a87d8b8752c 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -7,7 +7,7 @@ describe MergeRequests::FfMergeService do create(:merge_request, source_branch: 'flatten-dir', target_branch: 'improve/awesome', - assignee: user2, + assignees: [user2], author: create(:user)) end let(:project) { merge_request.project } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 887ec17171e..b0b3273e3dc 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::MergeService do set(:user) { create(:user) } set(:user2) { create(:user) } - let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) } + let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) } let(:project) { merge_request.project } before do @@ -111,7 +111,7 @@ describe MergeRequests::MergeService do end context 'closes related todos' do - let(:merge_request) { create(:merge_request, assignee: user, author: user) } + let(:merge_request) { create(:merge_request, assignees: [user], author: user) } let(:project) { merge_request.project } let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } let!(:todo) do diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index a3b48abae26..24d09c1fd00 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -149,7 +149,7 @@ describe MergeRequests::MergeToRefService do end context 'does not close related todos' do - let(:merge_request) { create(:merge_request, assignee: user, author: user) } + let(:merge_request) { create(:merge_request, assignees: [user], author: user) } let(:project) { merge_request.project } let!(:todo) do create(:todo, :assigned, diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index 5ad6f5528f9..2cebefee5d6 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequests::PostMergeService do let(:user) { create(:user) } - let(:merge_request) { create(:merge_request, assignee: user) } + let(:merge_request) { create(:merge_request, assignees: [user]) } let(:project) { merge_request.project } before do diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index 21e71509ed6..8b6db1ce33e 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -4,7 +4,7 @@ describe MergeRequests::ReopenService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:guest) { create(:user) } - let(:merge_request) { create(:merge_request, :closed, assignee: user2, author: create(:user)) } + let(:merge_request) { create(:merge_request, :closed, assignees: [user2], author: create(:user)) } let(:project) { merge_request.project } before do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 8e367db031c..0525899ebfa 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -13,7 +13,7 @@ describe MergeRequests::UpdateService, :mailer do let(:merge_request) do create(:merge_request, :simple, title: 'Old title', description: "FYI #{user2.to_reference}", - assignee_id: user3.id, + assignee_ids: [user3.id], source_project: project, author: create(:user)) end @@ -48,7 +48,7 @@ describe MergeRequests::UpdateService, :mailer do { title: 'New title', description: 'Also please fix', - assignee_id: user2.id, + assignee_ids: [user.id], state_event: 'close', label_ids: [label.id], target_branch: 'target', @@ -71,7 +71,7 @@ describe MergeRequests::UpdateService, :mailer do it 'matches base expectations' do expect(@merge_request).to be_valid expect(@merge_request.title).to eq('New title') - expect(@merge_request.assignee).to eq(user2) + expect(@merge_request.assignees).to match_array([user]) expect(@merge_request).to be_closed expect(@merge_request.labels.count).to eq(1) expect(@merge_request.labels.first.title).to eq(label.name) @@ -106,7 +106,7 @@ describe MergeRequests::UpdateService, :mailer do note = find_note('assigned to') expect(note).not_to be_nil - expect(note.note).to include "assigned to #{user2.to_reference}" + expect(note.note).to include "assigned to #{user.to_reference} and unassigned #{user3.to_reference}" end it 'creates a resource label event' do @@ -293,7 +293,7 @@ describe MergeRequests::UpdateService, :mailer do context 'when is reassigned' do before do - update_merge_request({ assignee: user2 }) + update_merge_request({ assignee_ids: [user2.id] }) end it 'marks previous assignee pending todos as done' do @@ -387,7 +387,7 @@ describe MergeRequests::UpdateService, :mailer do context 'when the assignee changes' do it 'updates open merge request counter for assignees when merge request is reassigned' do - update_merge_request(assignee_id: user2.id) + update_merge_request(assignee_ids: [user2.id]) expect(user3.assigned_open_merge_requests_count).to eq 0 expect(user2.assigned_open_merge_requests_count).to eq 1 @@ -541,36 +541,36 @@ describe MergeRequests::UpdateService, :mailer do end end - context 'updating asssignee_id' do + context 'updating asssignee_ids' do it 'does not update assignee when assignee_id is invalid' do - merge_request.update(assignee_id: user.id) + merge_request.update(assignee_ids: [user.id]) - update_merge_request(assignee_id: -1) + update_merge_request(assignee_ids: [-1]) - expect(merge_request.reload.assignee).to eq(user) + expect(merge_request.reload.assignees).to eq([user]) end it 'unassigns assignee when user id is 0' do - merge_request.update(assignee_id: user.id) + merge_request.update(assignee_ids: [user.id]) - update_merge_request(assignee_id: 0) + update_merge_request(assignee_ids: [0]) - expect(merge_request.assignee_id).to be_nil + expect(merge_request.assignee_ids).to be_empty end it 'saves assignee when user id is valid' do - update_merge_request(assignee_id: user.id) + update_merge_request(assignee_ids: [user.id]) - expect(merge_request.assignee_id).to eq(user.id) + expect(merge_request.assignee_ids).to eq([user.id]) end it 'does not update assignee_id when user cannot read issue' do - non_member = create(:user) - original_assignee = merge_request.assignee + non_member = create(:user) + original_assignees = merge_request.assignees - update_merge_request(assignee_id: non_member.id) + update_merge_request(assignee_ids: [non_member.id]) - expect(merge_request.assignee_id).to eq(original_assignee.id) + expect(merge_request.reload.assignees).to eq(original_assignees) end context "when issuable feature is private" do @@ -583,7 +583,7 @@ describe MergeRequests::UpdateService, :mailer do feature_visibility_attr = :"#{merge_request.model_name.plural}_access_level" project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE) - expect { update_merge_request(assignee_id: assignee) }.not_to change { merge_request.assignee } + expect { update_merge_request(assignee_ids: [assignee]) }.not_to change { merge_request.reload.assignees } end end end @@ -619,7 +619,7 @@ describe MergeRequests::UpdateService, :mailer do end it 'is allowed by a user that can push to the source and can update the merge request' do - merge_request.update!(assignee: user) + merge_request.update!(assignees: [user]) source_project.add_developer(user) update_merge_request(allow_collaboration: true, title: 'Updated title') diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 9ba4a11104a..14c73852e65 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -125,11 +125,7 @@ describe NotificationService, :mailer do shared_examples 'participating by assignee notification' do it 'emails the participant' do - if issuable.is_a?(Issue) - issuable.assignees << participant - else - issuable.update_attribute(:assignee, participant) - end + issuable.assignees << participant notification_trigger @@ -620,13 +616,13 @@ describe NotificationService, :mailer do context "merge request diff note" do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - let(:merge_request) { create(:merge_request, source_project: project, assignee: user, author: create(:user)) } + let(:merge_request) { create(:merge_request, source_project: project, assignees: [user], author: create(:user)) } let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) } before do build_team(note.project) project.add_maintainer(merge_request.author) - project.add_maintainer(merge_request.assignee) + merge_request.assignees.each { |assignee| project.add_maintainer(assignee) } end describe '#new_note' do @@ -637,7 +633,7 @@ describe NotificationService, :mailer do notification.new_note(note) expect(SentNotification.last(3).map(&:recipient).map(&:id)) - .to contain_exactly(merge_request.assignee.id, merge_request.author.id, @u_watcher.id) + .to contain_exactly(*merge_request.assignees.pluck(:id), merge_request.author.id, @u_watcher.id) expect(SentNotification.last.in_reply_to_discussion_id).to eq(note.discussion_id) end end @@ -1223,11 +1219,12 @@ describe NotificationService, :mailer do let(:group) { create(:group) } let(:project) { create(:project, :public, :repository, namespace: group) } let(:another_project) { create(:project, :public, namespace: group) } - let(:merge_request) { create :merge_request, source_project: project, assignee: create(:user), description: 'cc @participant' } + let(:assignee) { create(:user) } + let(:merge_request) { create :merge_request, source_project: project, assignees: [assignee], description: 'cc @participant' } before do project.add_maintainer(merge_request.author) - project.add_maintainer(merge_request.assignee) + merge_request.assignees.each { |assignee| project.add_maintainer(assignee) } build_team(merge_request.target_project) add_users_with_subscription(merge_request.target_project, merge_request) update_custom_notification(:new_merge_request, @u_guest_custom, resource: project) @@ -1239,7 +1236,7 @@ describe NotificationService, :mailer do it do notification.new_merge_request(merge_request, @u_disabled) - should_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_email(assignee) } should_email(@u_watcher) should_email(@watcher_and_subscriber) should_email(@u_participant_mentioned) @@ -1254,9 +1251,11 @@ describe NotificationService, :mailer do it 'adds "assigned" reason for assignee, if any' do notification.new_merge_request(merge_request, @u_disabled) - email = find_email_for(merge_request.assignee) + merge_request.assignees.each do |assignee| + email = find_email_for(assignee) - expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) + expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) + end end it "emails any mentioned users with the mention level" do @@ -1347,9 +1346,9 @@ describe NotificationService, :mailer do end it do - notification.reassigned_merge_request(merge_request, current_user, merge_request.author) + notification.reassigned_merge_request(merge_request, current_user, [assignee]) - should_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_email(assignee) } should_email(merge_request.author) should_email(@u_watcher) should_email(@u_participant_mentioned) @@ -1365,17 +1364,19 @@ describe NotificationService, :mailer do end it 'adds "assigned" reason for new assignee' do - notification.reassigned_merge_request(merge_request, current_user, merge_request.author) + notification.reassigned_merge_request(merge_request, current_user, [assignee]) - email = find_email_for(merge_request.assignee) + merge_request.assignees.each do |assignee| + email = find_email_for(assignee) - expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) + expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) + end end it_behaves_like 'participating notifications' do let(:participant) { create(:user, username: 'user-participant') } let(:issuable) { merge_request } - let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, merge_request.author) } + let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, [assignee]) } end end @@ -1388,7 +1389,7 @@ describe NotificationService, :mailer do it do notification.push_to_merge_request(merge_request, @u_disabled) - should_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_email(assignee) } should_email(@u_guest_custom) should_email(@u_custom_global) should_email(@u_participant_mentioned) @@ -1430,7 +1431,7 @@ describe NotificationService, :mailer do should_email(subscriber_1_to_group_label_2) should_email(subscriber_2_to_group_label_2) should_email(subscriber_to_label_2) - should_not_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_not_email(assignee) } should_not_email(merge_request.author) should_not_email(@u_watcher) should_not_email(@u_participant_mentioned) @@ -1499,7 +1500,7 @@ describe NotificationService, :mailer do it do notification.close_mr(merge_request, @u_disabled) - should_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_email(assignee) } should_email(@u_watcher) should_email(@u_guest_watcher) should_email(@u_guest_custom) @@ -1529,7 +1530,7 @@ describe NotificationService, :mailer do it do notification.merge_mr(merge_request, @u_disabled) - should_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_email(assignee) } should_email(@u_watcher) should_email(@u_guest_watcher) should_email(@u_guest_custom) @@ -1581,7 +1582,7 @@ describe NotificationService, :mailer do it do notification.reopen_mr(merge_request, @u_disabled) - should_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_email(assignee) } should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) @@ -1606,7 +1607,7 @@ describe NotificationService, :mailer do it do notification.resolve_all_discussions(merge_request, @u_disabled) - should_email(merge_request.assignee) + merge_request.assignees.each { |assignee| should_email(assignee) } should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) @@ -1850,8 +1851,8 @@ describe NotificationService, :mailer do let(:guest) { create(:user) } let(:developer) { create(:user) } let(:assignee) { create(:user) } - let(:merge_request) { create(:merge_request, source_project: private_project, assignee: assignee) } - let(:merge_request1) { create(:merge_request, source_project: private_project, assignee: assignee, description: "cc @#{guest.username}") } + let(:merge_request) { create(:merge_request, source_project: private_project, assignees: [assignee]) } + let(:merge_request1) { create(:merge_request, source_project: private_project, assignees: [assignee], description: "cc @#{guest.username}") } let(:note) { create(:note, noteable: merge_request, project: private_project) } before do diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index c7e5cca324f..c450f89c3cb 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -16,7 +16,9 @@ describe QuickActions::InterpretService do let(:service) { described_class.new(project, developer) } before do - stub_licensed_features(multiple_issue_assignees: false) + stub_licensed_features(multiple_issue_assignees: false, + multiple_merge_request_assignees: false) + project.add_developer(developer) end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 13d7d795703..51c5a803dbd 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -166,8 +166,8 @@ describe SystemNoteService do end end - describe '.change_issue_assignees' do - subject { described_class.change_issue_assignees(noteable, project, author, [assignee]) } + describe '.change_issuable_assignees' do + subject { described_class.change_issuable_assignees(noteable, project, author, [assignee]) } let(:assignee) { create(:user) } let(:assignee1) { create(:user) } @@ -180,7 +180,7 @@ describe SystemNoteService do def build_note(old_assignees, new_assignees) issue.assignees = new_assignees - described_class.change_issue_assignees(issue, project, author, old_assignees).note + described_class.change_issuable_assignees(issue, project, author, old_assignees).note end it_behaves_like 'a note with overridable created_at' diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 8631f3f9a33..89411b2e908 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -272,28 +272,6 @@ describe TodoService do end end - describe '#reassigned_issue' do - it 'creates a pending todo for new assignee' do - unassigned_issue.assignees << john_doe - service.reassigned_issue(unassigned_issue, author) - - should_create_todo(user: john_doe, target: unassigned_issue, action: Todo::ASSIGNED) - end - - it 'does not create a todo if unassigned' do - issue.assignees.destroy_all # rubocop: disable DestroyAll - - should_not_create_any_todo { service.reassigned_issue(issue, author) } - end - - it 'creates a todo if new assignee is the current user' do - unassigned_issue.assignees << john_doe - service.reassigned_issue(unassigned_issue, john_doe) - - should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED) - end - end - describe '#mark_pending_todos_as_done' do it 'marks related pending todos to the target for the user as done' do first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) @@ -504,10 +482,60 @@ describe TodoService do end end + describe '#reassigned_issuable' do + shared_examples 'reassigned issuable' do + it 'creates a pending todo for new assignee' do + issuable_unassigned.assignees = [john_doe] + service.reassigned_issuable(issuable_unassigned, author) + + should_create_todo(user: john_doe, target: issuable_unassigned, action: Todo::ASSIGNED) + end + + it 'does not create a todo if unassigned' do + issuable_assigned.assignees = [] + + should_not_create_any_todo { service.reassigned_issuable(issuable_assigned, author) } + end + + it 'creates a todo if new assignee is the current user' do + issuable_assigned.assignees = [john_doe] + service.reassigned_issuable(issuable_assigned, john_doe) + + should_create_todo(user: john_doe, target: issuable_assigned, author: john_doe, action: Todo::ASSIGNED) + end + + it 'does not create a todo for guests' do + service.reassigned_issuable(issuable_assigned, author) + should_not_create_todo(user: guest, target: issuable_assigned, action: Todo::MENTIONED) + end + + it 'does not create a directly addressed todo for guests' do + service.reassigned_issuable(addressed_issuable_assigned, author) + should_not_create_todo(user: guest, target: addressed_issuable_assigned, action: Todo::DIRECTLY_ADDRESSED) + end + end + + context 'issuable is a merge request' do + it_behaves_like 'reassigned issuable' do + let(:issuable_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } + let(:addressed_issuable_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } + let(:issuable_unassigned) { create(:merge_request, source_project: project, author: author, assignees: []) } + end + end + + context 'issuable is an issue' do + it_behaves_like 'reassigned issuable' do + let(:issuable_assigned) { create(:issue, project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } + let(:addressed_issuable_assigned) { create(:issue, project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } + let(:issuable_unassigned) { create(:issue, project: project, author: author, assignees: []) } + end + end + end + describe 'Merge Requests' do - let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } - let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } - let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) } + let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } + let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } + let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignees: []) } describe '#new_merge_request' do it 'creates a pending todo if assigned' do @@ -659,38 +687,6 @@ describe TodoService do end end - describe '#reassigned_merge_request' do - it 'creates a pending todo for new assignee' do - mr_unassigned.update_attribute(:assignee, john_doe) - service.reassigned_merge_request(mr_unassigned, author) - - should_create_todo(user: john_doe, target: mr_unassigned, action: Todo::ASSIGNED) - end - - it 'does not create a todo if unassigned' do - mr_assigned.update_attribute(:assignee, nil) - - should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) } - end - - it 'creates a todo if new assignee is the current user' do - mr_assigned.update_attribute(:assignee, john_doe) - service.reassigned_merge_request(mr_assigned, john_doe) - - should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED) - end - - it 'does not create a todo for guests' do - service.reassigned_merge_request(mr_assigned, author) - should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) - end - - it 'does not create a directly addressed todo for guests' do - service.reassigned_merge_request(addressed_mr_assigned, author) - should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - end - end - describe '#merge_merge_request' do it 'marks related pending todos to the target for the user as done' do first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author) diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 83f1495a1c6..450e76d5f58 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -78,7 +78,7 @@ describe Users::DestroyService do end context "for an merge request the user was assigned to" do - let!(:merge_request) { create(:merge_request, source_project: project, assignee: user) } + let!(:merge_request) { create(:merge_request, source_project: project, assignees: [user]) } before do service.execute(user) @@ -91,7 +91,7 @@ describe Users::DestroyService do it 'migrates the merge request so that it is "Unassigned"' do migrated_merge_request = MergeRequest.find_by_id(merge_request.id) - expect(migrated_merge_request.assignee).to be_nil + expect(migrated_merge_request.assignees).to be_empty end end end diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb index 4df80b4168a..ab6687f1d07 100644 --- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb @@ -46,9 +46,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests allow_gitaly_n_plus_1 { create(:project, group: subgroup) } end - let!(:merge_request1) { create(:merge_request, author: user, source_project: project2, target_project: project1, target_branch: 'merged-target') } - let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } - let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } + let!(:merge_request1) { create(:merge_request, assignees: [user], author: user, source_project: project2, target_project: project1, target_branch: 'merged-target') } + let!(:merge_request2) { create(:merge_request, :conflict, assignees: [user], author: user, source_project: project2, target_project: project1, state: 'closed') } + let!(:merge_request3) { create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') } let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') } diff --git a/spec/support/shared_contexts/merge_request_create.rb b/spec/support/shared_contexts/merge_request_create.rb new file mode 100644 index 00000000000..529f481c2b6 --- /dev/null +++ b/spec/support/shared_contexts/merge_request_create.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +shared_context 'merge request create context' do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:target_project) { create(:project, :public, :repository) } + let(:source_project) { target_project } + let!(:milestone) { create(:milestone, project: target_project) } + let!(:label) { create(:label, project: target_project) } + let!(:label2) { create(:label, project: target_project) } + + before do + source_project.add_maintainer(user) + target_project.add_maintainer(user) + target_project.add_maintainer(user2) + + sign_in(user) + visit project_new_merge_request_path(target_project, + merge_request: { + source_project_id: source_project.id, + target_project_id: target_project.id, + source_branch: 'fix', + target_branch: 'master' + }) + end +end diff --git a/spec/support/shared_contexts/merge_request_edit.rb b/spec/support/shared_contexts/merge_request_edit.rb new file mode 100644 index 00000000000..c84510ff47d --- /dev/null +++ b/spec/support/shared_contexts/merge_request_edit.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' +shared_context 'merge request edit context' do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:milestone) { create(:milestone, project: target_project) } + let!(:label) { create(:label, project: target_project) } + let!(:label2) { create(:label, project: target_project) } + let(:target_project) { create(:project, :public, :repository) } + let(:source_project) { target_project } + let(:merge_request) do + create(:merge_request, + source_project: source_project, + target_project: target_project, + source_branch: 'fix', + target_branch: 'master') + end + + before do + source_project.add_maintainer(user) + target_project.add_maintainer(user) + target_project.add_maintainer(user2) + + sign_in(user) + visit edit_project_merge_request_path(target_project, merge_request) + end +end diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb index 7038a366144..ec1b1754cf0 100644 --- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb @@ -1,42 +1,17 @@ RSpec.shared_examples 'a creatable merge request' do include WaitForRequests - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:target_project) { create(:project, :public, :repository) } - let(:source_project) { target_project } - let!(:milestone) { create(:milestone, project: target_project) } - let!(:label) { create(:label, project: target_project) } - let!(:label2) { create(:label, project: target_project) } - - before do - source_project.add_maintainer(user) - target_project.add_maintainer(user) - target_project.add_maintainer(user2) - - sign_in(user) - visit project_new_merge_request_path( - target_project, - merge_request: { - source_project_id: source_project.id, - target_project_id: target_project.id, - source_branch: 'fix', - target_branch: 'master' - }) - end - it 'creates new merge request', :js do - click_button 'Assignee' + find('.js-assignee-search').click page.within '.dropdown-menu-user' do click_link user2.name end - expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user2.id.to_s) + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user2.name end - click_link 'Assign to me' - expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user.name end diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb index eef0327c9a6..a6121fcc50a 100644 --- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb @@ -1,34 +1,10 @@ RSpec.shared_examples 'an editable merge request' do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let!(:milestone) { create(:milestone, project: target_project) } - let!(:label) { create(:label, project: target_project) } - let!(:label2) { create(:label, project: target_project) } - let(:target_project) { create(:project, :public, :repository) } - let(:source_project) { target_project } - let(:merge_request) do - create(:merge_request, - source_project: source_project, - target_project: target_project, - source_branch: 'fix', - target_branch: 'master') - end - - before do - source_project.add_maintainer(user) - target_project.add_maintainer(user) - target_project.add_maintainer(user2) - - sign_in(user) - visit edit_project_merge_request_path(target_project, merge_request) - end - it 'updates merge request', :js do - click_button 'Assignee' + find('.js-assignee-search').click page.within '.dropdown-menu-user' do click_link user.name end - expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user.name end diff --git a/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb new file mode 100644 index 00000000000..bab7963f06f --- /dev/null +++ b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +shared_examples 'multiple assignees merge request' do |action, save_button_title| + it "#{action} a MR with multiple assignees", :js do + find('.js-assignee-search').click + page.within '.dropdown-menu-user' do + click_link user.name + click_link user2.name + end + + # Extra click needed in order to toggle the dropdown + find('.js-assignee-search').click + + expect(all('input[name="merge_request[assignee_ids][]"]', visible: false).map(&:value)) + .to match_array([user.id.to_s, user2.id.to_s]) + + page.within '.js-assignee-search' do + expect(page).to have_content "#{user2.name} + 1 more" + end + + click_button save_button_title + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content '2 Assignees' + + click_link 'Edit' + + expect(page).to have_content user.name + expect(page).to have_content user2.name + end + end + + page.within '.dropdown-menu-user' do + click_link user.name + end + + page.within '.issuable-sidebar' do + page.within '.assignee' do + # Closing dropdown to persist + click_link 'Edit' + + expect(page).to have_content user2.name + end + end + end +end diff --git a/spec/support/shared_examples/finders/assignees_filter_spec.rb b/spec/support/shared_examples/finders/assignees_filter_spec.rb new file mode 100644 index 00000000000..782a2d97746 --- /dev/null +++ b/spec/support/shared_examples/finders/assignees_filter_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +shared_examples 'assignee ID filter' do + it 'returns issuables assigned to that user' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'assignee username filter' do + it 'returns issuables assigned to those users' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'no assignee filter' do + let(:params) { { assignee_id: 'None' } } + + it 'returns issuables not assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 0 + + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 'none' + + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'any assignee filter' do + context '' do + let(:params) { { assignee_id: 'Any' } } + + it 'returns issuables assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables assigned to any assignee' do + params[:assignee_id] = 'any' + + expect(issuables).to contain_exactly(*expected_issuables) + end + end +end diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index c13eab30054..529afa03f9c 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -17,7 +17,7 @@ describe 'projects/merge_requests/edit.html.haml' do source_project: forked_project, target_project: project, author: user, - assignee: user, + assignees: [user], milestone: milestone) end @@ -40,7 +40,7 @@ describe 'projects/merge_requests/edit.html.haml' do expect(rendered).to have_field('merge_request[title]') expect(rendered).to have_field('merge_request[description]') - expect(rendered).to have_selector('#merge_request_assignee_id', visible: false) + expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false) expect(rendered).to have_selector('#merge_request_milestone_id', visible: false) expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false) end @@ -52,7 +52,7 @@ describe 'projects/merge_requests/edit.html.haml' do expect(rendered).to have_field('merge_request[title]') expect(rendered).to have_field('merge_request[description]') - expect(rendered).to have_selector('#merge_request_assignee_id', visible: false) + expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false) expect(rendered).to have_selector('#merge_request_milestone_id', visible: false) expect(rendered).to have_selector('#merge_request_target_branch', visible: false) end diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index d9bda1a3414..23cb319a202 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -53,19 +53,6 @@ describe 'projects/merge_requests/show.html.haml' do expect(rendered).not_to have_css('.cannot-be-merged') end end - - context 'when assignee is not allowed to merge' do - it 'shows a warning icon' do - reporter = create(:user) - project.add_reporter(reporter) - closed_merge_request.update(assignee_id: reporter.id) - assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request)) - - render - - expect(rendered).to have_css('.cannot-be-merged') - end - end end context 'when the merge request is closed' do -- cgit v1.2.1 From 1883e320eafa02b332a16eec658f65c4a28def83 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Fri, 5 Apr 2019 17:19:30 +1300 Subject: Use Gitlab::PushOptions for `ci.skip` push option Previously the raw push option Array was sent to Pipeline::Chain::Skip. This commit updates this class (and the chain of classes that pass the push option parameters from the API internal `post_receive` endpoint to that class) to treat push options as a Hash of options parsed by GitLab::PushOptions. The GitLab::PushOptions class takes options like this: -o ci.skip -o merge_request.create -o merge_request.target=branch and turns them into a Hash like this: { ci: { skip: true }, merge_request: { create: true, target: 'branch' } } This now how Pipeline::Chain::Skip is determining if the `ci.skip` push option was used. --- spec/lib/gitlab/push_options_spec.rb | 12 ++++++++++++ spec/requests/api/internal_spec.rb | 2 +- spec/services/ci/create_pipeline_service_spec.rb | 3 +-- 3 files changed, 14 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/push_options_spec.rb b/spec/lib/gitlab/push_options_spec.rb index 68b64863c21..fc9e421bea6 100644 --- a/spec/lib/gitlab/push_options_spec.rb +++ b/spec/lib/gitlab/push_options_spec.rb @@ -39,6 +39,18 @@ describe Gitlab::PushOptions do end end + describe '#as_json' do + it 'returns all options' do + options = described_class.new(['merge_request.target=value']) + + expect(options.as_json).to include( + merge_request: { + target: 'value' + } + ) + end + end + it 'can parse multiple push options' do options = described_class.new([ 'merge_request.create', diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 3cc0cceeab1..a5a6e08e73b 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -905,7 +905,7 @@ describe API::Internal do it 'enqueues a PostReceive worker job' do expect(PostReceive).to receive(:perform_async) - .with(gl_repository, identifier, changes, push_options) + .with(gl_repository, identifier, changes, { ci: { skip: true } }) post api('/internal/post_receive'), params: valid_params end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 866d709d446..101b91e9cd8 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -418,8 +418,7 @@ describe Ci::CreatePipelineService do context 'when push options contain ci.skip' do let(:push_options) do - ['ci.skip', - 'another push option'] + { 'ci' => { 'skip' => true } } end it 'creates a pipline in the skipped state' do -- cgit v1.2.1 From 68f189ad23d7a384f40caa152d263fdf1465b30a Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Fri, 5 Apr 2019 13:22:58 +0000 Subject: Support merge on pipeline success w/ push options MergeRequests::PushOptionsHandlerService has been updated to allow creating and updating merge requests with the `merge_when_pipeline_succeeds` set using git push options. To create a new merge request and set it to merge when the pipeline succeeds: git push -u origin -o merge_request.create \ -o merge_request.merge_when_pipeline_succeeds To update an existing merge request and set it to merge when the pipeline succeeds: git push -u origin -o merge_request.merge_when_pipeline_succeeds Issue https://gitlab.com/gitlab-org/gitlab-ce/issues/53198 --- .../push_options_handler_service_spec.rb | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) (limited to 'spec') diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index 96becfcae26..eafe7a34617 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -66,6 +66,22 @@ describe MergeRequests::PushOptionsHandlerService do end end + shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do + subject(:last_mr) { MergeRequest.last } + + it 'sets merge_when_pipeline_succeeds' do + service.execute + + expect(last_mr.merge_when_pipeline_succeeds).to eq(true) + end + + it 'sets merge_user to the user' do + service.execute + + expect(last_mr.merge_user).to eq(user) + end + end + shared_examples_for 'a service that does not create a merge request' do it do expect { service.execute }.not_to change { MergeRequest.count } @@ -118,6 +134,72 @@ describe MergeRequests::PushOptionsHandlerService do end end + describe '`merge_when_pipeline_succeeds` push option' do + let(:push_options) { { merge_when_pipeline_succeeds: true } } + + context 'with a new branch' do + let(:changes) { new_branch_changes } + + it_behaves_like 'a service that does not create a merge request' + + it 'adds an error to the service' do + error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}" + + service.execute + + expect(service.errors).to include(error) + end + + context 'when coupled with the `create` push option' do + let(:push_options) { { create: true, merge_when_pipeline_succeeds: true } } + + it_behaves_like 'a service that can create a merge request' + it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds' + end + end + + context 'with an existing branch but no open MR' do + let(:changes) { existing_branch_changes } + + it_behaves_like 'a service that does not create a merge request' + + it 'adds an error to the service' do + error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}" + + service.execute + + expect(service.errors).to include(error) + end + + context 'when coupled with the `create` push option' do + let(:push_options) { { create: true, merge_when_pipeline_succeeds: true } } + + it_behaves_like 'a service that can create a merge request' + it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds' + end + end + + context 'with an existing branch that has a merge request open' do + let(:changes) { existing_branch_changes } + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)} + + it_behaves_like 'a service that does not create a merge request' + it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds' + end + + context 'with a deleted branch' do + let(:changes) { deleted_branch_changes } + + it_behaves_like 'a service that does nothing' + end + + context 'with the project default branch' do + let(:changes) { default_branch_changes } + + it_behaves_like 'a service that does nothing' + end + end + describe '`target` push option' do let(:push_options) { { target: target_branch } } -- cgit v1.2.1 From 8b8b466a7f6c1e24367fcb202239ddc5ea1bbfe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 9 Apr 2019 00:08:32 +0200 Subject: Use have_gitlab_http_status in runner_spec --- spec/requests/api/runner_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 5fdc7c64030..3585a827838 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1734,7 +1734,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end it 'download artifacts' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.headers.to_h).to include download_headers end end -- cgit v1.2.1 From 285fcb4744ed5464aab7d2ff248e7824dd77315c Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 8 Apr 2019 19:47:23 -0300 Subject: Add methods to check dead and retrying jobs It adds two methods for checking if a background job (for a given class) has dead or retrying jobs. --- spec/lib/gitlab/background_migration_spec.rb | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb index 7d3d8a949ef..1d0ffb5e9df 100644 --- a/spec/lib/gitlab/background_migration_spec.rb +++ b/spec/lib/gitlab/background_migration_spec.rb @@ -195,4 +195,44 @@ describe Gitlab::BackgroundMigration do end end end + + describe '.dead_jobs?' do + let(:queue) do + [double(args: ['Foo', [10, 20]], queue: described_class.queue)] + end + + context 'when there are dead jobs present' do + before do + allow(Sidekiq::DeadSet).to receive(:new).and_return(queue) + end + + it 'returns true if specific job exists' do + expect(described_class.dead_jobs?('Foo')).to eq(true) + end + + it 'returns false if specific job does not exist' do + expect(described_class.dead_jobs?('Bar')).to eq(false) + end + end + end + + describe '.retrying_jobs?' do + let(:queue) do + [double(args: ['Foo', [10, 20]], queue: described_class.queue)] + end + + context 'when there are dead jobs present' do + before do + allow(Sidekiq::RetrySet).to receive(:new).and_return(queue) + end + + it 'returns true if specific job exists' do + expect(described_class.retrying_jobs?('Foo')).to eq(true) + end + + it 'returns false if specific job does not exist' do + expect(described_class.retrying_jobs?('Bar')).to eq(false) + end + end + end end -- cgit v1.2.1 From e73f537cb5097e85849110bafe184cb89e3bbc22 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Mon, 8 Apr 2019 17:07:53 +1200 Subject: Refactor PushOptionsHandlerService from review Exceptions are no longer raised, instead all errors encountered are added to the errors property. MergeRequests::BuildService is used to generate attributes of a new merge request. Code moved from Api::Internal to Api::Helpers::InternalHelpers. --- spec/requests/api/internal_spec.rb | 26 ++++---- .../push_options_handler_service_spec.rb | 73 ++++++++++++---------- 2 files changed, 51 insertions(+), 48 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index a5a6e08e73b..26645ff0a44 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -888,8 +888,10 @@ describe API::Internal do } end + let(:branch_name) { 'feature' } + let(:changes) do - "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" + "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{branch_name}" end let(:push_options) do @@ -924,8 +926,8 @@ describe API::Internal do post api('/internal/post_receive'), params: valid_params expect(json_response['merge_request_urls']).to match [{ - "branch_name" => "new_branch", - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", + "branch_name" => branch_name, + "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}", "new_merge_request" => true }] end @@ -955,26 +957,22 @@ describe API::Internal do post api('/internal/post_receive'), params: valid_params end + it 'creates a new merge request' do + expect do + post api('/internal/post_receive'), params: valid_params + end.to change { MergeRequest.count }.by(1) + end + it 'links to the newly created merge request' do post api('/internal/post_receive'), params: valid_params expect(json_response['merge_request_urls']).to match [{ - 'branch_name' => 'new_branch', + 'branch_name' => branch_name, 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1", 'new_merge_request' => false }] end - it 'adds errors raised from MergeRequests::PushOptionsHandlerService to warnings' do - expect(MergeRequests::PushOptionsHandlerService).to receive(:new).and_raise( - MergeRequests::PushOptionsHandlerService::Error, 'my warning' - ) - - post api('/internal/post_receive'), params: valid_params - - expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my warning') - end - it 'adds errors on the service instance to warnings' do expect_any_instance_of( MergeRequests::PushOptionsHandlerService diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index eafe7a34617..af52feef24d 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -3,10 +3,13 @@ require 'spec_helper' describe MergeRequests::PushOptionsHandlerService do + include ProjectForksHelper + let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } + let(:forked_project) { fork_project(project, user, repository: true) } let(:service) { described_class.new(project, user, changes, push_options) } - let(:source_branch) { 'test' } + let(:source_branch) { 'fix' } let(:target_branch) { 'feature' } let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } @@ -38,21 +41,25 @@ describe MergeRequests::PushOptionsHandlerService do expect(last_mr.assignee).to eq(user) end - it 'sets the title and description from the first non-merge commit' do - commits = project.repository.commits('master', limit: 5) + context 'when project has been forked' do + let(:forked_project) { fork_project(project, user, repository: true) } + let(:service) { described_class.new(forked_project, user, changes, push_options) } - expect(Gitlab::Git::Commit).to receive(:between).at_least(:once).and_return(commits) + before do + allow(forked_project).to receive(:empty_repo?).and_return(false) + end - service.execute + it 'sets the correct source project' do + service.execute - merge_commit = commits.first - non_merge_commit = commits.second + expect(last_mr.source_project).to eq(forked_project) + end - expect(merge_commit.merge_commit?).to eq(true) - expect(non_merge_commit.merge_commit?).to eq(false) + it 'sets the correct target project' do + service.execute - expect(last_mr.title).to eq(non_merge_commit.title) - expect(last_mr.description).to eq(non_merge_commit.description) + expect(last_mr.target_project).to eq(project) + end end end @@ -271,7 +278,7 @@ describe MergeRequests::PushOptionsHandlerService do let(:changes) do [ new_branch_changes, - "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/second-branch" + "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/feature_conflict" ] end @@ -287,11 +294,10 @@ describe MergeRequests::PushOptionsHandlerService do end end - it 'throws an error' do - expect { service.execute }.to raise_error( - MergeRequests::PushOptionsHandlerService::Error, - "Too many branches pushed (#{limit + 1} were pushed, limit is #{limit})" - ) + it 'records an error' do + service.execute + + expect(service.errors).to eq(["Too many branches pushed (#{limit + 1} were pushed, limit is #{limit})"]) end end end @@ -308,11 +314,10 @@ describe MergeRequests::PushOptionsHandlerService do let(:push_options) { { create: true } } let(:changes) { new_branch_changes } - it 'throws an error' do - expect { service.execute }.to raise_error( - MergeRequests::PushOptionsHandlerService::Error, - 'User is required' - ) + it 'records an error' do + service.execute + + expect(service.errors).to eq(['User is required']) end end @@ -320,10 +325,12 @@ describe MergeRequests::PushOptionsHandlerService do let(:push_options) { { create: true } } let(:changes) { new_branch_changes } - it 'throws an error' do + it 'records an error' do Members::DestroyService.new(user).execute(ProjectMember.find_by!(user_id: user.id)) - expect { service.execute }.to raise_error(Gitlab::Access::AccessDeniedError) + service.execute + + expect(service.errors).to eq(['User access was denied']) end end @@ -331,11 +338,10 @@ describe MergeRequests::PushOptionsHandlerService do let(:push_options) { { create: true, target: 'my-branch' } } let(:changes) { new_branch_changes } - it 'throws an error' do - expect { service.execute }.to raise_error( - MergeRequests::PushOptionsHandlerService::Error, - 'Branch my-branch does not exist' - ) + it 'records an error' do + service.execute + + expect(service.errors).to eq(['Branch my-branch does not exist']) end end @@ -343,13 +349,12 @@ describe MergeRequests::PushOptionsHandlerService do let(:push_options) { { create: true } } let(:changes) { new_branch_changes } - it 'throws an error' do + it 'records an error' do expect(project).to receive(:merge_requests_enabled?).and_return(false) - expect { service.execute }.to raise_error( - MergeRequests::PushOptionsHandlerService::Error, - 'Merge requests are not enabled for project' - ) + service.execute + + expect(service.errors).to eq(["Merge requests are not enabled for project #{project.full_path}"]) end end -- cgit v1.2.1 From 3c40c98e263328ceb11a008dbec108362e727dbc Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Mon, 8 Apr 2019 21:59:12 +1200 Subject: Feature flag for merge requestion push options https://gitlab.com/gitlab-org/gitlab-ce/issues/43263 https://gitlab.com/gitlab-org/gitlab-ce/issues/53198 --- spec/requests/api/internal_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 26645ff0a44..1ce8f520962 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -995,6 +995,20 @@ describe API::Internal do expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') end + + context 'when the feature is disabled' do + it 'does not invoke MergeRequests::PushOptionsHandlerService' do + Feature.disable(:mr_push_options) + + expect(MergeRequests::PushOptionsHandlerService).to receive(:new) + + expect do + post api('/internal/post_receive'), params: valid_params + end.not_to change { MergeRequest.count } + + Feature.enable(:mr_push_options) + end + end end context 'broadcast message exists' do -- cgit v1.2.1 From b5bcf80c9a7470ac36bdbefcb8056beff67712ae Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Tue, 9 Apr 2019 10:55:07 +1200 Subject: Update service to handle unexpected exceptions This will ensure that now and in the future, PushOptionsHandlerService will not cause the post_receive API endpoint from running other code if something causes an unknown exception. --- .../push_options_handler_service_spec.rb | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'spec') diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index af52feef24d..686b4b49f24 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -334,6 +334,32 @@ describe MergeRequests::PushOptionsHandlerService do end end + describe 'handling unexpected exceptions' do + let(:push_options) { { create: true } } + let(:changes) { new_branch_changes } + let(:exception) { StandardError.new('My standard error') } + + def run_service_with_exception + allow_any_instance_of( + MergeRequests::BuildService + ).to receive(:execute).and_raise(exception) + + service.execute + end + + it 'records an error' do + run_service_with_exception + + expect(service.errors).to eq(['An unknown error occurred']) + end + + it 'writes to Gitlab::AppLogger' do + expect(Gitlab::AppLogger).to receive(:error).with(exception) + + run_service_with_exception + end + end + describe 'when target is not a valid branch name' do let(:push_options) { { create: true, target: 'my-branch' } } let(:changes) { new_branch_changes } -- cgit v1.2.1 From 5b7003282b6b3ce1bfc313b3271bd6827a230c34 Mon Sep 17 00:00:00 2001 From: Jason Goodman Date: Tue, 9 Apr 2019 06:52:15 +0000 Subject: Set release name when adding release notes to an existing tag Also set the release sha and author --- .../projects/tags/releases_controller_spec.rb | 61 +++++++++++++++++++--- .../legacy_github_import/release_formatter_spec.rb | 1 + spec/models/release_spec.rb | 16 ++++++ spec/services/releases/create_service_spec.rb | 10 ++++ 4 files changed, 80 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/tags/releases_controller_spec.rb b/spec/controllers/projects/tags/releases_controller_spec.rb index 29f206c574b..66eff4844c2 100644 --- a/spec/controllers/projects/tags/releases_controller_spec.rb +++ b/spec/controllers/projects/tags/releases_controller_spec.rb @@ -18,40 +18,85 @@ describe Projects::Tags::ReleasesController do tag_id = release.tag project.releases.destroy_all # rubocop: disable DestroyAll - get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: tag_id } + response = get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: tag_id } release = assigns(:release) expect(release).not_to be_nil expect(release).not_to be_persisted + expect(response).to have_http_status(:ok) end it 'retrieves an existing release' do - get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: release.tag } + response = get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: release.tag } release = assigns(:release) expect(release).not_to be_nil expect(release).to be_persisted + expect(response).to have_http_status(:ok) end end describe 'PUT #update' do it 'updates release note description' do - update_release('description updated') + response = update_release(release.tag, "description updated") - release = project.releases.find_by_tag(tag) + release = project.releases.find_by(tag: tag) expect(release.description).to eq("description updated") + expect(response).to have_http_status(:found) end - it 'deletes release note when description is null' do - expect { update_release('') }.to change(project.releases, :count).by(-1) + it 'creates a release if one does not exist' do + tag_without_release = create_new_tag + + expect do + update_release(tag_without_release.name, "a new release") + end.to change { project.releases.count }.by(1) + + expect(response).to have_http_status(:found) + end + + it 'sets the release name, sha, and author for a new release' do + tag_without_release = create_new_tag + + response = update_release(tag_without_release.name, "a new release") + + release = project.releases.find_by(tag: tag_without_release.name) + expect(release.name).to eq(tag_without_release.name) + expect(release.sha).to eq(tag_without_release.target_commit.sha) + expect(release.author.id).to eq(user.id) + expect(response).to have_http_status(:found) + end + + it 'deletes release when description is empty' do + initial_releases_count = project.releases.count + + response = update_release(release.tag, "") + + expect(initial_releases_count).to eq(1) + expect(project.releases.count).to eq(0) + expect(response).to have_http_status(:found) + end + + it 'does nothing when description is empty and the tag does not have a release' do + tag_without_release = create_new_tag + + expect do + update_release(tag_without_release.name, "") + end.not_to change { project.releases.count } + + expect(response).to have_http_status(:found) end end - def update_release(description) + def create_new_tag + project.repository.add_tag(user, 'mytag', 'master') + end + + def update_release(tag_id, description) put :update, params: { namespace_id: project.namespace.to_param, project_id: project, - tag_id: release.tag, + tag_id: tag_id, release: { description: description } } end diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb index 082e3b36dd0..c57b96fb00d 100644 --- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb @@ -25,6 +25,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do expected = { project: project, tag: 'v1.0.0', + name: 'First release', description: 'Release v1.0.0', created_at: created_at, updated_at: created_at diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index b4b32c95dee..0b19a4f8efc 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -18,6 +18,22 @@ RSpec.describe Release do describe 'validation' do it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:name) } + + context 'when a release exists in the database without a name' do + it 'does not require name' do + existing_release_without_name = build(:release, project: project, author: user, name: nil) + existing_release_without_name.save(validate: false) + + existing_release_without_name.description = "change" + existing_release_without_name.save + existing_release_without_name.reload + + expect(existing_release_without_name).to be_valid + expect(existing_release_without_name.description).to eq("change") + expect(existing_release_without_name.name).to be_nil + end + end end describe '#assets_count' do diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb index 612e9f152e7..0efe37f1167 100644 --- a/spec/services/releases/create_service_spec.rb +++ b/spec/services/releases/create_service_spec.rb @@ -19,6 +19,8 @@ describe Releases::CreateService do shared_examples 'a successful release creation' do it 'creates a new release' do result = service.execute + + expect(project.releases.count).to eq(1) expect(result[:status]).to eq(:success) expect(result[:tag]).not_to be_nil expect(result[:release]).not_to be_nil @@ -69,4 +71,12 @@ describe Releases::CreateService do end end end + + describe '#find_or_build_release' do + it 'does not save the built release' do + service.find_or_build_release + + expect(project.releases.count).to eq(0) + end + end end -- cgit v1.2.1 From b28ba16a1eeb3f6437f282eafc15ea963c82bdf3 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 19:19:54 +0200 Subject: Enable ESLint rule jest/no-jasmine-globals --- spec/frontend/.eslintrc.yml | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml index 30b559c2363..63004b96ad6 100644 --- a/spec/frontend/.eslintrc.yml +++ b/spec/frontend/.eslintrc.yml @@ -14,3 +14,4 @@ globals: setFixtures: false rules: jest/no-focused-tests: error + jest/no-jasmine-globals: error -- cgit v1.2.1 From cb892da622820505637bafc39c0c0688300a6dc7 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 19:20:40 +0200 Subject: Run yarn eslint --fix --- .../services/recent_searches_service_error_spec.js | 2 +- spec/frontend/ide/lib/editor_options_spec.js | 2 +- .../frontend/pages/admin/abuse_reports/abuse_reports_spec.js | 4 ++-- spec/frontend/pages/profiles/show/emoji_menu_spec.js | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js index ea7c146fa4f..0e62bc94517 100644 --- a/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js +++ b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js @@ -8,7 +8,7 @@ describe('RecentSearchesServiceError', () => { }); it('instantiates an instance of RecentSearchesServiceError and not an Error', () => { - expect(recentSearchesServiceError).toEqual(jasmine.any(RecentSearchesServiceError)); + expect(recentSearchesServiceError).toEqual(expect.any(RecentSearchesServiceError)); expect(recentSearchesServiceError.name).toBe('RecentSearchesServiceError'); }); diff --git a/spec/frontend/ide/lib/editor_options_spec.js b/spec/frontend/ide/lib/editor_options_spec.js index d149a883166..b07a583b7c8 100644 --- a/spec/frontend/ide/lib/editor_options_spec.js +++ b/spec/frontend/ide/lib/editor_options_spec.js @@ -2,7 +2,7 @@ import editorOptions from '~/ide/lib/editor_options'; describe('Multi-file editor library editor options', () => { it('returns an array', () => { - expect(editorOptions).toEqual(jasmine.any(Array)); + expect(editorOptions).toEqual(expect.any(Array)); }); it('contains readOnly option', () => { diff --git a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js index 1e0bc708c31..7e9aec84016 100644 --- a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js +++ b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js @@ -25,14 +25,14 @@ describe('Abuse Reports', () => { it('should truncate long messages', () => { const $longMessage = findMessage('LONG MESSAGE'); - expect($longMessage.data('originalMessage')).toEqual(jasmine.anything()); + expect($longMessage.data('originalMessage')).toEqual(expect.anything()); assertMaxLength($longMessage); }); it('should not truncate short messages', () => { const $shortMessage = findMessage('SHORT MESSAGE'); - expect($shortMessage.data('originalMessage')).not.toEqual(jasmine.anything()); + expect($shortMessage.data('originalMessage')).not.toEqual(expect.anything()); }); it('should allow clicking a truncated message to expand and collapse the full message', () => { diff --git a/spec/frontend/pages/profiles/show/emoji_menu_spec.js b/spec/frontend/pages/profiles/show/emoji_menu_spec.js index efc338b36eb..b8020a2035e 100644 --- a/spec/frontend/pages/profiles/show/emoji_menu_spec.js +++ b/spec/frontend/pages/profiles/show/emoji_menu_spec.js @@ -75,19 +75,19 @@ describe('EmojiMenu', () => { expect(emojiMenu.registerEventListener).toHaveBeenCalledWith( 'one', - jasmine.anything(), + expect.anything(), 'mouseenter focus', dummyToggleButtonSelector, 'mouseenter focus', - jasmine.anything(), + expect.anything(), ); expect(emojiMenu.registerEventListener).toHaveBeenCalledWith( 'on', - jasmine.anything(), + expect.anything(), 'click', dummyToggleButtonSelector, - jasmine.anything(), + expect.anything(), ); }); @@ -96,10 +96,10 @@ describe('EmojiMenu', () => { expect(emojiMenu.registerEventListener).toHaveBeenCalledWith( 'on', - jasmine.anything(), + expect.anything(), 'click', `.js-awards-block .js-emoji-btn, .${dummyMenuClass} .js-emoji-btn`, - jasmine.anything(), + expect.anything(), ); }); }); -- cgit v1.2.1 From e658b33792707a3dc190355ddc3d6c8d3ddb0cd8 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 19:24:00 +0200 Subject: Replace jasmine.createSpy() with jest.fn() --- spec/frontend/error_tracking/components/error_tracking_list_spec.js | 2 +- spec/frontend/ide/lib/common/disposable_spec.js | 2 +- spec/frontend/pages/profiles/show/emoji_menu_spec.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js index 503af3920a8..67e5dc399ac 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -31,7 +31,7 @@ describe('ErrorTrackingList', () => { actions = { getErrorList: () => {}, startPolling: () => {}, - restartPolling: jasmine.createSpy('restartPolling'), + restartPolling: jest.fn().mockName('restartPolling'), }; const state = { diff --git a/spec/frontend/ide/lib/common/disposable_spec.js b/spec/frontend/ide/lib/common/disposable_spec.js index af12ca15369..8596642eb7a 100644 --- a/spec/frontend/ide/lib/common/disposable_spec.js +++ b/spec/frontend/ide/lib/common/disposable_spec.js @@ -8,7 +8,7 @@ describe('Multi-file editor library disposable class', () => { instance = new Disposable(); disposableClass = { - dispose: jasmine.createSpy('dispose'), + dispose: jest.fn().mockName('dispose'), }; }); diff --git a/spec/frontend/pages/profiles/show/emoji_menu_spec.js b/spec/frontend/pages/profiles/show/emoji_menu_spec.js index b8020a2035e..6ac1e83829f 100644 --- a/spec/frontend/pages/profiles/show/emoji_menu_spec.js +++ b/spec/frontend/pages/profiles/show/emoji_menu_spec.js @@ -13,7 +13,7 @@ describe('EmojiMenu', () => { let dummyEmojiList; beforeEach(() => { - dummySelectEmojiCallback = jasmine.createSpy('dummySelectEmojiCallback'); + dummySelectEmojiCallback = jest.fn().mockName('dummySelectEmojiCallback'); dummyEmojiList = { glEmojiTag() { return dummyEmojiTag; -- cgit v1.2.1 From 724f19ba0a051bbe8e9dd89f208261abe0f8133a Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Tue, 9 Apr 2019 09:16:57 +0000 Subject: Add new API endpoint to expose single environment This is resolving https://gitlab.com/gitlab-org/gitlab-ce/issues/30157. Implement new API endpoint `/projects/:id/environments/:environment_id` to expose single environment. Include information for environment's last deployment if there is one. --- .../api/schemas/public_api/v4/artifact.json | 16 ++++++ .../api/schemas/public_api/v4/artifact_file.json | 12 +++++ .../api/schemas/public_api/v4/deployment.json | 32 +++++++++++ .../api/schemas/public_api/v4/environment.json | 23 ++++++++ spec/fixtures/api/schemas/public_api/v4/job.json | 63 ++++++++++++++++++++++ .../fixtures/api/schemas/public_api/v4/runner.json | 24 +++++++++ spec/requests/api/environments_spec.rb | 22 ++++++++ 7 files changed, 192 insertions(+) create mode 100644 spec/fixtures/api/schemas/public_api/v4/artifact.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/artifact_file.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/deployment.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/environment.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/job.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/runner.json (limited to 'spec') diff --git a/spec/fixtures/api/schemas/public_api/v4/artifact.json b/spec/fixtures/api/schemas/public_api/v4/artifact.json new file mode 100644 index 00000000000..9df957b1498 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/artifact.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "required": [ + "file_type", + "size", + "filename", + "file_format" + ], + "properties": { + "file_type": { "type": "string"}, + "size": { "type": "integer"}, + "filename": { "type": "string"}, + "file_format": { "type": "string"} + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/artifact_file.json b/spec/fixtures/api/schemas/public_api/v4/artifact_file.json new file mode 100644 index 00000000000..4017e6bdabc --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/artifact_file.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required": [ + "filename", + "size" + ], + "properties": { + "filename": { "type": "string"}, + "size": { "type": "integer"} + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/deployment.json b/spec/fixtures/api/schemas/public_api/v4/deployment.json new file mode 100644 index 00000000000..3af2dc27d55 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/deployment.json @@ -0,0 +1,32 @@ +{ + "type": "object", + "required": [ + "id", + "iid", + "ref", + "sha", + "created_at", + "user", + "deployable" + ], + "properties": { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "ref": { "type": "string" }, + "sha": { "type": "string" }, + "created_at": { "type": "string" }, + "user": { + "oneOf": [ + { "type": "null" }, + { "$ref": "user/basic.json" } + ] + }, + "deployable": { + "oneOf": [ + { "type": "null" }, + { "$ref": "job.json" } + ] + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/environment.json b/spec/fixtures/api/schemas/public_api/v4/environment.json new file mode 100644 index 00000000000..242e90fb7ac --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/environment.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "required": [ + "id", + "name", + "slug", + "external_url", + "last_deployment" + ], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "slug": { "type": "string" }, + "external_url": { "$ref": "../../types/nullable_string.json" }, + "last_deployment": { + "oneOf": [ + { "type": "null" }, + { "$ref": "deployment.json" } + ] + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/job.json b/spec/fixtures/api/schemas/public_api/v4/job.json new file mode 100644 index 00000000000..454935422a0 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/job.json @@ -0,0 +1,63 @@ +{ + "type": "object", + "required": [ + "id", + "status", + "stage", + "name", + "ref", + "tag", + "coverage", + "created_at", + "started_at", + "finished_at", + "duration", + "user", + "commit", + "pipeline", + "web_url", + "artifacts", + "artifacts_expire_at", + "runner" + ], + "properties": { + "id": { "type": "integer" }, + "status": { "type": "string" }, + "stage": { "type": "string" }, + "name": { "type": "string" }, + "ref": { "type": "string" }, + "tag": { "type": "boolean" }, + "coverage": { "type": ["number", "null"] }, + "created_at": { "type": "string" }, + "started_at": { "type": ["null", "string"] }, + "finished_at": { "type": ["null", "string"] }, + "duration": { "type": ["null", "number"] }, + "user": { "$ref": "user/basic.json" }, + "commit": { + "oneOf": [ + { "type": "null" }, + { "$ref": "commit/basic.json" } + ] + }, + "pipeline": { "$ref": "pipeline/basic.json" }, + "web_url": { "type": "string" }, + "artifacts": { + "type": "array", + "items": { "$ref": "artifact.json" } + }, + "artifacts_file": { + "oneOf": [ + { "type": "null" }, + { "$ref": "artifact_file.json" } + ] + }, + "artifacts_expire_at": { "type": ["null", "string"] }, + "runner": { + "oneOf": [ + { "type": "null" }, + { "$ref": "runner.json" } + ] + } + }, + "additionalProperties":false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/runner.json b/spec/fixtures/api/schemas/public_api/v4/runner.json new file mode 100644 index 00000000000..d97d74a93f2 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/runner.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "required": [ + "id", + "description", + "ip_address", + "active", + "is_shared", + "name", + "online", + "status" + ], + "properties": { + "id": { "type": "integer" }, + "description": { "type": "string" }, + "ip_address": { "type": "string" }, + "active": { "type": "boolean" }, + "is_shared": { "type": "boolean" }, + "name": { "type": ["null", "string"] }, + "online": { "type": "boolean" }, + "status": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 493d3642255..8fc7fdc8632 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -32,6 +32,7 @@ describe API::Environments do expect(json_response.first['name']).to eq(environment.name) expect(json_response.first['external_url']).to eq(environment.external_url) expect(json_response.first['project'].keys).to contain_exactly(*project_data_keys) + expect(json_response.first).not_to have_key("last_deployment") end end @@ -188,4 +189,25 @@ describe API::Environments do end end end + + describe 'GET /projects/:id/environments/:environment_id' do + context 'as member of the project' do + it 'returns project environments' do + create(:deployment, :success, project: project, environment: environment) + + get api("/projects/#{project.id}/environments/#{environment.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/environment') + end + end + + context 'as non member' do + it 'returns a 404 status code' do + get api("/projects/#{project.id}/environments/#{environment.id}", non_member) + + expect(response).to have_gitlab_http_status(404) + end + end + end end -- cgit v1.2.1 From 0a4f44de83fac88f39d9f4507c2fa4d935703e1b Mon Sep 17 00:00:00 2001 From: Wolphin Date: Tue, 9 Apr 2019 09:28:55 +0000 Subject: Add environment url validation --- .../lib/gitlab/ci/config/entry/environment_spec.rb | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb index 3c0007f4d57..0bc9e8bd3cd 100644 --- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb @@ -100,6 +100,26 @@ describe Gitlab::Ci::Config::Entry::Environment do end end + context 'when wrong action type is used' do + let(:config) do + { name: 'production', + action: ['stop'] } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about wrong action type' do + expect(entry.errors) + .to include 'environment action should be a string' + end + end + end + context 'when invalid action is used' do let(:config) do { name: 'production', @@ -151,6 +171,26 @@ describe Gitlab::Ci::Config::Entry::Environment do end end + context 'when wrong url type is used' do + let(:config) do + { name: 'production', + url: ['https://meow.meow'] } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about wrong url type' do + expect(entry.errors) + .to include 'environment url should be a string' + end + end + end + context 'when variables are used for environment' do let(:config) do { name: 'review/$CI_COMMIT_REF_NAME', -- cgit v1.2.1 From 193e6602914a45974e5bd6e2ab12a63f91c62143 Mon Sep 17 00:00:00 2001 From: Sarah Yasonik Date: Tue, 9 Apr 2019 09:54:17 +0000 Subject: Resolve Environments#additional_metrics TypeError, ensure unix format --- .../projects/environments_controller_spec.rb | 11 ++++++++ spec/javascripts/monitoring/utils_spec.js | 29 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 spec/javascripts/monitoring/utils_spec.js (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 43639875265..168c0168bba 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -419,6 +419,17 @@ describe Projects::EnvironmentsController do expect(json_response['data']).to eq({}) expect(json_response['last_update']).to eq(42) end + + context 'when time params are provided' do + it 'returns a metrics JSON document' do + additional_metrics(start: '1554702993.5398998', end: '1554717396.996232') + + expect(response).to be_ok + expect(json_response['success']).to be(true) + expect(json_response['data']).to eq({}) + expect(json_response['last_update']).to eq(42) + end + end end context 'when only one time param is provided' do diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js new file mode 100644 index 00000000000..e3c455d1686 --- /dev/null +++ b/spec/javascripts/monitoring/utils_spec.js @@ -0,0 +1,29 @@ +import { getTimeDiff } from '~/monitoring/utils'; +import { timeWindows } from '~/monitoring/constants'; + +describe('getTimeDiff', () => { + it('defaults to an 8 hour (28800s) difference', () => { + const params = getTimeDiff(); + + expect(params.end - params.start).toEqual(28800); + }); + + it('accepts time window as an argument', () => { + const params = getTimeDiff(timeWindows.thirtyMinutes); + + expect(params.end - params.start).not.toEqual(28800); + }); + + it('returns a value for every defined time window', () => { + const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours'); + + nonDefaultWindows.forEach(window => { + const params = getTimeDiff(timeWindows[window]); + const diff = params.end - params.start; + + // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs) + expect(diff).not.toEqual(28800); + expect(typeof diff).toEqual('number'); + }); + }); +}); -- cgit v1.2.1 From 0991dc8c823cdc04ec1e298651b45f7a9cf8f82a Mon Sep 17 00:00:00 2001 From: Sanad Liaquat Date: Tue, 9 Apr 2019 10:04:03 +0000 Subject: Reduce number of rspec retries In both e2e QA tests and unit tests, reduce the number of retires to 2 (i.e., 1 initial and one retry) --- spec/features/issuables/shortcuts_issuable_spec.rb | 2 +- spec/features/profiles/user_visits_profile_preferences_page_spec.rb | 2 +- spec/lib/gitlab/ci/yaml_processor_spec.rb | 2 +- spec/spec_helper.rb | 4 ++-- spec/support/shared_examples/wiki_file_attachments_examples.rb | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/features/issuables/shortcuts_issuable_spec.rb b/spec/features/issuables/shortcuts_issuable_spec.rb index a0ae6720a9f..a19101366a0 100644 --- a/spec/features/issuables/shortcuts_issuable_spec.rb +++ b/spec/features/issuables/shortcuts_issuable_spec.rb @@ -13,7 +13,7 @@ describe 'Blob shortcuts', :js do end shared_examples "quotes the selected text" do - it "quotes the selected text" do + it "quotes the selected text", :quarantine do select_element('.note-text') find('body').native.send_key('r') diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index 9909bfb5904..1b3718968b9 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -63,7 +63,7 @@ describe 'User visits the profile preferences page' do end describe 'User changes their language', :js do - it 'creates a flash message' do + it 'creates a flash message', :quarantine do select2('en', from: '#user_preferred_language') click_button 'Save' diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 8b39c4e4dd0..b7b30e60d44 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -615,7 +615,7 @@ module Gitlab subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) } context "when validating a ci config file with no project context" do - context "when a single string is provided" do + context "when a single string is provided", :quarantine do let(:include_content) { "/local.gitlab-ci.yml" } it "does not return any error" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 60db3e1bc46..953cf1519bb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -100,8 +100,8 @@ RSpec.configure do |config| config.include PolicyHelpers, type: :policy if ENV['CI'] - # This includes the first try, i.e. tests will be run 4 times before failing. - config.default_retry_count = 4 + # This includes the first try, i.e. tests will be run 2 times before failing. + config.default_retry_count = 2 config.reporter.register_listener( RspecFlaky::Listener.new, :example_passed, diff --git a/spec/support/shared_examples/wiki_file_attachments_examples.rb b/spec/support/shared_examples/wiki_file_attachments_examples.rb index b6fb2a66b0e..22fbfb48928 100644 --- a/spec/support/shared_examples/wiki_file_attachments_examples.rb +++ b/spec/support/shared_examples/wiki_file_attachments_examples.rb @@ -42,7 +42,7 @@ shared_examples 'wiki file attachments' do end end - context 'uploading is complete' do + context 'uploading is complete', :quarantine do it 'shows "Attach a file" button on uploading complete' do attach_with_dropzone wait_for_requests -- cgit v1.2.1 From cf804218c95ce79d56bc6470135e14a505b3f9ab Mon Sep 17 00:00:00 2001 From: Daniel Wyatt Date: Tue, 9 Apr 2019 05:10:15 -0400 Subject: Add test for github project import to user namespace. --- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 8c56622e0ba..3aba744458f 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -54,6 +54,18 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do expect(project.visibility_level).to eq(namespace.visibility_level) end + + context 'when importing into a user namespace' do + subject(:service) { described_class.new(repo, repo.name, user.namespace, user, github_access_token: 'asdffg') } + + it 'sets project visibility to user namespace visibility level' do + repo.private = false + + project = service.execute + + expect(project.visibility_level).to eq(user.namespace.visibility_level) + end + end end context 'when visibility level is restricted' do -- cgit v1.2.1 From ba4c32c033135b5a2339afa6b85d0eff2ce0dfd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 9 Apr 2019 13:57:40 +0200 Subject: Quarantine flaky specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: - https://gitlab.com/gitlab-org/gitlab-ce/issues/60270 - https://gitlab.com/gitlab-org/gitlab-ce/issues/60271 - https://gitlab.com/gitlab-org/gitlab-ce/issues/60272 - https://gitlab.com/gitlab-org/gitlab-ce/issues/60273 Signed-off-by: Rémy Coutable --- spec/features/issues/filtered_search/dropdown_milestone_spec.rb | 2 +- spec/features/labels_hierarchy_spec.rb | 2 +- spec/features/merge_request/user_creates_image_diff_notes_spec.rb | 2 +- spec/features/projects/wiki/user_creates_wiki_page_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 7584339ccc0..7a6f76cb382 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -139,7 +139,7 @@ describe 'Dropdown milestone', :js do expect_filtered_search_input_empty end - it 'fills in the milestone name when the milestone is partially filled' do + it 'fills in the milestone name when the milestone is partially filled', :quarantine do filtered_search.send_keys('v') click_milestone(milestone.title) diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index bac297de4a6..489651fea15 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -21,7 +21,7 @@ describe 'Labels Hierarchy', :js, :nested_groups do end shared_examples 'assigning labels from sidebar' do - it 'can assign all ancestors labels' do + it 'can assign all ancestors labels', :quarantine do [grandparent_group_label, parent_group_label, project_label_1].each do |label| page.within('.block.labels') do find('.edit-link').click diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb index c837a6752f9..65de0afae0c 100644 --- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb +++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb @@ -113,7 +113,7 @@ describe 'Merge request > User creates image diff notes', :js do create_image_diff_note end - it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes' do + it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', :quarantine do indicator = find('.js-image-badge', match: :first) badge = find('.user-avatar-link .badge', match: :first) diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index efb7b01f5ad..bcbba6f14da 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -43,7 +43,7 @@ describe "User creates wiki page" do expect(page).to have_content("Create Page") end - it "shows non-escaped link in the pages list", :js do + it "shows non-escaped link in the pages list", :js, :quarantine do fill_in(:wiki_title, with: "one/two/three-test") page.within(".wiki-form") do -- cgit v1.2.1 From 20093f9de0b34da88a8b01ca94ee773685b16308 Mon Sep 17 00:00:00 2001 From: Agustin Henze Date: Tue, 9 Apr 2019 14:53:44 +0000 Subject: Add new permission model `read-pipeline-variable` Used to get the variables via the API endpoint `/projects/:id/pipelines/:pipeline_id/variables` Signed-off-by: Agustin Henze --- spec/policies/ci/pipeline_policy_spec.rb | 46 ++++++++++++++++++++++ spec/requests/api/pipelines_spec.rb | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) (limited to 'spec') diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb index 844d96017de..126d44d1860 100644 --- a/spec/policies/ci/pipeline_policy_spec.rb +++ b/spec/policies/ci/pipeline_policy_spec.rb @@ -100,5 +100,51 @@ describe Ci::PipelinePolicy, :models do end end end + + describe 'read_pipeline_variable' do + let(:project) { create(:project, :public) } + + context 'when user has owner access' do + let(:user) { project.owner } + + it 'is enabled' do + expect(policy).to be_allowed :read_pipeline_variable + end + end + + context 'when user is developer and the creator of the pipeline' do + let(:pipeline) { create(:ci_empty_pipeline, project: project, user: user) } + + before do + project.add_developer(user) + create(:protected_branch, :developers_can_merge, + name: pipeline.ref, project: project) + end + + it 'is enabled' do + expect(policy).to be_allowed :read_pipeline_variable + end + end + + context 'when user is developer and it is not the creator of the pipeline' do + let(:pipeline) { create(:ci_empty_pipeline, project: project, user: project.owner) } + + before do + project.add_developer(user) + create(:protected_branch, :developers_can_merge, + name: pipeline.ref, project: project) + end + + it 'is disabled' do + expect(policy).to be_disallowed :read_pipeline_variable + end + end + + context 'when user is not owner nor developer' do + it 'is disabled' do + expect(policy).not_to be_allowed :read_pipeline_variable + end + end + end end end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 9fed07cae82..0d46463312b 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -445,6 +445,72 @@ describe API::Pipelines do end end + describe 'GET /projects/:id/pipelines/:pipeline_id/variables' do + subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", api_user) } + + let(:api_user) { user } + + context 'user is a mantainer' do + it 'returns pipeline variables empty' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_empty + end + + context 'with variables' do + let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') } + + it 'returns pipeline variables' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to contain_exactly({ "key" => "foo", "value" => "bar" }) + end + end + end + + context 'user is a developer' do + let(:pipeline_owner_user) { create(:user) } + let(:pipeline) { create(:ci_empty_pipeline, project: project, user: pipeline_owner_user) } + + before do + project.add_developer(api_user) + end + + context 'pipeline created by the developer user' do + let(:api_user) { pipeline_owner_user } + let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') } + + it 'returns pipeline variables' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to contain_exactly({ "key" => "foo", "value" => "bar" }) + end + end + + context 'pipeline created is not created by the developer user' do + let(:api_user) { create(:user) } + + it 'should not return pipeline variables' do + subject + + expect(response).to have_gitlab_http_status(403) + end + end + end + + context 'user is not a project member' do + it 'should not return pipeline variables' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq '404 Project Not Found' + end + end + end + describe 'DELETE /projects/:id/pipelines/:pipeline_id' do context 'authorized user' do let(:owner) { project.owner } -- cgit v1.2.1 From 326f6ce40743dc4d44293063637d35b9a2edc693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 9 Apr 2019 17:04:31 +0200 Subject: Quarantine a flaky spec in spec/features/issues/user_interacts_with_awards_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/issues/user_interacts_with_awards_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index afa425c2cec..eaeaa04f50d 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -14,7 +14,7 @@ describe 'User interacts with awards' do visit(project_issue_path(project, issue)) end - it 'toggles the thumbsup award emoji' do + it 'toggles the thumbsup award emoji', :quarantine do page.within('.awards') do thumbsup = page.first('.award-control') thumbsup.click -- cgit v1.2.1 From 9bc5ed14fe97fe63cd5be30c013c6af978715621 Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Tue, 9 Apr 2019 15:38:58 +0000 Subject: Move Contribution Analytics related spec in spec/features/groups/group_page_with_external_authorization_service_spec to EE --- .../admin/application_settings_controller_spec.rb | 22 ++++ spec/controllers/boards/issues_controller_spec.rb | 26 ++++ .../concerns/project_unauthorized_spec.rb | 51 ++++++++ .../dashboard/groups_controller_spec.rb | 50 +++++--- .../dashboard/labels_controller_spec.rb | 6 +- .../dashboard/milestones_controller_spec.rb | 6 + .../dashboard/projects_controller_spec.rb | 24 +++- .../controllers/dashboard/todos_controller_spec.rb | 6 + spec/controllers/groups/avatars_controller_spec.rb | 10 ++ spec/controllers/groups/boards_controller_spec.rb | 8 ++ .../controllers/groups/children_controller_spec.rb | 12 ++ .../groups/group_members_controller_spec.rb | 86 +++++++++++++ spec/controllers/groups/labels_controller_spec.rb | 6 + .../groups/milestones_controller_spec.rb | 6 + .../groups/settings/ci_cd_controller_spec.rb | 15 +++ .../groups/variables_controller_spec.rb | 34 +++++ spec/controllers/groups_controller_spec.rb | 96 ++++++++++++++ .../controllers/projects/boards_controller_spec.rb | 4 + .../controllers/projects/issues_controller_spec.rb | 11 ++ spec/controllers/projects_controller_spec.rb | 32 +++++ spec/controllers/search_controller_spec.rb | 39 ++++++ spec/controllers/users_controller_spec.rb | 12 ++ ...ard_with_external_authorization_service_spec.rb | 38 ++++++ ...age_with_external_authorization_service_spec.rb | 58 +++++++++ .../classification_label_on_project_pages_spec.rb | 22 ++++ spec/features/projects/forks/fork_list_spec.rb | 35 +++++ ...ues_with_external_authorization_enabled_spec.rb | 128 +++++++++++++++++++ ...external_authorization_service_settings_spec.rb | 21 +++ spec/features/users/show_spec.rb | 22 ++++ spec/finders/issues_finder_spec.rb | 7 + spec/finders/labels_finder_spec.rb | 7 + spec/finders/merge_requests_finder_spec.rb | 7 + spec/finders/snippets_finder_spec.rb | 32 +++++ spec/finders/todos_finder_spec.rb | 7 + spec/fixtures/passphrase_x509_certificate.crt | 27 ++++ spec/fixtures/passphrase_x509_certificate_pk.key | 54 ++++++++ spec/fixtures/x509_certificate.crt | 27 ++++ spec/fixtures/x509_certificate_pk.key | 51 ++++++++ .../gitlab/external_authorization/access_spec.rb | 142 +++++++++++++++++++++ .../gitlab/external_authorization/cache_spec.rb | 48 +++++++ .../gitlab/external_authorization/client_spec.rb | 97 ++++++++++++++ .../gitlab/external_authorization/logger_spec.rb | 45 +++++++ .../gitlab/external_authorization/response_spec.rb | 52 ++++++++ spec/lib/gitlab/external_authorization_spec.rb | 54 ++++++++ .../gitlab/import_export/safe_model_attributes.yml | 1 + spec/models/application_setting_spec.rb | 48 ++++++- spec/models/concerns/protected_ref_access_spec.rb | 12 ++ spec/models/issue_spec.rb | 45 +++++++ spec/models/project_spec.rb | 20 +++ spec/policies/base_policy_spec.rb | 23 ++++ spec/policies/issue_policy_spec.rb | 19 +++ spec/policies/merge_request_policy_spec.rb | 19 +++ spec/policies/project_policy_spec.rb | 53 ++++++++ spec/requests/api/projects_spec.rb | 49 +++++++ spec/requests/api/settings_spec.rb | 33 +++++ spec/serializers/group_child_entity_spec.rb | 19 +++ .../application_settings/update_service_spec.rb | 35 +++++ spec/services/notification_service_spec.rb | 41 ++++++ spec/services/projects/create_service_spec.rb | 37 ++++++ spec/services/projects/update_service_spec.rb | 41 ++++++ .../external_authorization_service_helpers.rb | 33 +++++ ...ternal_authorization_service_shared_examples.rb | 40 ++++++ .../finder_with_external_authorization_enabled.rb | 30 +++++ .../x509_certificate_credentials_validator_spec.rb | 87 +++++++++++++ 64 files changed, 2206 insertions(+), 22 deletions(-) create mode 100644 spec/controllers/concerns/project_unauthorized_spec.rb create mode 100644 spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb create mode 100644 spec/features/groups/group_page_with_external_authorization_service_spec.rb create mode 100644 spec/features/projects/classification_label_on_project_pages_spec.rb create mode 100644 spec/features/projects/forks/fork_list_spec.rb create mode 100644 spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb create mode 100644 spec/features/projects/settings/external_authorization_service_settings_spec.rb create mode 100644 spec/fixtures/passphrase_x509_certificate.crt create mode 100644 spec/fixtures/passphrase_x509_certificate_pk.key create mode 100644 spec/fixtures/x509_certificate.crt create mode 100644 spec/fixtures/x509_certificate_pk.key create mode 100644 spec/lib/gitlab/external_authorization/access_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/cache_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/client_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/logger_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/response_spec.rb create mode 100644 spec/lib/gitlab/external_authorization_spec.rb create mode 100644 spec/support/external_authorization_service_helpers.rb create mode 100644 spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb create mode 100644 spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb create mode 100644 spec/validators/x509_certificate_credentials_validator_spec.rb (limited to 'spec') diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 1a7be4c9a85..f3450a8289f 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -92,6 +92,28 @@ describe Admin::ApplicationSettingsController do expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.default_project_creation).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) end + + context 'external policy classification settings' do + let(:settings) do + { + external_authorization_service_enabled: true, + external_authorization_service_url: 'https://custom.service/', + external_authorization_service_default_label: 'default', + external_authorization_service_timeout: 3, + external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'), + external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'), + external_auth_client_key_pass: "5iveL!fe" + } + end + + it 'updates settings when the feature is available' do + put :update, params: { application_setting: settings } + + settings.each do |attribute, value| + expect(ApplicationSetting.current.public_send(attribute)).to eq(value) + end + end + end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 5eb05f01b8d..309cac47928 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Boards::IssuesController do + include ExternalAuthorizationServiceHelpers + let(:project) { create(:project, :private) } let(:board) { create(:board, project: project) } let(:user) { create(:user) } @@ -136,6 +138,30 @@ describe Boards::IssuesController do end end + context 'with external authorization' do + before do + sign_in(user) + enable_external_authorization_service_check + end + + it 'returns a 403 for group boards' do + group = create(:group) + group_board = create(:board, group: group) + + list_issues(user: user, board: group_board) + + expect(response).to have_gitlab_http_status(403) + end + + it 'is successful for project boards' do + project_board = create(:board, project: project) + + list_issues(user: user, board: project_board) + + expect(response).to have_gitlab_http_status(200) + end + end + def list_issues(user:, board:, list: nil) sign_in(user) diff --git a/spec/controllers/concerns/project_unauthorized_spec.rb b/spec/controllers/concerns/project_unauthorized_spec.rb new file mode 100644 index 00000000000..90b59b027cf --- /dev/null +++ b/spec/controllers/concerns/project_unauthorized_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe ProjectUnauthorized do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } + + before do + sign_in user + end + + render_views + + describe '#project_unauthorized_proc' do + controller(::Projects::ApplicationController) do + def show + head :ok + end + end + + let(:project) { create(:project) } + + before do + project.add_developer(user) + end + + it 'renders a 200 when the service allows access to the project' do + external_service_allow_access(user, project) + + get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param } + + expect(response).to have_gitlab_http_status(200) + end + + it 'renders a 403 when the service denies access to the project' do + external_service_deny_access(user, project) + + get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param } + + expect(response).to have_gitlab_http_status(403) + expect(response.body).to match("External authorization denied access to this project") + end + + it 'renders a 404 when the user cannot see the project at all' do + other_project = create(:project, :private) + + get :show, params: { namespace_id: other_project.namespace.to_param, id: other_project.to_param } + + expect(response).to have_gitlab_http_status(404) + end + end +end diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb index c8d99f79277..775b3ca40b2 100644 --- a/spec/controllers/dashboard/groups_controller_spec.rb +++ b/spec/controllers/dashboard/groups_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Dashboard::GroupsController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } before do @@ -11,33 +13,43 @@ describe Dashboard::GroupsController do expect(described_class).to include(GroupTree) end - it 'only includes projects the user is a member of' do - member_of_group = create(:group) - member_of_group.add_developer(user) - create(:group, :public) + describe '#index' do + it 'only includes projects the user is a member of' do + member_of_group = create(:group) + member_of_group.add_developer(user) + create(:group, :public) - get :index + get :index - expect(assigns(:groups)).to contain_exactly(member_of_group) - end + expect(assigns(:groups)).to contain_exactly(member_of_group) + end - context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do - let!(:top_level_result) { create(:group, name: 'chef-top') } - let!(:top_level_a) { create(:group, name: 'top-a') } - let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) } - let!(:other_group) { create(:group, name: 'other') } + context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do + let!(:top_level_result) { create(:group, name: 'chef-top') } + let!(:top_level_a) { create(:group, name: 'top-a') } + let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) } + let!(:other_group) { create(:group, name: 'other') } - before do - top_level_result.add_maintainer(user) - top_level_a.add_maintainer(user) + before do + top_level_result.add_maintainer(user) + top_level_a.add_maintainer(user) + end + + it 'renders only groups the user is a member of when searching hierarchy correctly' do + get :index, params: { filter: 'chef' }, format: :json + + expect(response).to have_gitlab_http_status(200) + all_groups = [top_level_result, top_level_a, sub_level_result_a] + expect(assigns(:groups)).to contain_exactly(*all_groups) + end end - it 'renders only groups the user is a member of when searching hierarchy correctly' do - get :index, params: { filter: 'chef' }, format: :json + it 'works when the external authorization service is enabled' do + enable_external_authorization_service_check + + get :index expect(response).to have_gitlab_http_status(200) - all_groups = [top_level_result, top_level_a, sub_level_result_a] - expect(assigns(:groups)).to contain_exactly(*all_groups) end end end diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb index a3bfb2f3a87..01de896f9f4 100644 --- a/spec/controllers/dashboard/labels_controller_spec.rb +++ b/spec/controllers/dashboard/labels_controller_spec.rb @@ -13,13 +13,17 @@ describe Dashboard::LabelsController do describe "#index" do let!(:unrelated_label) { create(:label, project: create(:project, :public)) } + subject { get :index, format: :json } + it 'returns global labels for projects the user has a relationship with' do - get :index, format: :json + subject expect(json_response).to be_kind_of(Array) expect(json_response.size).to eq(1) expect(json_response[0]["id"]).to be_nil expect(json_response[0]["title"]).to eq(label.title) end + + it_behaves_like 'disabled when using an external authorization service' end end diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 828de0e7ca5..1614739db05 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -81,5 +81,11 @@ describe Dashboard::MilestonesController do expect(response.body).to include("Open\n2") expect(response.body).to include("Closed\n0") end + + context 'external authorization' do + subject { get :index } + + it_behaves_like 'disabled when using an external authorization service' + end end end diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb index 649441f4917..c17cb49e460 100644 --- a/spec/controllers/dashboard/projects_controller_spec.rb +++ b/spec/controllers/dashboard/projects_controller_spec.rb @@ -1,7 +1,29 @@ require 'spec_helper' describe Dashboard::ProjectsController do - it_behaves_like 'authenticates sessionless user', :index, :atom + include ExternalAuthorizationServiceHelpers + + describe '#index' do + context 'user not logged in' do + it_behaves_like 'authenticates sessionless user', :index, :atom + end + + context 'user logged in' do + before do + sign_in create(:user) + end + + context 'external authorization' do + it 'works when the external authorization service is enabled' do + enable_external_authorization_service_check + + get :index + + expect(response).to have_gitlab_http_status(200) + end + end + end + end context 'json requests' do render_views diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index d88beaff0e1..abbf0b52306 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -105,6 +105,12 @@ describe Dashboard::TodosController do end end end + + context 'external authorization' do + subject { get :index } + + it_behaves_like 'disabled when using an external authorization service' + end end describe 'PATCH #restore' do diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb index 772d1d0c1dd..6ececa6f372 100644 --- a/spec/controllers/groups/avatars_controller_spec.rb +++ b/spec/controllers/groups/avatars_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::AvatarsController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } @@ -15,4 +17,12 @@ describe Groups::AvatarsController do expect(@group.avatar.present?).to be_falsey expect(@group).to be_valid end + + it 'works when external authorization service is enabled' do + enable_external_authorization_service_check + + delete :destroy, params: { group_id: group } + + expect(response).to have_gitlab_http_status(302) + end end diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index 27ee37b3817..0ca5ce51750 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -82,6 +82,10 @@ describe Groups::BoardsController do end end + it_behaves_like 'disabled when using an external authorization service' do + subject { list_boards } + end + def list_boards(format: :html) get :index, params: { group_id: group }, format: format end @@ -160,6 +164,10 @@ describe Groups::BoardsController do end end + it_behaves_like 'disabled when using an external authorization service' do + subject { read_board board: board } + end + def read_board(board:, format: :html) get :show, params: { group_id: group, diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb index e1b97013408..4085c8f95a9 100644 --- a/spec/controllers/groups/children_controller_spec.rb +++ b/spec/controllers/groups/children_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::ChildrenController do + include ExternalAuthorizationServiceHelpers + let(:group) { create(:group, :public) } let(:user) { create(:user) } let!(:group_member) { create(:group_member, group: group, user: user) } @@ -317,5 +319,15 @@ describe Groups::ChildrenController do end end end + + context 'external authorization' do + it 'works when external authorization service is enabled' do + enable_external_authorization_service_check + + get :index, params: { group_id: group }, format: :json + + expect(response).to have_gitlab_http_status(200) + end + end end end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 3a801fabafc..96a58d6d87c 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -1,8 +1,11 @@ require 'spec_helper' describe Groups::GroupMembersController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } + let(:membership) { create(:group_member, group: group) } describe 'GET index' do it 'renders index with 200 status code' do @@ -263,4 +266,87 @@ describe Groups::GroupMembersController do end end end + + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + group.add_owner(user) + sign_in(user) + end + + describe 'GET #index' do + it 'is successful' do + get :index, params: { group_id: group } + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'POST #create' do + it 'is successful' do + post :create, params: { group_id: group, users: user, access_level: Gitlab::Access::GUEST } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'PUT #update' do + it 'is successful' do + put :update, + params: { + group_member: { access_level: Gitlab::Access::GUEST }, + group_id: group, + id: membership + }, + format: :js + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'DELETE #destroy' do + it 'is successful' do + delete :destroy, params: { group_id: group, id: membership } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #destroy' do + it 'is successful' do + sign_in(create(:user)) + + post :request_access, params: { group_id: group } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #approve_request_access' do + it 'is successful' do + access_request = create(:group_member, :access_request, group: group) + post :approve_access_request, params: { group_id: group, id: access_request } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'DELETE #leave' do + it 'is successful' do + group.add_owner(create(:user)) + + delete :leave, params: { group_id: group } + + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #resend_invite' do + it 'is successful' do + post :resend_invite, params: { group_id: group, id: membership } + + expect(response).to have_gitlab_http_status(302) + end + end + end end diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index fa664a29066..9af47114838 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -37,6 +37,12 @@ describe Groups::LabelsController do expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) end end + + context 'external authorization' do + subject { get :index, params: { group_id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end end describe 'POST #toggle_subscription' do diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 043cf28514b..d70946cbc8f 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -80,6 +80,12 @@ describe Groups::MilestonesController do expect(response.content_type).to eq 'application/json' end end + + context 'external authorization' do + subject { get :index, params: { group_id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end end describe '#show' do diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index 3290ed8b088..b998f64ef72 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::Settings::CiCdController do + include ExternalAuthorizationServiceHelpers + let(:group) { create(:group) } let(:user) { create(:user) } @@ -33,6 +35,19 @@ describe Groups::Settings::CiCdController do expect(response).to have_gitlab_http_status(404) end end + + context 'external authorization' do + before do + enable_external_authorization_service_check + group.add_owner(user) + end + + it 'renders show with 200 status code' do + get :show, params: { group_id: group } + + expect(response).to have_gitlab_http_status(200) + end + end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb index 29ec3588316..40f05167350 100644 --- a/spec/controllers/groups/variables_controller_spec.rb +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Groups::VariablesController do + include ExternalAuthorizationServiceHelpers + let(:group) { create(:group) } let(:user) { create(:user) } @@ -34,4 +36,36 @@ describe Groups::VariablesController do include_examples 'PATCH #update updates variables' end + + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + end + + describe 'GET #show' do + let!(:variable) { create(:ci_group_variable, group: group) } + + it 'is successful' do + get :show, params: { group_id: group }, format: :json + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'PATCH #update' do + let!(:variable) { create(:ci_group_variable, group: group) } + let(:owner) { group } + + it 'is successful' do + patch :update, + params: { + group_id: group, + variables_attributes: [{ id: variable.id, key: 'hello' }] + }, + format: :json + + expect(response).to have_gitlab_http_status(200) + end + end + end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 4a28a27da79..431627cf85a 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe GroupsController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:admin) { create(:admin) } let(:group) { create(:group, :public) } @@ -665,4 +667,98 @@ describe GroupsController do end end end + + describe 'external authorization' do + before do + group.add_owner(user) + sign_in(user) + end + + context 'with external authorization service enabled' do + before do + enable_external_authorization_service_check + end + + describe 'GET #show' do + it 'is successful' do + get :show, params: { id: group.to_param } + + expect(response).to have_gitlab_http_status(200) + end + + it 'does not allow other formats' do + get :show, params: { id: group.to_param }, format: :atom + + expect(response).to have_gitlab_http_status(403) + end + end + + describe 'GET #edit' do + it 'is successful' do + get :edit, params: { id: group.to_param } + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'GET #new' do + it 'is successful' do + get :new + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'GET #index' do + it 'is successful' do + get :index + + # Redirects to the dashboard + expect(response).to have_gitlab_http_status(302) + end + end + + describe 'POST #create' do + it 'creates a group' do + expect do + post :create, params: { group: { name: 'a name', path: 'a-name' } } + end.to change { Group.count }.by(1) + end + end + + describe 'PUT #update' do + it 'updates a group' do + expect do + put :update, params: { id: group.to_param, group: { name: 'world' } } + end.to change { group.reload.name } + end + end + + describe 'DELETE #destroy' do + it 'deletes the group' do + delete :destroy, params: { id: group.to_param } + + expect(response).to have_gitlab_http_status(302) + end + end + end + + describe 'GET #activity' do + subject { get :activity, params: { id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end + + describe 'GET #issues' do + subject { get :issues, params: { id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end + + describe 'GET #merge_requests' do + subject { get :merge_requests, params: { id: group.to_param } } + + it_behaves_like 'disabled when using an external authorization service' + end + end end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 1eeded06459..b1203fd00b0 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -98,6 +98,10 @@ describe Projects::BoardsController do end end + it_behaves_like 'unauthorized when external service denies access' do + subject { list_boards } + end + def list_boards(format: :html) get :index, params: { namespace_id: project.namespace, diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index c34d7c13d57..bfa23af76d5 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -127,6 +127,17 @@ describe Projects::IssuesController do expect(assigns(:issues).size).to eq(2) end end + + context 'external authorization' do + before do + sign_in user + project.add_developer(user) + end + + it_behaves_like 'unauthorized when external service denies access' do + subject { get :index, params: { namespace_id: project.namespace, project_id: project } } + end + end end describe 'GET #new' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index af437c5561b..1ce06bc877c 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1,6 +1,7 @@ require('spec_helper') describe ProjectsController do + include ExternalAuthorizationServiceHelpers include ProjectForksHelper let(:project) { create(:project) } @@ -411,6 +412,37 @@ describe ProjectsController do it_behaves_like 'updating a project' end + + context 'as maintainer' do + before do + project.add_maintainer(user) + sign_in(user) + end + + it_behaves_like 'unauthorized when external service denies access' do + subject do + put :update, + params: { + namespace_id: project.namespace, + id: project, + project: { description: 'Hello world' } + } + project.reload + end + + it 'updates when the service allows access' do + external_service_allow_access(user, project) + + expect { subject }.to change(project, :description) + end + + it 'does not update when the service rejects access' do + external_service_deny_access(user, project) + + expect { subject }.not_to change(project, :description) + end + end + end end describe '#transfer' do diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 02a0cfe0272..752d6ae55cc 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe SearchController do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } before do @@ -76,4 +78,41 @@ describe SearchController do expect(assigns[:search_objects].count).to eq(0) end end + + context 'with external authorization service enabled' do + let(:project) { create(:project, namespace: user.namespace) } + let(:note) { create(:note_on_issue, project: project) } + + before do + enable_external_authorization_service_check + end + + describe 'GET #show' do + it 'renders a 403 when no project is given' do + get :show, params: { scope: 'notes', search: note.note } + + expect(response).to have_gitlab_http_status(403) + end + + it 'renders a 200 when a project was set' do + get :show, params: { project_id: project.id, scope: 'notes', search: note.note } + + expect(response).to have_gitlab_http_status(200) + end + end + + describe 'GET #autocomplete' do + it 'renders a 403 when no project is given' do + get :autocomplete, params: { term: 'hello' } + + expect(response).to have_gitlab_http_status(403) + end + + it 'renders a 200 when a project was set' do + get :autocomplete, params: { project_id: project.id, term: 'hello' } + + expect(response).to have_gitlab_http_status(200) + end + end + end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 4f6a6881193..42d28c53d34 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -223,6 +223,12 @@ describe UsersController do end end + context 'external authorization' do + subject { get :calendar_activities, params: { username: user.username } } + + it_behaves_like 'disabled when using an external authorization service' + end + def create_push_event push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) EventCreateService.new.push(project, public_user, push_data) @@ -286,6 +292,12 @@ describe UsersController do expect(JSON.parse(response.body)).to have_key('html') end end + + context 'external authorization' do + subject { get :snippets, params: { username: user.username } } + + it_behaves_like 'disabled when using an external authorization service' + end end describe 'GET #exists' do diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb new file mode 100644 index 00000000000..4098dd02141 --- /dev/null +++ b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'The group dashboard' do + include ExternalAuthorizationServiceHelpers + + let(:user) { create(:user) } + + before do + sign_in user + end + + describe 'The top navigation' do + it 'has all the expected links' do + visit dashboard_groups_path + + within('.navbar') do + expect(page).to have_button('Projects') + expect(page).to have_button('Groups') + expect(page).to have_link('Activity') + expect(page).to have_link('Milestones') + expect(page).to have_link('Snippets') + end + end + + it 'hides some links when an external authorization service is enabled' do + enable_external_authorization_service_check + visit dashboard_groups_path + + within('.navbar') do + expect(page).to have_button('Projects') + expect(page).to have_button('Groups') + expect(page).not_to have_link('Activity') + expect(page).not_to have_link('Milestones') + expect(page).to have_link('Snippets') + end + end + end +end diff --git a/spec/features/groups/group_page_with_external_authorization_service_spec.rb b/spec/features/groups/group_page_with_external_authorization_service_spec.rb new file mode 100644 index 00000000000..c05c3f4f3d6 --- /dev/null +++ b/spec/features/groups/group_page_with_external_authorization_service_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'The group page' do + include ExternalAuthorizationServiceHelpers + + let(:user) { create(:user) } + let(:group) { create(:group) } + + before do + sign_in user + group.add_owner(user) + end + + def expect_all_sidebar_links + within('.nav-sidebar') do + expect(page).to have_link('Overview') + expect(page).to have_link('Details') + expect(page).to have_link('Activity') + expect(page).to have_link('Issues') + expect(page).to have_link('Merge Requests') + expect(page).to have_link('Members') + end + end + + describe 'The sidebar' do + it 'has all the expected links' do + visit group_path(group) + + expect_all_sidebar_links + end + + it 'shows all project features when policy control is enabled' do + stub_application_setting(external_authorization_service_enabled: true) + + visit group_path(group) + + expect_all_sidebar_links + end + + it 'hides some links when an external authorization service configured with an url' do + enable_external_authorization_service_check + visit group_path(group) + + within('.nav-sidebar') do + expect(page).to have_link('Overview') + expect(page).to have_link('Details') + expect(page).not_to have_link('Activity') + expect(page).not_to have_link('Contribution Analytics') + + expect(page).not_to have_link('Issues') + expect(page).not_to have_link('Merge Requests') + expect(page).to have_link('Members') + end + end + end +end diff --git a/spec/features/projects/classification_label_on_project_pages_spec.rb b/spec/features/projects/classification_label_on_project_pages_spec.rb new file mode 100644 index 00000000000..92f8aa8eb8d --- /dev/null +++ b/spec/features/projects/classification_label_on_project_pages_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Classification label on project pages' do + let(:project) do + create(:project, external_authorization_classification_label: 'authorized label') + end + let(:user) { create(:user) } + + before do + stub_application_setting(external_authorization_service_enabled: true) + project.add_maintainer(user) + sign_in(user) + end + + it 'shows the classification label on the project page' do + visit project_path(project) + + expect(page).to have_content('authorized label') + end +end diff --git a/spec/features/projects/forks/fork_list_spec.rb b/spec/features/projects/forks/fork_list_spec.rb new file mode 100644 index 00000000000..2c41c61a660 --- /dev/null +++ b/spec/features/projects/forks/fork_list_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'listing forks of a project' do + include ProjectForksHelper + include ExternalAuthorizationServiceHelpers + + let(:source) { create(:project, :public, :repository) } + let!(:fork) { fork_project(source, nil, repository: true) } + let(:user) { create(:user) } + + before do + source.add_maintainer(user) + sign_in(user) + end + + it 'shows the forked project in the list with commit as description' do + visit project_forks_path(source) + + page.within('li.project-row') do + expect(page).to have_content(fork.full_name) + expect(page).to have_css('a.commit-row-message') + end + end + + it 'does not show the commit message when an external authorization service is used' do + enable_external_authorization_service_check + + visit project_forks_path(source) + + page.within('li.project-row') do + expect(page).to have_content(fork.full_name) + expect(page).not_to have_css('a.commit-row-message') + end + end +end diff --git a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb new file mode 100644 index 00000000000..a8612d77a5e --- /dev/null +++ b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +describe 'viewing an issue with cross project references' do + include ExternalAuthorizationServiceHelpers + include Gitlab::Routing.url_helpers + + let(:user) { create(:user) } + let(:other_project) do + create(:project, :public, + external_authorization_classification_label: 'other_label') + end + let(:other_issue) do + create(:issue, :closed, + title: 'I am in another project', + project: other_project) + end + let(:other_confidential_issue) do + create(:issue, :confidential, :closed, + title: 'I am in another project and confidential', + project: other_project) + end + let(:other_merge_request) do + create(:merge_request, :closed, + title: 'I am a merge request in another project', + source_project: other_project) + end + let(:description_referencing_other_issue) do + "Referencing: #{other_issue.to_reference(project)}, "\ + "a confidential issue #{confidential_issue.to_reference}, "\ + "a cross project confidential issue #{other_confidential_issue.to_reference(project)}, and "\ + "a cross project merge request #{other_merge_request.to_reference(project)}" + end + let(:project) { create(:project) } + let(:issue) do + create(:issue, + project: project, + description: description_referencing_other_issue ) + end + let(:confidential_issue) do + create(:issue, :confidential, :closed, + title: "I am in the same project and confidential", + project: project) + end + + before do + project.add_developer(user) + sign_in(user) + end + + it 'shows all information related to the cross project reference' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_issue.to_reference(project)} (#{other_issue.state})") + expect(page).to have_xpath("//a[@title='#{other_issue.title}']") + end + + it 'shows a link to the confidential issue in the same project' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})") + expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']") + end + + it 'does not show the link to a cross project confidential issue when the user does not have access' do + visit project_issue_path(project, issue) + + expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})") + expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + it 'shows the link to a cross project confidential issue when the user has access' do + other_project.add_developer(user) + + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})") + expect(page).to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + context 'when an external authorization service is enabled' do + before do + enable_external_authorization_service_check + end + + it 'only hits the external service for the project the user is viewing' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'default_label', any_args).at_least(1).and_return(true) + expect(::Gitlab::ExternalAuthorization) + .not_to receive(:access_allowed?).with(user, 'other_label', any_args) + + visit project_issue_path(project, issue) + end + + it 'shows only the link to the cross project references' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_issue.to_reference(project)}") + expect(page).to have_link("#{other_merge_request.to_reference(project)}") + expect(page).not_to have_content("#{other_issue.to_reference(project)} (#{other_issue.state})") + expect(page).not_to have_xpath("//a[@title='#{other_issue.title}']") + expect(page).not_to have_content("#{other_merge_request.to_reference(project)} (#{other_merge_request.state})") + expect(page).not_to have_xpath("//a[@title='#{other_merge_request.title}']") + end + + it 'does not link a cross project confidential issue if the user does not have access' do + visit project_issue_path(project, issue) + + expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)}") + expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + it 'links a cross project confidential issue without exposing information when the user has access' do + other_project.add_developer(user) + + visit project_issue_path(project, issue) + + expect(page).to have_link("#{other_confidential_issue.to_reference(project)}") + expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']") + end + + it 'shows a link to the confidential issue in the same project' do + visit project_issue_path(project, issue) + + expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})") + expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']") + end + end +end diff --git a/spec/features/projects/settings/external_authorization_service_settings_spec.rb b/spec/features/projects/settings/external_authorization_service_settings_spec.rb new file mode 100644 index 00000000000..31b2892cf6f --- /dev/null +++ b/spec/features/projects/settings/external_authorization_service_settings_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Projects > Settings > External Authorization Classification Label setting' do + let(:user) { create(:user) } + let(:project) { create(:project_empty_repo) } + + before do + project.add_maintainer(user) + sign_in(user) + end + + it 'shows the field to set a classification label' do + stub_application_setting(external_authorization_service_enabled: true) + + visit edit_project_path(project) + + expect(page).to have_selector('#project_external_authorization_classification_label') + end +end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 86379164cf0..351750c0179 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'User page' do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } context 'with public profile' do @@ -86,4 +88,24 @@ describe 'User page' do end end end + + context 'most recent activity' do + it 'shows the most recent activity' do + visit(user_path(user)) + + expect(page).to have_content('Most Recent Activity') + end + + context 'when external authorization is enabled' do + before do + enable_external_authorization_service_check + end + + it 'hides the most recent activity' do + visit(user_path(user)) + + expect(page).not_to have_content('Most Recent Activity') + end + end + end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 6e6dc334354..6a47cd013f8 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -531,6 +531,13 @@ describe IssuesFinder do expect(issues.count).to eq 0 end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:issue, project: project) } + let(:project_params) { { project_id: project.id } } + end + end end describe '#row_count', :request_store do diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index 3f060ba0553..98b4933fef6 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -226,5 +226,12 @@ describe LabelsFinder do expect(finder.execute).to eq [project_label_1] end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:label, project: project) } + let(:project_params) { { project_id: project.id } } + end + end end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index ef002125635..117f4a03735 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -282,6 +282,13 @@ describe MergeRequestsFinder do expect(finder.row_count).to eq(1) end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:merge_request, source_project: project) } + let(:project_params) { { project_id: project.id } } + end + end end context 'when projects require different access levels for merge requests' do diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 93287f3e9b8..d367f9015c7 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe SnippetsFinder do + include ExternalAuthorizationServiceHelpers include Gitlab::Allowable describe '#initialize' do @@ -164,4 +165,35 @@ describe SnippetsFinder do end it_behaves_like 'snippet visibility' + + context 'external authorization' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let!(:snippet) { create(:project_snippet, :public, project: project) } + + before do + project.add_maintainer(user) + end + + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:project_snippet, project: project) } + let(:project_params) { { project: project } } + end + + it 'includes the result if the external service allows access' do + external_service_allow_access(user, project) + + results = described_class.new(user, project: project).execute + + expect(results).to contain_exactly(snippet) + end + + it 'does not include any results if the external service denies access' do + external_service_deny_access(user, project) + + results = described_class.new(user, project: project).execute + + expect(results).to be_empty + end + end end diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index d4ed41d54f0..22318a9946a 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -47,6 +47,13 @@ describe TodosFinder do end end end + + context 'external authorization' do + it_behaves_like 'a finder with external authorization service' do + let!(:subject) { create(:todo, project: project, user: user) } + let(:project_params) { { project_id: project.id } } + end + end end describe '#sort' do diff --git a/spec/fixtures/passphrase_x509_certificate.crt b/spec/fixtures/passphrase_x509_certificate.crt new file mode 100644 index 00000000000..6973163b79e --- /dev/null +++ b/spec/fixtures/passphrase_x509_certificate.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEpTCCAo0CAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5 +MB4XDTE4MDMyMzE0MDIwOFoXDTE5MDMyMzE0MDIwOFowHTEbMBkGA1UEAwwSZ2l0 +bGFiLXBhc3NwaHJhc2VkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +zpsWHOewP/khfDsLUWxaRCinrBzVJm2C01bVahKVR3g/JD4vEH901Wod9Pvbh/9e +PEfE+YZmgSUUopbL3JUheMnyW416F43HKE/fPW4+QeuIEceuhCXg20eOXmvnWWNM +0hXZh4hq69rwvMPREC/LkZy/QkTDKhJNLNAqAQu2AJ3C7Yga8hFQYEhx1hpfGtwD +z/Nf3efat9WN/d6yW9hfJ98NCmImTm5l9Pc0YPNWCAf96vsqsNHBrTkFy6CQwkhH +K1ynVYuqnHYxSc4FPCT5SAleD9gR/xFBAHb7pPy4yGxMSEmiWaMjjZCVPsghj1jM +Ej77MTDL3U9LeDfiILhvZ+EeQxqPiFwwG2eaIn3ZEs2Ujvw7Z2VpG9VMcPTnB4jK +ot6qPM1YXnkGWQ6iT0DTPS3h7zg1xIJXI5N2sI6GXuKrXXwZ1wPqzFLKPv+xBjp8 +P6dih+EImfReFi9zIO1LqGMY+XmRcqodsb6jzsmBimJkqBtatJM7FuUUUN56wiaj +q9+BWbm+ZdQ2lvqndMljjUjTh6pNERfGAJgkNuLn3X9hXVE0TSpmn0nOgaL5izP3 +7FWUt0PTyGgK2zq9SEhZmK2TKckLkKMk/ZBBBVM/nrnjs72IlbsqdcVoTnApytZr +xVYTj1hV7QlAfaU3w/M534qXDiy8+HfX5ksWQMtSklECAwEAATANBgkqhkiG9w0B +AQUFAAOCAgEAMMhzSRq9PqCpui74nwjhmn8Dm2ky7A+MmoXNtk70cS/HWrjzaacb +B/rxsAUp7f0pj4QMMM0ETMFpbNs8+NPd2FRY0PfWE4yyDpvZO2Oj1HZKLHX72Gjn +K5KB9DYlVsXhGPfuFWXpxGWF2Az9hDWnj58M3DOAps+6tHuAtudQUuwf5ENQZWwE +ySpr7yoHm1ykgl0Tsb9ZHi9qLrWRRMNYXRT+gvwP1bba8j9jOtjO/xYiIskwMPLM +W8SFmQxbg0Cvi8Q89PB6zoTNOhPQyoyeSlw9meeZJHAMK2zxeglEm8C4EQ+I9Y6/ +yylM5/Sc55TjWAvRFgbsq+OozgMvffk/Q2fzcGF44J9DEQ7nrhmJxJ+X4enLknR5 +Hw4+WhdYA+bwjx3YZBNTh9/YMgNPYwQhf5gtcZGTd6X4j6qZfJ6CXBmhkC1Cbfyl +yM7B7i4JAqPWMeDP50pXCgyKlwgw1JuFW+xkbkYQAj7wtggQ6z1Vjb5W8R8kYn9q +LXClVtThEeSV5KkVwNX21aFcUs8qeQ+zsgKqpEyM5oILQQ1gDSxLTtrr2KuN+WJN +wM0acwD45X7gA/aZYpCGkIgHIBq0zIDP1s6IqeebFJjW8lWofhRxOEWomWdRweJG +N7qQ1WCTQxAPGAkDI8QPjaspvnAhFKmpBG/mR5IXLFKDbttu7WNdYDo= +-----END CERTIFICATE----- diff --git a/spec/fixtures/passphrase_x509_certificate_pk.key b/spec/fixtures/passphrase_x509_certificate_pk.key new file mode 100644 index 00000000000..f9760dfe70e --- /dev/null +++ b/spec/fixtures/passphrase_x509_certificate_pk.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,79CCB506B0FD42A6F1BAE6D72E1CB20C + +EuZQOfgaO6LVCNytTHNJmbiq1rbum9xg6ohfBTVt7Cw4+8yLezWva/3sJQtnEk2P +M2yEQYWIiCX+clPkRiRL8WLjRfLTNcYS6QxxuJdpOrowPrBYr4Aig8jBUUBI4VQf +w1ZEUQd0mxQGnyzkKpsudFOntCtZbvbrBsIAQUNLcrKEFk3XW/BqE1Q/ja6WfWqX +b6EKg6DoXi92V90O6sLDfpmTKZq3ThvVDFuWeJ2K/GVp2cs+MkBIBJ8XX+NT1nWg +g+Ok+yaSI/N9ILX4XDgXunJGwcooI8PhHSjkDWRusi8vbo7RFqIKiSF+h6tIwktF +Uss3JESKgXZCQ7upCnHSzK/aWFtwHtXxqOi7esqEZd+1sB0LY+XMnbaxweCMx2Kj +czktKYvoXUs69Whln+yyXULtl5XhJ8lbvlbIG2FbZ9y+/hHOyBqZyeUyCnXDzv8/ +0U0iZwreP3XPVMsy578pIdcdL27q+r05j4yjrJfbX3T9xp2u3F9uVubCa4euEBwV +yrFdsxJLKON8pFeDS49m5gHNsHmeZ0sUeTPZVGNXdabVetkOA0eAAGK4zAoqG79L +hEN7cDenz+E4XHp8gMzwwMiVyU4FuAb6SXkfSodctmSTWVbzNBja0FBek3UXy+pn +9qq7cIpe7NY5gzcbyoy9lSkyYVkAm8j6BIYtY1ZUAmtCklC2ADWARTjd7dI7aEbO +QbXxNIq2+O/zMOXfougSPoDP8SLyLuE1p6SwfWV7Dwf119hn+mjWlGzAZDxxHhsR +yYUQCUe0NIKzuUp3WYIx8xIb7/WFwit/JaFaxurjBnhkkEviBn+TgXiuFBO3tv/d +URpZ39rH0mrDsR61pCiIcoNVkQkynHcAFPd5VtaeSJPvZP280uOCPPS31cr6/0LB +1JX3lZoWWCuA+JQjxtZDaDTcvEUbfOQ2rexQQo4uylNkBF9F5WOdQBkKG/AfqBq8 +S/TdubYzvpcKhFAlXsI67JdbxGlU4HCsxOLwWzSUYclN4W3l7s7KZ5zxt+MU03Uf +vara9uuZHiKUjZohjXeqcXTc+UyC8VH1dF19M3Cj9RNrwl2xEDUMtIiALBjbGp1E +pu2nPj9NhWf9Vw5MtSszutesxXba2nPmvvGvvZ7N3h/k4NsKL7JdENF7XqkI0D2K +jpO1t6d3cazS1VpMWLZS45kWaM3Y07tVR3V+4Iv9Vo1e9H2u/Z5U4YeJ44sgMsct +dBOAhHdUAI5+P+ocLXiCKo+EcS0cKvz+CC4ux0vvcF3JrTqZJN1U/JxRka2EyJ1B +2Xtu3DF36XpBJcs+MJHjJ+kUn6DHYoYxZa+bB8LX6+FQ+G7ue+Dx/RsGlP7if1nq +DAaM6kZg7/FbFzOZyl5xhwAJMxfgNNU7nSbk9lrvQ4mdwgFjvgGu3jlER4+TcleE +4svXInxp1zK6ES44tI9fXkhPaFkafxAL7eUSyjjEwMC06h+FtqK3mmoKLo5NrGJE +zVl69r2WdoSQEylVN1Kbp+U4YbfncInLJqBq2q5w9ASL/8Rhe8b52q6PuVX/bjoz +0pkSu+At4jVbAhRpER5NGlzG884IaqqvBvMYR5zFJeRroIijyUyH0KslK37/sXRk +ty0yKrkm31De9gDa3+XlgAVDAgbEQmGVwVVcV0IYYJbjIf36lUdGh4+3krwxolr/ +vZct5Z7QxfJlBtdOstjz5U9o05yOhjoNrPZJXuKMmWOQjSwr7rRSdqmAABF9IrBf +Pa/ChF1y5j3gJESAFMyiea3kvLq1EbZRaKoybsQE2ctBQ8EQjzUz+OOxVO6GJ4W9 +XHyfcviFrpsVcJEpXQlEtGtKdfKLp48cytob1Fu1JOYPDCrafUQINCZP4H3Nt892 +zZiTmdwux7pbgf4KbONImN5XkpvdCGjQHSkYMmm5ETRK8s7Fmvt2aBPtlyXxJDOq +iJUqwDV5HZXOnQVE/v/yESKgo2Cb8BWqPZ4/8Ubgu/OADYyv/dtjQel8QQ2FMhO4 +2tnwWbBBJk8VpR/vjFHkGSnj+JJfW/vUVQ+06D3wHYhNp7mh4M+37AngwzGCp7k+ +9aFwb2FBGghArB03E4lIO/959T0cX95WZ6tZtLLEsf3+ug7PPOSswCqsoPsXzFJH +MgXVGKFXccNSsWol7VvrX/uja7LC1OE+pZNXxCRzSs4aljJBpvQ6Mty0lk2yBC0R +MdujMoZH9PG9U6stwFd+P17tlGrQdRD3H2uimn82Ck+j2l0z0pzN0JB2WBYEyK0O +1MC36wLICWjgIPLPOxDEEBeZPbc24DCcYfs/F/hSCHv/XTJzVVILCX11ShGPSXlI +FL9qyq6jTNh/pVz6NiN/WhUPBFfOSzLRDyU0MRsSHM8b/HPpf3NOI3Ywmmj65c2k +2kle1F2M5ZTL+XvLS61qLJ/8AgXWvDHP3xWuKGG/pM40CRTUkRW6NAokMr2/pEFw +IHTE2+84dOKnUIEczzMY3aqzNmYDCmhOY0jD/Ieb4hy9tN+1lbQ/msYMIJ1w7CFR +38yB/UbDD90NcuDhjrMbzVUv1At2rW7GM9lSbxGOlYDmtMNEL63md1pQ724v4gSE +mzoFcMkqdh+hjFvv11o4H32lF3mPYcXuL+po76tqxGOiUrLKe/ZqkT5XAclYV/7H +k3Me++PCh4ZqXBRPvR8Xr90NETtiFCkBQXLdhNWXrRe2v0EbSX+cYAWk68FQKCHa +HKTz9T7wAvB6QWBXFhH9iCP8rnQLCEhLEhdrt+4v2KFkIVzBgOlMoHsZsMp0sBeq +c5ZVbJdiKik3P/8ZQTn4jmOnQXCEyWx+LU4acks8Aho4lqq9yKq2DZpwbIRED47E +r7R/NUevhqqzEHZ2SGD6EDqRN+bHJEi64vq0ryaEielusYXZqlnFXDHJcfLCmR5X +3bj5pCwQF4ScTukrGQB/c4henG4vlF4CaD0CIIK3W6tH+AoDohYJts6YK49LGxmK +yXiyKNak8zHYBBoRvd2avRHyGuR5yC9KrN8cbC/kZqMDvAyM65pIK+U7exJwYJhv +ezCcbiH3bK3anpiRpdeNOot2ba/Y+/ks+DRC+xs4QDIhrmSEBCsLv1JbcWjtHSaG +lm+1DSVduUk/kN+fBnlfif+TQV9AP3/wb8ekk8jjKXsL7H1tJKHsLLIIvrgrpxjw +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/x509_certificate.crt b/spec/fixtures/x509_certificate.crt new file mode 100644 index 00000000000..8a84890b928 --- /dev/null +++ b/spec/fixtures/x509_certificate.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEnDCCAoQCAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5 +MB4XDTE4MDMxOTE1MjYzMloXDTE5MDMxOTE1MjYzMlowFDESMBAGA1UEAwwJbG9j +YWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+tcM7iphsLlR +ccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzAn/eVU4jyVWkaBym6MHa8CiDOro9H +OXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/2FAgFWzrB2HnYSShiN8tBeeDI5cJ +ii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UTK37k2kbDQZ41rv1ng2w0AUZt0LRA +NWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ ++1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNkI+cyv0Gle6tk+CkOfE1m0CvNWlNg +b8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5xMXpdUCsh22CZZHe/4SeFE64amkf +1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q/nLdY8haMC6KOtpbAWvKX/Jqq0z1 +nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVrVef0pb2mfdtzjzUrYCP0PtnQExPB +rocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8RSvAoEUs9VbPiUfN7WAyU1K1rTYH +KV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRlq07Q5LDz33h9KXw1LZT8MWRinVJf +RePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEAATANBgkqhkiG9w0BAQUFAAOCAgEA +Skp0tbvVsg3RG2pX0GP25j0ix+f78zG0+BJ6LiKGMoCIBtGKitfUjBg83ru/ILpa +fpgrQpNQVUnGQ9tmpnqV605ZBBRUC1CRDsvUnyN6p7+yQAq6Fl+2ZKONHpPk+Bl4 +CIewgdkHjTwTpvIM/1DFVCz4R1FxNjY3uqOVcNDczMYEk2Pn2GZNNN35hUHHxWh4 +89ZvI+XKuRFZq3cDPA60PySeJJpCRScWGgnkdEX1gTtWH3WUlq9llxIvRexyNyzZ +Yqvcfx5UT75/Pp+JPh9lpUCcKLHeUiadjkiLxu3IcrYa4gYx4lA8jgm7adNEahd0 +oMAHoO9DU6XMo7o6tnQH3xQv9RAbQanjuyJR9N7mwmc59bQ6mW+pxCk843GwT73F +slseJ1nE1fQQQD7mn/KGjmeWtxY2ElUjTay9ff9/AgJeQYRW+oH0cSdo8WCpc2+G ++LZtLWfBgFLHseRlmarSe2pP8KmbaTd3q7Bu0GekVQOxYcNX59Pj4muQZDVLh8aX +mSQ+Ifts/ljT649MISHn2AZMR4+BUx63tFcatQhbAGGH5LeFdbaGcaVdsUVyZ9a2 +HBmFWNsgEPtcC+WmNzCXbv7jQsLAJXufKG5MnurJgNf/n5uKCmpGsEJDT/KF1k/3 +x9YnqM7zTyV6un+LS3HjEJvwQmqPWe+vFAeXWGCoWxE= +-----END CERTIFICATE----- diff --git a/spec/fixtures/x509_certificate_pk.key b/spec/fixtures/x509_certificate_pk.key new file mode 100644 index 00000000000..c02a3cf6189 --- /dev/null +++ b/spec/fixtures/x509_certificate_pk.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEA+tcM7iphsLlRccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzA +n/eVU4jyVWkaBym6MHa8CiDOro9HOXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/ +2FAgFWzrB2HnYSShiN8tBeeDI5cJii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UT +K37k2kbDQZ41rv1ng2w0AUZt0LRANWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK +88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ+1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNk +I+cyv0Gle6tk+CkOfE1m0CvNWlNgb8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5 +xMXpdUCsh22CZZHe/4SeFE64amkf1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q +/nLdY8haMC6KOtpbAWvKX/Jqq0z1nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVr +Vef0pb2mfdtzjzUrYCP0PtnQExPBrocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8 +RSvAoEUs9VbPiUfN7WAyU1K1rTYHKV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRl +q07Q5LDz33h9KXw1LZT8MWRinVJfRePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEA +AQKCAgBf1urJ1Meeji/gGETVx9qBWLbDjn9QTayZSyyEd78155tDShIPDLmxQRHW +MGIReo/5FGSkOgS+DWBZRZ77oGOGrtuMnjkheXhDr8dZvw5b1PBv5ntqWrLnfMYP +/Ag7xZMyiJLbPqmMX5j1gsFt8zPzUoVMnnl9DYryV0Edrs/utHgfJCM+6yzleUQB +PkGkqo1yWVVFZ3Nt2nDt9dNsdlC594+dYQ1m2JuArNvYNiw3dpHT98GnhRc1aLh4 +U+q22FiFn3BKGQat43JdlaLa6KO5f8MIQRYWuI8tss2DGPlhRv9AnUcVsLBjAuIH +bmUVrBosxCYUQ6giatjd2sZPfdC+VIDCbIWRthxkXJ9I/Ap8R98xx/7qIcPFc+XA +hcK1xOM7zIq2xgAOFeeh8O8Wq9cH8NmUhMCgzIE0WT32Zo0JAW6l0kZc82Y/Yofz +U+TJKo0NOFZe687HOhanOHbbQSG29XOqxMYTABZ7Ixf+4RZPD5+yQgZWP1BhLluy +PxZhsLl67xvbfB2i9VVorMN7PbFx5hbni3C7/p63Z0rG5q4/uJBbX3Uuh6KdhIo+ +Zh9UC6u29adIthdxz+ZV5wBccTOgaeHB9wRL9Hbp6ZxyqesQB4RTsFtPNXxZ7K43 +fmJgHZvHhF5gSbeB8JAeBf0cy3pytJM49ZxplifeGVzUJP2gAQKCAQEA/1T9quz5 +sOD03FxV//oRWD1kqfunq3v56sIBG4ZMVZKUqc6wLjTmeklLYKq85AWX8gnCHi0g +nmG/xDh/rt1/IngMWP98WVuD67hFbrj87g7A7YGIiwZ2gi6hqhqmALN+5JjCSTPp +XOiPvNnXP0XM4gIHBXV8diHq5rF9NsSh4vx3OExr8KQqVzWoDcnnWNfnDlrFB8cq +ViII+UqdovXp59hAVOsc+pYAe+8JeQDX17H3U/NMkUw4gU2aWUCvUVjxi9oBG/CW +ncIdYuW8zne4qXbX7YLC0QUUIDVOWzhLauAUBduTqRTldJo0KAxu887tf+uStXs8 +RACLGIaBQw7BXQKCAQEA+38NFnpflKquU92xRtmqWAVaW7rm865ZO6EIaS4JII/N +/Ebu1YZrAhT0ruGJQaolYj8w79BEZRF2CYDPZxKFv/ye0O7rWCAGtCdWQ0BXcrIU +7SdlsdfTNXO1R3WbwCyVxyjg6YF7FjbTaaOAoTiosTjDs2ZOgkbdh/sMeWkSN5HB +aQz4c8rqq0kkYucLqp4nWYSWSJn88bL8ctwEwW77MheJiSpo1ohNRP3ExHnbCbYw +RIj7ATSz74ebpd9NMauB5clvMMh4jRG0EQyt7KCoOyfPRFc3fddvTr03LlgFfX/n +qoxd2nejgAS3NnG1XMxdcUa7cPannt46Sef1uZo3gQKCAQB454zquCYQDKXGBu8u +NAKsjv2wxBqESENyV4VgvDo/NxawRdAFQUV12GkaEB87ti5aDSbfVS0h8lV1G+/S +JM5DyybFqcz/Hyebofk20d/q9g+DJ5g5hMjvIhepTc8Xe+d1ZaRyN2Oke/c8TMbx +DiNTTfR3MEfMRIlPzfHl0jx6GGR3wzBFleb6vsyiIt4qoqmlkXPFGBlDCgDH0v5M +ITgucacczuw8+HSoOut4Yd7TI1FjbkzubHJBQDb7VnbuBTjzqTpnOYiIkVeK8hBy +kBxgGodqz0Vi5o2+Jp/A8Co+JHc2wt/r65ovmali4WhUiMLLlQg2aXGDHeK/rUle +MIl9AoIBAQCPKCYSCnyHypRK5uG3W8VsLzfdCUnXogHnQGXiQTMu1szA8ruWzdnx +qG4TcgxIVYrMHv5DNAEKquLOzATDPjbmLu1ULvvGAQzv1Yhz5ZchkZ7507g+gIUY +YxHoaFjNDlP/txQ3tt2SqoizFD/vBap4nsA/SVgdLiuB8PSL07Rr70rx+lEe0H2+ +HHda2Pu6FiZ9/Uvybb0e8+xhkT4fwYW5YM6IRpzAqXuabv1nfZmiMJPPH04JxK88 +BKwjwjVVtbPOUlg5o5ODcXVXUylZjaXVbna8Bw1uU4hngKt9dNtDMeB0I0x1RC7M +e2Ky2g0LksUJ6uJdjfmiJAt38FLeYJuBAoIBAC2oqaqr86Dug5v8xHpgFoC5u7z7 +BRhaiHpVrUr+wnaNJEXfAEmyKf4xF5xDJqldnYG3c9ETG/7bLcg1dcrMPzXx94Si +MI3ykwiPeI/sVWYmUlq4U8zCIC7MY6sWzWt3oCBNoCN/EeYx9e7+eLNBB+fADAXq +v9RMGlUIy7beX0uac8Bs771dsxIb/RrYw58wz+jrwGlzuDmcPWiu+ARu7hnBqCAV +AITlCV/tsEk7u08oBuv47+rVGCh1Qb19pNswyTtTZARAGErJO0Q+39BNuu0M2TIn +G3M8eNmGHC+mNsZTVgKRuyk9Ye0s4Bo0KcqSndiPFGHjcrF7/t+RqEOXr/E= +-----END RSA PRIVATE KEY----- diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb new file mode 100644 index 00000000000..5dc2521b310 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/access_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do + subject(:access) { described_class.new(build(:user), 'dummy_label') } + + describe '#loaded?' do + it 'is `true` when it was loaded recently' do + Timecop.freeze do + allow(access).to receive(:loaded_at).and_return(5.minutes.ago) + + expect(access).to be_loaded + end + end + + it 'is `false` when there is no loading time' do + expect(access).not_to be_loaded + end + + it 'is `false` when there the result was loaded a long time ago' do + Timecop.freeze do + allow(access).to receive(:loaded_at).and_return(2.weeks.ago) + + expect(access).not_to be_loaded + end + end + end + + describe 'load!' do + let(:fake_client) { double('ExternalAuthorization::Client') } + let(:fake_response) do + double( + 'Response', + 'successful?' => true, + 'valid?' => true, + 'reason' => nil + ) + end + + before do + allow(access).to receive(:load_from_cache) + allow(fake_client).to receive(:request_access).and_return(fake_response) + allow(Gitlab::ExternalAuthorization::Client).to receive(:new) { fake_client } + end + + context 'when loading from the webservice' do + it 'loads from the webservice it the cache was empty' do + expect(access).to receive(:load_from_cache) + expect(access).to receive(:load_from_service).and_call_original + + access.load! + + expect(access).to be_loaded + end + + it 'assigns the accessibility, reason and loaded_at' do + allow(fake_response).to receive(:successful?).and_return(false) + allow(fake_response).to receive(:reason).and_return('Inaccessible label') + + access.load! + + expect(access.reason).to eq('Inaccessible label') + expect(access).not_to have_access + expect(access.loaded_at).not_to be_nil + end + + it 'returns itself' do + expect(access.load!).to eq(access) + end + + it 'stores the result in redis' do + Timecop.freeze do + fake_cache = double + expect(fake_cache).to receive(:store).with(true, nil, Time.now) + expect(access).to receive(:cache).and_return(fake_cache) + + access.load! + end + end + + context 'when the request fails' do + before do + allow(fake_client).to receive(:request_access) do + raise ::Gitlab::ExternalAuthorization::RequestFailed.new('Service unavailable') + end + end + + it 'is loaded' do + access.load! + + expect(access).to be_loaded + end + + it 'assigns the correct accessibility, reason and loaded_at' do + access.load! + + expect(access.reason).to eq('Service unavailable') + expect(access).not_to have_access + expect(access.loaded_at).not_to be_nil + end + + it 'does not store the result in redis' do + fake_cache = double + expect(fake_cache).not_to receive(:store) + allow(access).to receive(:cache).and_return(fake_cache) + + access.load! + end + end + end + + context 'When loading from cache' do + let(:fake_cache) { double('ExternalAuthorization::Cache') } + + before do + allow(access).to receive(:cache).and_return(fake_cache) + end + + it 'does not load from the webservice' do + Timecop.freeze do + expect(fake_cache).to receive(:load).and_return([true, nil, Time.now]) + + expect(access).to receive(:load_from_cache).and_call_original + expect(access).not_to receive(:load_from_service) + + access.load! + end + end + + it 'loads from the webservice when the cached result was too old' do + Timecop.freeze do + expect(fake_cache).to receive(:load).and_return([true, nil, 2.days.ago]) + + expect(access).to receive(:load_from_cache).and_call_original + expect(access).to receive(:load_from_service).and_call_original + allow(fake_cache).to receive(:store) + + access.load! + end + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb new file mode 100644 index 00000000000..58e7d626707 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/cache_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do + let(:user) { build_stubbed(:user) } + let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" } + + subject(:cache) { described_class.new(user, 'dummy_label') } + + def read_from_redis(key) + Gitlab::Redis::Cache.with do |redis| + redis.hget(cache_key, key) + end + end + + def set_in_redis(key, value) + Gitlab::Redis::Cache.with do |redis| + redis.hmset(cache_key, key, value) + end + end + + describe '#load' do + it 'reads stored info from redis' do + Timecop.freeze do + set_in_redis(:access, false) + set_in_redis(:reason, 'Access denied for now') + set_in_redis(:refreshed_at, Time.now) + + access, reason, refreshed_at = cache.load + + expect(access).to eq(false) + expect(reason).to eq('Access denied for now') + expect(refreshed_at).to be_within(1.second).of(Time.now) + end + end + end + + describe '#store' do + it 'sets the values in redis' do + Timecop.freeze do + cache.store(true, 'the reason', Time.now) + + expect(read_from_redis(:access)).to eq('true') + expect(read_from_redis(:reason)).to eq('the reason') + expect(read_from_redis(:refreshed_at)).to eq(Time.now.to_s) + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb new file mode 100644 index 00000000000..fa18c1e56e8 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/client_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Client do + let(:user) { build(:user, email: 'dummy_user@example.com') } + let(:dummy_url) { 'https://dummy.net/' } + subject(:client) { described_class.new(user, 'dummy_label') } + + before do + stub_application_setting(external_authorization_service_url: dummy_url) + end + + describe '#request_access' do + it 'performs requests to the configured endpoint' do + expect(Excon).to receive(:post).with(dummy_url, any_args) + + client.request_access + end + + it 'adds the correct params for the user to the body of the request' do + expected_body = { + user_identifier: 'dummy_user@example.com', + project_classification_label: 'dummy_label' + }.to_json + expect(Excon).to receive(:post) + .with(dummy_url, hash_including(body: expected_body)) + + client.request_access + end + + it 'respects the the timeout' do + stub_application_setting( + external_authorization_service_timeout: 3 + ) + + expect(Excon).to receive(:post).with(dummy_url, + hash_including( + connect_timeout: 3, + read_timeout: 3, + write_timeout: 3 + )) + + client.request_access + end + + it 'adds the mutual tls params when they are present' do + stub_application_setting( + external_auth_client_cert: 'the certificate data', + external_auth_client_key: 'the key data', + external_auth_client_key_pass: 'open sesame' + ) + expected_params = { + client_cert_data: 'the certificate data', + client_key_data: 'the key data', + client_key_pass: 'open sesame' + } + + expect(Excon).to receive(:post).with(dummy_url, hash_including(expected_params)) + + client.request_access + end + + it 'returns an expected response' do + expect(Excon).to receive(:post) + + expect(client.request_access) + .to be_kind_of(::Gitlab::ExternalAuthorization::Response) + end + + it 'wraps exceptions if the request fails' do + expect(Excon).to receive(:post) { raise Excon::Error.new('the request broke') } + + expect { client.request_access } + .to raise_error(::Gitlab::ExternalAuthorization::RequestFailed) + end + + describe 'for ldap users' do + let(:user) do + create(:omniauth_user, + email: 'dummy_user@example.com', + extern_uid: 'external id', + provider: 'ldapprovider') + end + + it 'includes the ldap dn for ldap users' do + expected_body = { + user_identifier: 'dummy_user@example.com', + project_classification_label: 'dummy_label', + user_ldap_dn: 'external id' + }.to_json + expect(Excon).to receive(:post) + .with(dummy_url, hash_including(body: expected_body)) + + client.request_access + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb new file mode 100644 index 00000000000..81f1b2390e6 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/logger_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Logger do + let(:request_time) { Time.parse('2018-03-26 20:22:15') } + + def fake_access(has_access, user, load_type = :request) + access = double('access') + allow(access).to receive_messages(user: user, + has_access?: has_access, + loaded_at: request_time, + label: 'dummy_label', + load_type: load_type) + + access + end + + describe '.log_access' do + it 'logs a nice message for an access request' do + expected_message = "GRANTED admin@example.com access to 'dummy_label' (the/project/path)" + fake_access = fake_access(true, build(:user, email: 'admin@example.com')) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, 'the/project/path') + end + + it 'does not trip without a project path' do + expected_message = "DENIED admin@example.com access to 'dummy_label'" + fake_access = fake_access(false, build(:user, email: 'admin@example.com')) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, nil) + end + + it 'adds the load time for cached accesses' do + expected_message = "DENIED admin@example.com access to 'dummy_label' - cache #{request_time}" + fake_access = fake_access(false, build(:user, email: 'admin@example.com'), :cache) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, nil) + end + end +end diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb new file mode 100644 index 00000000000..43211043eca --- /dev/null +++ b/spec/lib/gitlab/external_authorization/response_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Response do + let(:excon_response) { double } + subject(:response) { described_class.new(excon_response) } + + describe '#valid?' do + it 'is valid for 200, 401, and 403 responses' do + [200, 401, 403].each do |status| + allow(excon_response).to receive(:status).and_return(status) + + expect(response).to be_valid + end + end + + it "is invalid for other statuses" do + expect(excon_response).to receive(:status).and_return(500) + + expect(response).not_to be_valid + end + end + + describe '#reason' do + it 'returns a reason if it was included in the response body' do + expect(excon_response).to receive(:body).and_return({ reason: 'Not authorized' }.to_json) + + expect(response.reason).to eq('Not authorized') + end + + it 'returns nil when there was no body' do + expect(excon_response).to receive(:body).and_return('') + + expect(response.reason).to eq(nil) + end + end + + describe '#successful?' do + it 'is `true` if the status is 200' do + allow(excon_response).to receive(:status).and_return(200) + + expect(response).to be_successful + end + + it 'is `false` if the status is 401 or 403' do + [401, 403].each do |status| + allow(excon_response).to receive(:status).and_return(status) + + expect(response).not_to be_successful + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb new file mode 100644 index 00000000000..7394fbfe0ce --- /dev/null +++ b/spec/lib/gitlab/external_authorization_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization, :request_store do + include ExternalAuthorizationServiceHelpers + + let(:user) { build(:user) } + let(:label) { 'dummy_label' } + + describe '#access_allowed?' do + it 'is always true when the feature is disabled' do + # Not using `stub_application_setting` because the method is prepended in + # `EE::ApplicationSetting` which breaks when using `any_instance` + # https://gitlab.com/gitlab-org/gitlab-ce/issues/33587 + expect(::Gitlab::CurrentSettings.current_application_settings) + .to receive(:external_authorization_service_enabled) { false } + + expect(described_class).not_to receive(:access_for_user_to_label) + + expect(described_class.access_allowed?(user, label)).to be_truthy + end + end + + describe '#rejection_reason' do + it 'is always nil when the feature is disabled' do + expect(::Gitlab::CurrentSettings.current_application_settings) + .to receive(:external_authorization_service_enabled) { false } + + expect(described_class).not_to receive(:access_for_user_to_label) + + expect(described_class.rejection_reason(user, label)).to be_nil + end + end + + describe '#access_for_user_to_label' do + it 'only loads the access once per request' do + enable_external_authorization_service_check + + expect(::Gitlab::ExternalAuthorization::Access) + .to receive(:new).with(user, label).once.and_call_original + + 2.times { described_class.access_for_user_to_label(user, label, nil) } + end + + it 'logs the access request once per request' do + expect(::Gitlab::ExternalAuthorization::Logger) + .to receive(:log_access) + .with(an_instance_of(::Gitlab::ExternalAuthorization::Access), + 'the/project/path') + .once + + 2.times { described_class.access_for_user_to_label(user, label, 'the/project/path') } + end + end +end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 06995604a24..ebb62124cb1 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -496,6 +496,7 @@ Project: - merge_requests_ff_only_enabled - merge_requests_rebase_enabled - jobs_cache_index +- external_authorization_classification_label - pages_https_only Author: - name diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index c81572d739e..c7d7dbac736 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ApplicationSetting do - let(:setting) { described_class.create_from_defaults } + subject(:setting) { described_class.create_from_defaults } it { include(CacheableAttributes) } it { include(ApplicationSettingImplementation) } @@ -284,6 +284,52 @@ describe ApplicationSetting do expect(subject).to be_valid end end + + describe 'when external authorization service is enabled' do + before do + setting.external_authorization_service_enabled = true + end + + it { is_expected.not_to allow_value('not a URL').for(:external_authorization_service_url) } + it { is_expected.to allow_value('https://example.com').for(:external_authorization_service_url) } + it { is_expected.to allow_value('').for(:external_authorization_service_url) } + it { is_expected.not_to allow_value(nil).for(:external_authorization_service_default_label) } + it { is_expected.not_to allow_value(11).for(:external_authorization_service_timeout) } + it { is_expected.not_to allow_value(0).for(:external_authorization_service_timeout) } + it { is_expected.not_to allow_value('not a certificate').for(:external_auth_client_cert) } + it { is_expected.to allow_value('').for(:external_auth_client_cert) } + it { is_expected.to allow_value('').for(:external_auth_client_key) } + + context 'when setting a valid client certificate for external authorization' do + let(:certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') } + + before do + setting.external_auth_client_cert = certificate_data + end + + it 'requires a valid client key when a certificate is set' do + expect(setting).not_to allow_value('fefefe').for(:external_auth_client_key) + end + + it 'requires a matching certificate' do + other_private_key = File.read('spec/fixtures/x509_certificate_pk.key') + + expect(setting).not_to allow_value(other_private_key).for(:external_auth_client_key) + end + + it 'the credentials are valid when the private key can be read and matches the certificate' do + tls_attributes = [:external_auth_client_key_pass, + :external_auth_client_key, + :external_auth_client_cert] + setting.external_auth_client_key = File.read('spec/fixtures/passphrase_x509_certificate_pk.key') + setting.external_auth_client_key_pass = '5iveL!fe' + + setting.validate + + expect(setting.errors).not_to include(*tls_attributes) + end + end + end end context 'restrict creating duplicates' do diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb index 94798f0590d..f63ad958ed3 100644 --- a/spec/models/concerns/protected_ref_access_spec.rb +++ b/spec/models/concerns/protected_ref_access_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe ProtectedRefAccess do + include ExternalAuthorizationServiceHelpers + subject(:protected_ref_access) do create(:protected_branch, :maintainers_can_push).push_access_levels.first end @@ -29,5 +31,15 @@ describe ProtectedRefAccess do expect(protected_ref_access.check_access(developer)).to be_falsy end + + context 'external authorization' do + it 'is false if external authorization denies access' do + maintainer = create(:user) + project.add_maintainer(maintainer) + external_service_deny_access(maintainer, project) + + expect(protected_ref_access.check_access(maintainer)).to be_falsey + end + end end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 892dd053e39..0cd69cb4817 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Issue do + include ExternalAuthorizationServiceHelpers + describe "Associations" do it { is_expected.to belong_to(:milestone) } it { is_expected.to have_many(:assignees) } @@ -779,4 +781,47 @@ describe Issue do it_behaves_like 'throttled touch' do subject { create(:issue, updated_at: 1.hour.ago) } end + + context 'when an external authentication service' do + before do + enable_external_authorization_service_check + end + + describe '#visible_to_user?' do + it 'is `false` when an external authorization service is enabled' do + issue = build(:issue, project: build(:project, :public)) + + expect(issue).not_to be_visible_to_user + end + + it 'checks the external service to determine if an issue is readable by a user' do + project = build(:project, :public, + external_authorization_classification_label: 'a-label') + issue = build(:issue, project: project) + user = build(:user) + + expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).with(user, 'a-label') { false } + expect(issue.visible_to_user?(user)).to be_falsy + end + + it 'does not check the external service if a user does not have access to the project' do + project = build(:project, :private, + external_authorization_classification_label: 'a-label') + issue = build(:issue, project: project) + user = build(:user) + + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + expect(issue.visible_to_user?(user)).to be_falsy + end + + it 'does not check the external webservice for admins' do + issue = build(:issue) + user = build(:admin) + + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + issue.visible_to_user?(user) + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5eb31430ccd..7222580e115 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Project do include ProjectForksHelper include GitHelpers + include ExternalAuthorizationServiceHelpers it_behaves_like 'having unique enum values' @@ -4417,6 +4418,25 @@ describe Project do end end + describe '#external_authorization_classification_label' do + it 'falls back to the default when none is configured' do + enable_external_authorization_service_check + + expect(build(:project).external_authorization_classification_label) + .to eq('default_label') + end + + it 'returns the classification label if it was configured on the project' do + enable_external_authorization_service_check + + project = build(:project, + external_authorization_classification_label: 'hello') + + expect(project.external_authorization_classification_label) + .to eq('hello') + end + end + describe "#pages_https_only?" do subject { build(:project) } diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb index c03d95b34db..09be831dcd5 100644 --- a/spec/policies/base_policy_spec.rb +++ b/spec/policies/base_policy_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe BasePolicy do + include ExternalAuthorizationServiceHelpers + describe '.class_for' do it 'detects policy class based on the subject ancestors' do expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy) @@ -16,4 +18,25 @@ describe BasePolicy do expect(DeclarativePolicy.class_for(:global)).to eq(GlobalPolicy) end end + + describe 'read cross project' do + let(:current_user) { create(:user) } + let(:user) { create(:user) } + + subject { described_class.new(current_user, [user]) } + + it { is_expected.to be_allowed(:read_cross_project) } + + context 'when an external authorization service is enabled' do + before do + enable_external_authorization_service_check + end + + it { is_expected.not_to be_allowed(:read_cross_project) } + + it 'allows admins' do + expect(described_class.new(build(:admin), nil)).to be_allowed(:read_cross_project) + end + end + end end diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index 008d118b557..b149dbcf871 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe IssuePolicy do + include ExternalAuthorizationServiceHelpers + let(:guest) { create(:user) } let(:author) { create(:user) } let(:assignee) { create(:user) } @@ -204,4 +206,21 @@ describe IssuePolicy do end end end + + context 'with external authorization enabled' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:policies) { described_class.new(user, issue) } + + before do + enable_external_authorization_service_check + end + + it 'can read the issue iid without accessing the external service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(policies).to be_allowed(:read_issue_iid) + end + end end diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb index 1efa70addc2..81279225d61 100644 --- a/spec/policies/merge_request_policy_spec.rb +++ b/spec/policies/merge_request_policy_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe MergeRequestPolicy do + include ExternalAuthorizationServiceHelpers + let(:guest) { create(:user) } let(:author) { create(:user) } let(:developer) { create(:user) } @@ -47,4 +49,21 @@ describe MergeRequestPolicy do expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request) end end + + context 'with external authorization enabled' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:policies) { described_class.new(user, merge_request) } + + before do + enable_external_authorization_service_check + end + + it 'can read the issue iid without accessing the external service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(policies).to be_allowed(:read_merge_request_iid) + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 125ed818bc6..42f8bf3137b 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe ProjectPolicy do + include ExternalAuthorizationServiceHelpers include_context 'ProjectPolicy context' set(:guest) { create(:user) } set(:reporter) { create(:user) } @@ -292,4 +293,56 @@ describe ProjectPolicy do projects: [clusterable]) end end + + context 'reading a project' do + it 'allows access when a user has read access to the repo' do + expect(described_class.new(owner, project)).to be_allowed(:read_project) + expect(described_class.new(developer, project)).to be_allowed(:read_project) + expect(described_class.new(admin, project)).to be_allowed(:read_project) + end + + it 'never checks the external service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(described_class.new(owner, project)).to be_allowed(:read_project) + end + + context 'with an external authorization service' do + before do + enable_external_authorization_service_check + end + + it 'allows access when the external service allows it' do + external_service_allow_access(owner, project) + external_service_allow_access(developer, project) + + expect(described_class.new(owner, project)).to be_allowed(:read_project) + expect(described_class.new(developer, project)).to be_allowed(:read_project) + end + + it 'does not check the external service for admins and allows access' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(described_class.new(admin, project)).to be_allowed(:read_project) + end + + it 'prevents all but seeing a public project in a list when access is denied' do + [developer, owner, build(:user), nil].each do |user| + external_service_deny_access(user, project) + policy = described_class.new(user, project) + + expect(policy).not_to be_allowed(:read_project) + expect(policy).not_to be_allowed(:owner_access) + expect(policy).not_to be_allowed(:change_namespace) + end + end + + it 'passes the full path to external authorization for logging purposes' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(owner, 'default_label', project.full_path).and_call_original + + described_class.new(owner, project).allowed?(:read_project) + end + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 2bfb17d9c9a..352ea448c00 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -46,6 +46,8 @@ shared_examples 'languages and percentages JSON response' do end describe API::Projects do + include ExternalAuthorizationServiceHelpers + let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } @@ -1336,6 +1338,39 @@ describe API::Projects do end end end + + context 'with external authorization' do + let(:project) do + create(:project, + namespace: user.namespace, + external_authorization_classification_label: 'the-label') + end + + context 'when the user has access to the project' do + before do + external_service_allow_access(user, project) + end + + it 'includes the label in the response' do + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['external_authorization_classification_label']).to eq('the-label') + end + end + + context 'when the external service denies access' do + before do + external_service_deny_access(user, project) + end + + it 'returns a 404' do + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(404) + end + end + end end describe 'GET /projects/:id/users' do @@ -1890,6 +1925,20 @@ describe API::Projects do expect(response).to have_gitlab_http_status(403) end end + + context 'when updating external classification' do + before do + enable_external_authorization_service_check + end + + it 'updates the classification label' do + put(api("/projects/#{project.id}", user), params: { external_authorization_classification_label: 'new label' }) + + expect(response).to have_gitlab_http_status(200) + + expect(project.reload.external_authorization_classification_label).to eq('new label') + end + end end describe 'POST /projects/:id/archive' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index f869325e892..527ab1cfb66 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -116,6 +116,39 @@ describe API::Settings, 'Settings' do expect(json_response['performance_bar_allowed_group_id']).to be_nil end + context 'external policy classification settings' do + let(:settings) do + { + external_authorization_service_enabled: true, + external_authorization_service_url: 'https://custom.service/', + external_authorization_service_default_label: 'default', + external_authorization_service_timeout: 9.99, + external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'), + external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'), + external_auth_client_key_pass: "5iveL!fe" + } + end + let(:attribute_names) { settings.keys.map(&:to_s) } + + it 'includes the attributes in the API' do + get api("/application/settings", admin) + + expect(response).to have_gitlab_http_status(200) + attribute_names.each do |attribute| + expect(json_response.keys).to include(attribute) + end + end + + it 'allows updating the settings' do + put api("/application/settings", admin), params: settings + + expect(response).to have_gitlab_http_status(200) + settings.each do |attribute, value| + expect(ApplicationSetting.current.public_send(attribute)).to eq(value) + end + end + end + context "missing plantuml_url value when plantuml_enabled is true" do it "returns a blank parameter error message" do put api("/application/settings", admin), params: { plantuml_enabled: true } diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb index d02b4c554b1..b58d95ccb43 100644 --- a/spec/serializers/group_child_entity_spec.rb +++ b/spec/serializers/group_child_entity_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe GroupChildEntity do + include ExternalAuthorizationServiceHelpers include Gitlab::Routing.url_helpers let(:user) { create(:user) } @@ -109,4 +110,22 @@ describe GroupChildEntity do it_behaves_like 'group child json' end + + describe 'for a project with external authorization enabled' do + let(:object) do + create(:project, :with_avatar, + description: 'Awesomeness') + end + + before do + enable_external_authorization_service_check + object.add_maintainer(user) + end + + it 'does not hit the external authorization service' do + expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) + + expect(json[:can_edit]).to eq(false) + end + end end diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index a4a733eff77..258e5635113 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe ApplicationSettings::UpdateService do + include ExternalAuthorizationServiceHelpers + let(:application_settings) { create(:application_setting) } let(:admin) { create(:user, :admin) } let(:params) { {} } @@ -143,4 +145,37 @@ describe ApplicationSettings::UpdateService do end end end + + context 'when external authorization is enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not save the settings with an error if the service denies access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(admin, 'new-label') { false } + + described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute + + expect(application_settings.errors[:external_authorization_service_default_label]).to be_present + end + + it 'saves the setting when the user has access to the label' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(admin, 'new-label') { true } + + described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute + + # Read the attribute directly to avoid the stub from + # `enable_external_authorization_service_check` + expect(application_settings[:external_authorization_service_default_label]).to eq('new-label') + end + + it 'does not validate the label if it was not passed' do + expect(::Gitlab::ExternalAuthorization) + .not_to receive(:access_allowed?) + + described_class.new(application_settings, admin, { home_page_url: 'http://foo.bar' }).execute + end + end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 14c73852e65..ac4aabf3fbd 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe NotificationService, :mailer do include EmailSpec::Matchers + include ExternalAuthorizationServiceHelpers include NotificationHelpers let(:notification) { described_class.new } @@ -2218,6 +2219,46 @@ describe NotificationService, :mailer do end end + context 'with external authorization service' do + let(:issue) { create(:issue) } + let(:project) { issue.project } + let(:note) { create(:note, noteable: issue, project: project) } + let(:member) { create(:user) } + + subject { NotificationService.new } + + before do + project.add_maintainer(member) + member.global_notification_setting.update!(level: :watch) + end + + it 'sends email when the service is not enabled' do + expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original + + subject.new_issue(issue, member) + end + + context 'when the service is enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not send an email' do + expect(Notify).not_to receive(:new_issue_email) + + subject.new_issue(issue, member) + end + + it 'still delivers email to admins' do + member.update!(admin: true) + + expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original + + subject.new_issue(issue, member) + end + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e8418b09dc2..e1ec932918e 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Projects::CreateService, '#execute' do + include ExternalAuthorizationServiceHelpers include GitHelpers let(:gitlab_shell) { Gitlab::Shell.new } @@ -344,6 +345,42 @@ describe Projects::CreateService, '#execute' do expect(rugged.config['gitlab.fullpath']).to eq project.full_path end + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not save the project with an error if the service denies access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label', any_args) { false } + + project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' })) + + expect(project.errors[:external_authorization_classification_label]).to be_present + expect(project).not_to be_persisted + end + + it 'saves the project when the user has access to the label' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label', any_args) { true } + + project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' })) + + expect(project).to be_persisted + expect(project.external_authorization_classification_label).to eq('new-label') + end + + it 'does not save the project when the user has no access to the default label and no label is provided' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'default_label', any_args) { false } + + project = create_project(user, opts) + + expect(project.errors[:external_authorization_classification_label]).to be_present + expect(project).not_to be_persisted + end + end + def create_project(user, opts) Projects::CreateService.new(user, opts).execute end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 90eaea9c872..95eb17b5e3a 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Projects::UpdateService do + include ExternalAuthorizationServiceHelpers include ProjectForksHelper let(:user) { create(:user) } @@ -361,6 +362,46 @@ describe Projects::UpdateService do call_service end end + + context 'with external authorization enabled' do + before do + enable_external_authorization_service_check + end + + it 'does not save the project with an error if the service denies access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label') { false } + + result = update_project(project, user, { external_authorization_classification_label: 'new-label' }) + + expect(result[:message]).to be_present + expect(result[:status]).to eq(:error) + end + + it 'saves the new label if the service allows access' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'new-label') { true } + + result = update_project(project, user, { external_authorization_classification_label: 'new-label' }) + + expect(result[:status]).to eq(:success) + expect(project.reload.external_authorization_classification_label).to eq('new-label') + end + + it 'checks the default label when the classification label was cleared' do + expect(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?).with(user, 'default_label') { true } + + update_project(project, user, { external_authorization_classification_label: '' }) + end + + it 'does not check the label when it does not change' do + expect(::Gitlab::ExternalAuthorization) + .not_to receive(:access_allowed?) + + update_project(project, user, { name: 'New name' }) + end + end end describe '#run_auto_devops_pipeline?' do diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb new file mode 100644 index 00000000000..79dd9a3d58e --- /dev/null +++ b/spec/support/external_authorization_service_helpers.rb @@ -0,0 +1,33 @@ +module ExternalAuthorizationServiceHelpers + def enable_external_authorization_service_check + stub_application_setting(external_authorization_service_enabled: true) + + stub_application_setting(external_authorization_service_url: 'https://authorize.me') + stub_application_setting(external_authorization_service_default_label: 'default_label') + stub_request(:post, "https://authorize.me").to_return(status: 200) + end + + def external_service_set_access(allowed, user, project) + enable_external_authorization_service_check + classification_label = ::Gitlab::CurrentSettings.current_application_settings + .external_authorization_service_default_label + + # Reload the project so cached licensed features are reloaded + if project + classification_label = Project.find(project.id).external_authorization_classification_label + end + + allow(::Gitlab::ExternalAuthorization) + .to receive(:access_allowed?) + .with(user, classification_label, any_args) + .and_return(allowed) + end + + def external_service_allow_access(user, project = nil) + external_service_set_access(true, user, project) + end + + def external_service_deny_access(user, project = nil) + external_service_set_access(false, user, project) + end +end diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb new file mode 100644 index 00000000000..8dd78fd0a25 --- /dev/null +++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +shared_examples 'disabled when using an external authorization service' do + include ExternalAuthorizationServiceHelpers + + it 'works when the feature is not enabled' do + subject + + expect(response).to be_success + end + + it 'renders a 404 with a message when the feature is enabled' do + enable_external_authorization_service_check + + subject + + expect(response).to have_gitlab_http_status(403) + end +end + +shared_examples 'unauthorized when external service denies access' do + include ExternalAuthorizationServiceHelpers + + it 'allows access when the authorization service allows it' do + external_service_allow_access(user, project) + + subject + + # Account for redirects after updates + expect(response.status).to be_between(200, 302) + end + + it 'allows access when the authorization service denies it' do + external_service_deny_access(user, project) + + subject + + expect(response).to have_gitlab_http_status(403) + end +end diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb new file mode 100644 index 00000000000..d7e17cc0b70 --- /dev/null +++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +shared_examples 'a finder with external authorization service' do + include ExternalAuthorizationServiceHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + project.add_maintainer(user) + end + + it 'finds the subject' do + expect(described_class.new(user).execute).to include(subject) + end + + context 'with an external authorization service' do + before do + enable_external_authorization_service_check + end + + it 'does not include the subject when no project was given' do + expect(described_class.new(user).execute).not_to include(subject) + end + + it 'includes the subject when a project id was given' do + expect(described_class.new(user, project_params).execute).to include(subject) + end + end +end diff --git a/spec/validators/x509_certificate_credentials_validator_spec.rb b/spec/validators/x509_certificate_credentials_validator_spec.rb new file mode 100644 index 00000000000..24ef68c1fab --- /dev/null +++ b/spec/validators/x509_certificate_credentials_validator_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe X509CertificateCredentialsValidator do + let(:certificate_data) { File.read('spec/fixtures/x509_certificate.crt') } + let(:pkey_data) { File.read('spec/fixtures/x509_certificate_pk.key') } + + let(:validatable) do + Class.new do + include ActiveModel::Validations + + attr_accessor :certificate, :private_key, :passphrase + + def initialize(certificate, private_key, passphrase = nil) + @certificate, @private_key, @passphrase = certificate, private_key, passphrase + end + end + end + + subject(:validator) do + described_class.new(certificate: :certificate, pkey: :private_key) + end + + it 'is not valid when the certificate is not valid' do + record = validatable.new('not a certificate', nil) + + validator.validate(record) + + expect(record.errors[:certificate]).to include('is not a valid X509 certificate.') + end + + it 'is not valid without a certificate' do + record = validatable.new(nil, nil) + + validator.validate(record) + + expect(record.errors[:certificate]).not_to be_empty + end + + context 'when a valid certificate is passed' do + let(:record) { validatable.new(certificate_data, nil) } + + it 'does not track an error for the certificate' do + validator.validate(record) + + expect(record.errors[:certificate]).to be_empty + end + + it 'adds an error when not passing a correct private key' do + validator.validate(record) + + expect(record.errors[:private_key]).to include('could not read private key, is the passphrase correct?') + end + + it 'has no error when the private key is correct' do + record.private_key = pkey_data + + validator.validate(record) + + expect(record.errors).to be_empty + end + end + + context 'when using a passphrase' do + let(:passphrase_certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') } + let(:passphrase_pkey_data) { File.read('spec/fixtures/passphrase_x509_certificate_pk.key') } + + let(:record) { validatable.new(passphrase_certificate_data, passphrase_pkey_data, '5iveL!fe') } + + subject(:validator) do + described_class.new(certificate: :certificate, pkey: :private_key, pass: :passphrase) + end + + it 'is valid with the correct data' do + validator.validate(record) + + expect(record.errors).to be_empty + end + + it 'adds an error when the passphrase is wrong' do + record.passphrase = 'wrong' + + validator.validate(record) + + expect(record.errors[:private_key]).not_to be_empty + end + end +end -- cgit v1.2.1 From 15e2b5ad0b2bdd0ccc0e5ebd47b78c0cd064dbea Mon Sep 17 00:00:00 2001 From: Daniel Wyatt Date: Tue, 9 Apr 2019 11:51:01 -0400 Subject: Address style review comment --- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 3aba744458f..8675d8691c8 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -49,7 +49,6 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do context 'when GitHub project is public' do it 'sets project visibility to namespace visibility level' do repo.private = false - project = service.execute expect(project.visibility_level).to eq(namespace.visibility_level) @@ -60,7 +59,6 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do it 'sets project visibility to user namespace visibility level' do repo.private = false - project = service.execute expect(project.visibility_level).to eq(user.namespace.visibility_level) -- cgit v1.2.1 From d69d29011cf9fe06e50a2c7d65b1ea88ea2d41d5 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Tue, 9 Apr 2019 17:46:29 +0000 Subject: Mark unverified pages domains for removal Set pages_domain.remove_at when disabling it Add specs for marking pages domain for removal Notify user that domain is being removed Add documentation --- spec/factories/pages_domains.rb | 4 + spec/mailers/emails/pages_domains_spec.rb | 24 ++++++ spec/services/verify_pages_domain_service_spec.rb | 89 +++++++++++++++++------ 3 files changed, 96 insertions(+), 21 deletions(-) (limited to 'spec') diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index 20671da016e..b74f72f2bd3 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -41,6 +41,10 @@ nNp/xedE1YxutQ== enabled_until nil end + trait :scheduled_for_removal do + remove_at { 1.day.from_now } + end + trait :unverified do verified_at nil end diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb index c74fd66ad22..050af587061 100644 --- a/spec/mailers/emails/pages_domains_spec.rb +++ b/spec/mailers/emails/pages_domains_spec.rb @@ -26,6 +26,26 @@ describe Emails::PagesDomains do end end + shared_examples 'notification about upcoming domain removal' do + context 'when domain is not scheduled for removal' do + it 'asks user to remove it' do + is_expected.to have_body_text 'please remove it' + end + end + + context 'when domain is scheduled for removal' do + before do + domain.update!(remove_at: 1.week.from_now) + end + it 'notifies user that domain will be removed automatically' do + aggregate_failures do + is_expected.to have_body_text domain.remove_at.strftime('%F %T') + is_expected.to have_body_text "it will be removed from your GitLab project" + end + end + end + end + describe '#pages_domain_enabled_email' do let(:email_subject) { "#{project.path} | GitLab Pages domain '#{domain.domain}' has been enabled" } @@ -43,6 +63,8 @@ describe Emails::PagesDomains do it_behaves_like 'a pages domain email' + it_behaves_like 'notification about upcoming domain removal' + it { is_expected.to have_body_text 'has been disabled' } end @@ -63,6 +85,8 @@ describe Emails::PagesDomains do it_behaves_like 'a pages domain email' + it_behaves_like 'notification about upcoming domain removal' + it 'says verification has failed and when the domain is enabled until' do is_expected.to have_body_text 'Verification has failed' is_expected.to have_body_text domain.enabled_until.strftime('%F %T') diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb index ddf9d2b4917..b60b13bb3fc 100644 --- a/spec/services/verify_pages_domain_service_spec.rb +++ b/spec/services/verify_pages_domain_service_spec.rb @@ -27,6 +27,7 @@ describe VerifyPagesDomainService do expect(domain).to be_verified expect(domain).to be_enabled + expect(domain.remove_at).to be_nil end end @@ -48,18 +49,32 @@ describe VerifyPagesDomainService do end end + shared_examples 'unverifies and disables domain' do + it 'unverifies domain' do + expect(service.execute).to eq(error_status) + expect(domain).not_to be_verified + end + + it 'disables domain and shedules it for removal' do + Timecop.freeze do + service.execute + expect(domain).not_to be_enabled + expect(domain.remove_at).to be_within(1.second).of(1.week.from_now) + end + end + end + context 'when domain is disabled(or new)' do let(:domain) { create(:pages_domain, :disabled) } include_examples 'successful enablement and verification' - shared_examples 'unverifies and disables domain' do - it 'unverifies and disables domain' do - expect(service.execute).to eq(error_status) - - expect(domain).not_to be_verified - expect(domain).not_to be_enabled + context 'when txt record does not contain verification code' do + before do + stub_resolver(domain_name => 'something else') end + + include_examples 'unverifies and disables domain' end context 'when txt record does not contain verification code' do @@ -84,16 +99,25 @@ describe VerifyPagesDomainService do include_examples 'successful enablement and verification' - context 'when txt record does not contain verification code' do - before do - stub_resolver(domain_name => 'something else') - end - + shared_examples 'unverifing domain' do it 'unverifies but does not disable domain' do expect(service.execute).to eq(error_status) expect(domain).not_to be_verified expect(domain).to be_enabled end + + it 'does not schedule domain for removal' do + service.execute + expect(domain.remove_at).to be_nil + end + end + + context 'when txt record does not contain verification code' do + before do + stub_resolver(domain_name => 'something else') + end + + include_examples 'unverifing domain' end context 'when no txt records are present' do @@ -101,11 +125,7 @@ describe VerifyPagesDomainService do stub_resolver end - it 'unverifies but does not disable domain' do - expect(service.execute).to eq(error_status) - expect(domain).not_to be_verified - expect(domain).to be_enabled - end + include_examples 'unverifing domain' end end @@ -125,13 +145,40 @@ describe VerifyPagesDomainService do stub_resolver end - it 'disables domain' do - error_status[:message] += '. It is now disabled.' + let(:error_status) { { status: :error, message: "Couldn't verify #{domain.domain}. It is now disabled." } } - expect(service.execute).to eq(error_status) + include_examples 'unverifies and disables domain' + end + end - expect(domain).not_to be_verified - expect(domain).not_to be_enabled + context 'when domain is disabled and scheduled for removal' do + let(:domain) { create(:pages_domain, :disabled, :scheduled_for_removal) } + + context 'when the right code is present' do + before do + stub_resolver(domain.domain => domain.keyed_verification_code) + end + + it 'verifies and enables domain' do + expect(service.execute).to eq(status: :success) + + expect(domain).to be_verified + expect(domain).to be_enabled + end + + it 'prevent domain from being removed' do + expect { service.execute }.to change { domain.remove_at }.to(nil) + end + end + + context 'when the right code is not present' do + before do + stub_resolver + end + + it 'keeps domain scheduled for removal but does not change removal time' do + expect { service.execute }.not_to change { domain.remove_at } + expect(domain.remove_at).to be_present end end end -- cgit v1.2.1 From 8fc2347fa96f2594f04a9482fbdca34eab349f15 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Tue, 9 Apr 2019 11:04:26 -0700 Subject: Quarantine flaky test --- spec/support/features/discussion_comments_shared_example.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 5b79c40f27b..542f533d590 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -7,7 +7,7 @@ shared_examples 'discussion comments' do |resource_name| let(:close_selector) { "#{form_selector} .btn-comment-and-close" } let(:comments_selector) { '.timeline > .note.timeline-entry' } - it 'clicking "Comment" will post a comment' do + it 'clicking "Comment" will post a comment', :quarantine do expect(page).to have_selector toggle_selector find("#{form_selector} .note-textarea").send_keys('a') -- cgit v1.2.1 From ff06452e05b1191ce8649ae6a9e646341ab073ba Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 8 Apr 2019 15:21:21 -0300 Subject: Prevent leaking information when issue is moved Prevent leaking namespace and project names on moved issue links --- spec/views/projects/issues/show.html.haml_spec.rb | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb index 1d9c6d36ad7..1ca9eaf8fdb 100644 --- a/spec/views/projects/issues/show.html.haml_spec.rb +++ b/spec/views/projects/issues/show.html.haml_spec.rb @@ -19,6 +19,7 @@ describe 'projects/issues/show' do context 'when the issue is closed' do before do allow(issue).to receive(:closed?).and_return(true) + allow(view).to receive(:current_user).and_return(user) end context 'when the issue was moved' do @@ -28,16 +29,30 @@ describe 'projects/issues/show' do issue.moved_to = new_issue end - it 'shows "Closed (moved)" if an issue has been moved' do - render + context 'when user can see the moved issue' do + before do + project.add_developer(user) + end - expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)') + it 'shows "Closed (moved)" if an issue has been moved' do + render + + expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)') + end + + it 'links "moved" to the new issue the original issue was moved to' do + render + + expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved') + end end - it 'links "moved" to the new issue the original issue was moved to' do - render + context 'when user cannot see moved issue' do + it 'does not show moved issue link' do + render - expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved') + expect(rendered).not_to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved') + end end end -- cgit v1.2.1 From f9b4d5efbde0bf3b6b1822177d47199ad2f02fbd Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 9 Apr 2019 20:30:52 +0100 Subject: Fix MR assignees for push options With multiple MR assignees, `merge_request.assignee_id` is always nil, and `merge_request.assignee_ids` isn't in `merge_request.attributes`. So the existing code doesn't set assignees in the created MR. This fix gets all the tests passing, but we should also check that no other associations in the MergeRequest need similar fixups. --- spec/services/merge_requests/push_options_handler_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index 686b4b49f24..f7a39bb42d5 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -38,7 +38,7 @@ describe MergeRequests::PushOptionsHandlerService do it 'assigns the MR to the user' do service.execute - expect(last_mr.assignee).to eq(user) + expect(last_mr.assignees).to contain_exactly(user) end context 'when project has been forked' do -- cgit v1.2.1 From bcfd04a2210c88997332c069493fb008e5d39d90 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 10 Apr 2019 03:36:09 +0000 Subject: Revert "Merge branch 'sh-optimize-projects-api' into 'master'" This reverts merge request !26481 --- spec/models/project_spec.rb | 49 +-------------------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) (limited to 'spec') diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 7222580e115..7f8d2ff91fd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2722,7 +2722,7 @@ describe Project do end describe '#any_lfs_file_locks?', :request_store do - let!(:project) { create(:project) } + set(:project) { create(:project) } it 'returns false when there are no LFS file locks' do expect(project.any_lfs_file_locks?).to be_falsey @@ -3160,53 +3160,6 @@ describe Project do expect(projects).to eq([public_project]) end end - - context 'with requested visibility levels' do - set(:internal_project) { create(:project, :internal, :repository) } - set(:private_project_2) { create(:project, :private) } - - context 'with admin user' do - set(:admin) { create(:admin) } - - it 'returns all projects' do - projects = described_class.all.public_or_visible_to_user(admin, []) - - expect(projects).to match_array([public_project, private_project, private_project_2, internal_project]) - end - - it 'returns all public and private projects' do - projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE]) - - expect(projects).to match_array([public_project, private_project, private_project_2]) - end - - it 'returns all private projects' do - projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PRIVATE]) - - expect(projects).to match_array([private_project, private_project_2]) - end - end - - context 'with regular user' do - it 'returns authorized projects' do - projects = described_class.all.public_or_visible_to_user(user, []) - - expect(projects).to match_array([public_project, private_project, internal_project]) - end - - it "returns user's public and private projects" do - projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE]) - - expect(projects).to match_array([public_project, private_project]) - end - - it 'returns one private project' do - projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PRIVATE]) - - expect(projects).to eq([private_project]) - end - end - end end describe '.with_feature_available_for_user' do -- cgit v1.2.1 From c239bfcb1750794ec1bf8172dfa380dea64fe4c1 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Wed, 10 Apr 2019 06:38:27 +0000 Subject: Add more info logging to cluster apps Log events so that it's easy to see when different requests are starting. --- spec/lib/gitlab/kubernetes/namespace_spec.rb | 27 ++++++++++++++++++++++ .../services/base_helm_service_shared_examples.rb | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb index e1c35c355f4..e91a755aa03 100644 --- a/spec/lib/gitlab/kubernetes/namespace_spec.rb +++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb @@ -62,5 +62,32 @@ describe Gitlab::Kubernetes::Namespace do subject.ensure_exists! end + + context 'when client errors' do + let(:exception) { Kubeclient::HttpError.new(500, 'system failure', nil) } + + before do + allow(client).to receive(:get_namespace).with(name).once.and_raise(exception) + end + + it 'raises the exception' do + expect { subject.ensure_exists! }.to raise_error(exception) + end + + it 'logs the error' do + expect(subject.send(:logger)).to receive(:error).with( + hash_including( + exception: 'Kubeclient::HttpError', + status_code: 500, + namespace: 'a_namespace', + class_name: 'Gitlab::Kubernetes::Namespace', + event: :failed_to_create_namespace, + message: 'system failure' + ) + ) + + expect { subject.ensure_exists! }.to raise_error(exception) + end + end end end diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb index 78a8e49fd76..fa76b95f768 100644 --- a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb +++ b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb @@ -20,7 +20,7 @@ shared_examples 'logs kubernetes errors' do end it 'logs into kubernetes.log and Sentry' do - expect(service.send(:logger)).to receive(:error).with(logger_hash) + expect(service.send(:logger)).to receive(:error).with(hash_including(logger_hash)) expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with( error, -- cgit v1.2.1 From 6d7876c1287a105e447aeea727e43431e10f9f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 10 Apr 2019 09:35:24 +0200 Subject: Quarantine a flaky spec in spec/features/issues/user_creates_branch_and_merge_request_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/issues/user_creates_branch_and_merge_request_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index 0a006011c89..cf665cff262 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -139,7 +139,7 @@ describe 'User creates branch and merge request on issue page', :js do visit project_issue_path(project, issue) end - it 'disables the create branch button' do + it 'disables the create branch button', :quarantine do expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)') expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false) expect(page).to have_content /Related merge requests/ -- cgit v1.2.1 From af46d33fba477785dfbe5af2d38a2e4f0ed0b696 Mon Sep 17 00:00:00 2001 From: Jason van den Hurk Date: Thu, 28 Mar 2019 13:53:35 +0100 Subject: Added the CI_REF_PROTECTED variable to the Gitlab runner This variable shows if the branch the runner is executing is protected or not. References: #50909 --- spec/models/ci/bridge_spec.rb | 2 +- spec/models/ci/build_spec.rb | 3 ++- spec/models/ci/pipeline_spec.rb | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index aacfbe3f180..fce53a96857 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -33,7 +33,7 @@ describe Ci::Bridge do CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE - CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION + CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_REF_PROTECTED ] expect(bridge.scoped_variables_hash.keys).to include(*variables) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 1352a2de2d7..4c717357bd0 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2227,7 +2227,8 @@ describe Ci::Build do { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false }, { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, - { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false } + { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false }, + { key: 'CI_REF_PROTECTED', value: pipeline.protected_ref?.to_s, public: true, masked: false } ] end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index f3e78630c1b..9b86fbbefe8 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -670,7 +670,8 @@ describe Ci::Pipeline, :mailer do CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE CI_COMMIT_TITLE - CI_COMMIT_DESCRIPTION] + CI_COMMIT_DESCRIPTION + CI_REF_PROTECTED] end context 'when source is merge request' do -- cgit v1.2.1 From 919e22e05bdcd0e1edb642e0e04be02321387cea Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 10 Apr 2019 17:19:42 +0800 Subject: Double retries for rspec-retries It was 2*4 => 8 times before, so 4 times is still half retries --- spec/spec_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 953cf1519bb..60db3e1bc46 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -100,8 +100,8 @@ RSpec.configure do |config| config.include PolicyHelpers, type: :policy if ENV['CI'] - # This includes the first try, i.e. tests will be run 2 times before failing. - config.default_retry_count = 2 + # This includes the first try, i.e. tests will be run 4 times before failing. + config.default_retry_count = 4 config.reporter.register_listener( RspecFlaky::Listener.new, :example_passed, -- cgit v1.2.1 From 2b9492a292d389d8390a9eca6a80e730ab7b6f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 10 Apr 2019 11:04:51 +0200 Subject: Process at most 4 pipelines during push This adds a limitation that we will try to create pipeline for at most 4 first changes (branches and tags). This does not affect processing of Pipelines for Merge Requests, as each updated MR will have associated pipeline created. --- spec/workers/post_receive_spec.rb | 42 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index a3fe8fa4501..39f1beb4efa 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -99,11 +99,21 @@ describe PostReceive do end context "gitlab-ci.yml" do - let(:changes) { "123456 789012 refs/heads/feature\n654321 210987 refs/tags/tag" } + let(:changes) do + <<-EOF.strip_heredoc + 123456 789012 refs/heads/feature + 654321 210987 refs/tags/tag + 123456 789012 refs/heads/feature2 + 123458 789013 refs/heads/feature3 + 123459 789015 refs/heads/feature4 + EOF + end + + let(:changes_count) { changes.lines.count } subject { described_class.new.perform(gl_repository, key_id, base64_changes) } - context "creates a Ci::Pipeline for every change" do + context "with valid .gitlab-ci.yml" do before do stub_ci_pipeline_to_return_yaml_file @@ -116,7 +126,33 @@ describe PostReceive do .and_return(true) end - it { expect { subject }.to change { Ci::Pipeline.count }.by(2) } + context 'when git_push_create_all_pipelines is disabled' do + before do + stub_feature_flags(git_push_create_all_pipelines: false) + end + + it "creates pipeline for branches and tags" do + subject + + expect(Ci::Pipeline.pluck(:ref)).to contain_exactly("feature", "tag", "feature2", "feature3") + end + + it "creates exactly #{described_class::PIPELINE_PROCESS_LIMIT} pipelines" do + expect(changes_count).to be > described_class::PIPELINE_PROCESS_LIMIT + + expect { subject }.to change { Ci::Pipeline.count }.by(described_class::PIPELINE_PROCESS_LIMIT) + end + end + + context 'when git_push_create_all_pipelines is enabled' do + before do + stub_feature_flags(git_push_create_all_pipelines: true) + end + + it "creates all pipelines" do + expect { subject }.to change { Ci::Pipeline.count }.by(changes_count) + end + end end context "does not create a Ci::Pipeline" do -- cgit v1.2.1 From e116c45619899d668fd3bae65007cb5398c5bc80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 10 Apr 2019 12:28:01 +0200 Subject: Add optional IGNORE_ACCESSORS in RetryBuildService --- spec/services/ci/retry_build_service_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 87185891470..17e2b17a499 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -35,7 +35,7 @@ describe Ci::RetryBuildService do 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 + sourced_pipelines artifacts_file_store artifacts_metadata_store metadata runner_session trace_chunks].freeze shared_examples 'build duplication' do @@ -95,7 +95,8 @@ describe Ci::RetryBuildService do end it 'has correct number of known attributes' do - known_accessors = CLONE_ACCESSORS + REJECT_ACCESSORS + IGNORE_ACCESSORS + processed_accessors = CLONE_ACCESSORS + REJECT_ACCESSORS + known_accessors = processed_accessors + IGNORE_ACCESSORS # :tag_list is a special case, this accessor does not exist # in reflected associations, comes from `act_as_taggable` and @@ -108,7 +109,8 @@ describe Ci::RetryBuildService do current_accessors.uniq! - expect(known_accessors).to contain_exactly(*current_accessors) + expect(current_accessors).to include(*processed_accessors) + expect(known_accessors).to include(*current_accessors) end end -- cgit v1.2.1 From c2c8189b10037bdc3e9e31fa73242d169e1ca46b Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 20:04:49 +0200 Subject: Enable ESLint rule jest/valid-describe --- spec/frontend/.eslintrc.yml | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml index 0e6ca11c1f8..ff18f0e4a2d 100644 --- a/spec/frontend/.eslintrc.yml +++ b/spec/frontend/.eslintrc.yml @@ -15,4 +15,5 @@ globals: rules: jest/no-identical-title: error jest/no-focused-tests: error + jest/valid-describe: error jest/no-jasmine-globals: error -- cgit v1.2.1 From 44d3590e36e8fb96f5cf4f97768ea4dfcb0450b9 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 20:06:14 +0200 Subject: Wrap file template mutation names in template strings --- .../ide/stores/modules/file_templates/mutations_spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js index 8e0e3ae99a1..8e8b7f06ca2 100644 --- a/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js +++ b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js @@ -9,7 +9,7 @@ describe('IDE file templates mutations', () => { state = createState(); }); - describe(types.REQUEST_TEMPLATE_TYPES, () => { + describe(`${types.REQUEST_TEMPLATE_TYPES}`, () => { it('sets isLoading', () => { mutations[types.REQUEST_TEMPLATE_TYPES](state); @@ -17,7 +17,7 @@ describe('IDE file templates mutations', () => { }); }); - describe(types.RECEIVE_TEMPLATE_TYPES_ERROR, () => { + describe(`${types.RECEIVE_TEMPLATE_TYPES_ERROR}`, () => { it('sets isLoading', () => { state.isLoading = true; @@ -27,7 +27,7 @@ describe('IDE file templates mutations', () => { }); }); - describe(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, () => { + describe(`${types.RECEIVE_TEMPLATE_TYPES_SUCCESS}`, () => { it('sets isLoading to false', () => { state.isLoading = true; @@ -43,7 +43,7 @@ describe('IDE file templates mutations', () => { }); }); - describe(types.SET_SELECTED_TEMPLATE_TYPE, () => { + describe(`${types.SET_SELECTED_TEMPLATE_TYPE}`, () => { it('sets selectedTemplateType', () => { mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type'); @@ -59,7 +59,7 @@ describe('IDE file templates mutations', () => { }); }); - describe(types.SET_UPDATE_SUCCESS, () => { + describe(`${types.SET_UPDATE_SUCCESS}`, () => { it('sets updateSuccess', () => { mutations[types.SET_UPDATE_SUCCESS](state, true); -- cgit v1.2.1 From 84792c29ef6e65deae83c7d9dc97465ee9212981 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 20:07:20 +0200 Subject: Wrap import project mutation names in template strings --- spec/frontend/import_projects/store/mutations_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/frontend/import_projects/store/mutations_spec.js b/spec/frontend/import_projects/store/mutations_spec.js index 8db8e9819ba..505545f7aa5 100644 --- a/spec/frontend/import_projects/store/mutations_spec.js +++ b/spec/frontend/import_projects/store/mutations_spec.js @@ -2,7 +2,7 @@ import * as types from '~/import_projects/store/mutation_types'; import mutations from '~/import_projects/store/mutations'; describe('import_projects store mutations', () => { - describe(types.RECEIVE_IMPORT_SUCCESS, () => { + describe(`${types.RECEIVE_IMPORT_SUCCESS}`, () => { it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => { const repoId = 1; const state = { @@ -20,7 +20,7 @@ describe('import_projects store mutations', () => { }); }); - describe(types.RECEIVE_JOBS_SUCCESS, () => { + describe(`${types.RECEIVE_JOBS_SUCCESS}`, () => { it('updates importStatus of existing importedProjects', () => { const repoId = 1; const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] }; -- cgit v1.2.1 From 41c5733f557c2db74556b868e53b2742239880be Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 20:09:37 +0200 Subject: Hard code test name in suggestion_diff_row_spec.js --- .../frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js index 866d6eb05c6..c8deac1c086 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js @@ -23,7 +23,7 @@ const newLine = { type: 'new', }; -describe(SuggestionDiffRow.name, () => { +describe('SuggestionDiffRow', () => { let wrapper; const factory = (options = {}) => { -- cgit v1.2.1 From 2704c4a588892e2d1928e0d772610cdaf0f8e760 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 20:16:46 +0200 Subject: Hard code test name in timeline_entry_item_spec.js --- spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js index c15635f2105..be6c58f0683 100644 --- a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js +++ b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js @@ -1,7 +1,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; -describe(TimelineEntryItem.name, () => { +describe(`TimelineEntryItem`, () => { let wrapper; const factory = (options = {}) => { -- cgit v1.2.1 From 7a255af451a1755a9cc3fc2902ba414d9b5a5f07 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Apr 2019 20:17:53 +0200 Subject: Wrap modal mutation names in template strings --- spec/frontend/vuex_shared/modules/modal/mutations_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/frontend/vuex_shared/modules/modal/mutations_spec.js b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js index d07f8ba1e65..eaaf196d1ec 100644 --- a/spec/frontend/vuex_shared/modules/modal/mutations_spec.js +++ b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js @@ -2,7 +2,7 @@ import mutations from '~/vuex_shared/modules/modal/mutations'; import * as types from '~/vuex_shared/modules/modal/mutation_types'; describe('Vuex ModalModule mutations', () => { - describe(types.SHOW, () => { + describe(`${types.SHOW}`, () => { it('sets isVisible to true', () => { const state = { isVisible: false, @@ -16,7 +16,7 @@ describe('Vuex ModalModule mutations', () => { }); }); - describe(types.HIDE, () => { + describe(`${types.HIDE}`, () => { it('sets isVisible to false', () => { const state = { isVisible: true, @@ -30,7 +30,7 @@ describe('Vuex ModalModule mutations', () => { }); }); - describe(types.OPEN, () => { + describe(`${types.OPEN}`, () => { it('sets data and sets isVisible to true', () => { const data = { id: 7 }; const state = { -- cgit v1.2.1 From 8d1649f77460b1203e6497b91342cc08b93ae160 Mon Sep 17 00:00:00 2001 From: Jason van den Hurk Date: Wed, 10 Apr 2019 12:56:56 +0200 Subject: Change CI_REF_PROTECTED variable to CI_COMMIT_REF_PROTECTED --- spec/models/ci/bridge_spec.rb | 2 +- spec/models/ci/build_spec.rb | 2 +- spec/models/ci/pipeline_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index fce53a96857..44b5af5e5aa 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -33,7 +33,7 @@ describe Ci::Bridge do CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE - CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_REF_PROTECTED + CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED ] expect(bridge.scoped_variables_hash.keys).to include(*variables) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 4c717357bd0..7af5237a503 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2228,7 +2228,7 @@ describe Ci::Build do { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false }, - { key: 'CI_REF_PROTECTED', value: pipeline.protected_ref?.to_s, public: true, masked: false } + { key: 'CI_COMMIT_REF_PROTECTED', value: pipeline.protected_ref?.to_s, public: true, masked: false } ] end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 9b86fbbefe8..f3310ff0f82 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -671,7 +671,7 @@ describe Ci::Pipeline, :mailer do CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION - CI_REF_PROTECTED] + CI_COMMIT_REF_PROTECTED] end context 'when source is merge request' do -- cgit v1.2.1 From a2d34fbfbe0ba1772359d4c62b93cb90a4d752b4 Mon Sep 17 00:00:00 2001 From: pataar Date: Wed, 10 Apr 2019 11:00:41 +0000 Subject: Add time tracking to issue board entities --- spec/fixtures/api/schemas/entities/issue_board.json | 3 +++ 1 file changed, 3 insertions(+) (limited to 'spec') diff --git a/spec/fixtures/api/schemas/entities/issue_board.json b/spec/fixtures/api/schemas/entities/issue_board.json index f7b270ffa8d..7cb65e1f2f5 100644 --- a/spec/fixtures/api/schemas/entities/issue_board.json +++ b/spec/fixtures/api/schemas/entities/issue_board.json @@ -9,6 +9,9 @@ "project_id": { "type": "integer" }, "relative_position": { "type": ["integer", "null"] }, "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["string", "null"] }, + "human_total_time_spent": { "type": ["string", "null"] }, "weight": { "type": ["integer", "null"] }, "project": { "type": "object", -- cgit v1.2.1 From 60f0e3c9933c9080e173be04471fe11eb3cab74f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 10 Apr 2019 13:18:31 +0200 Subject: Use different query count expectations in EE --- spec/serializers/pipeline_serializer_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 0fdd675aa01..d9023036534 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -157,7 +157,8 @@ describe PipelineSerializer do it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expect(recorded.count).to be_within(2).of(31) + expected_queries = Gitlab.ee? ? 38 : 31 + expect(recorded.count).to be_within(2).of(expected_queries) expect(recorded.cached_count).to eq(0) end end @@ -176,7 +177,8 @@ describe PipelineSerializer do # pipeline. With the same ref this check is cached but if refs are # different then there is an extra query per ref # https://gitlab.com/gitlab-org/gitlab-ce/issues/46368 - expect(recorded.count).to be_within(2).of(38) + expected_queries = Gitlab.ee? ? 44 : 38 + expect(recorded.count).to be_within(2).of(expected_queries) expect(recorded.cached_count).to eq(0) end end -- cgit v1.2.1 From cd9ae6bb820ae8ec98cce38d958298e081860dab Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 9 Apr 2019 14:06:36 +0100 Subject: Revert "Remove HipChat integration from GitLab" This reverts commit a5378665a1dc0b9c8dc3a4fa279a0eb78aac5aac. --- spec/factories/services.rb | 6 + .../projects/services/disable_triggers_spec.rb | 5 +- .../services/user_activates_hipchat_spec.rb | 38 ++ .../projects/services/user_views_services_spec.rb | 3 +- spec/lib/gitlab/import_export/all_models.yml | 1 + spec/lib/gitlab/import_export/project.json | 22 ++ .../project_services/hipchat_service_spec.rb | 408 +++++++++++++++++++++ spec/models/project_spec.rb | 1 + 8 files changed, 479 insertions(+), 5 deletions(-) create mode 100644 spec/features/projects/services/user_activates_hipchat_spec.rb create mode 100644 spec/models/project_services/hipchat_service_spec.rb (limited to 'spec') diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 70c34f8640b..0d8c26a2ee9 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -62,4 +62,10 @@ FactoryBot.define do project_key: 'jira-key' ) end + + factory :hipchat_service do + project + type 'HipchatService' + token 'test_token' + end end diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb index 65b597da269..1a13fe03a67 100644 --- a/spec/features/projects/services/disable_triggers_spec.rb +++ b/spec/features/projects/services/disable_triggers_spec.rb @@ -14,11 +14,10 @@ describe 'Disable individual triggers' do end context 'service has multiple supported events' do - let(:service_name) { 'JIRA' } + let(:service_name) { 'HipChat' } it 'shows trigger checkboxes' do - event_count = JiraService.supported_events.count - expect(event_count).to be > 1 + event_count = HipchatService.supported_events.count expect(page).to have_content "Trigger" expect(page).to have_css(checkbox_selector, count: event_count) diff --git a/spec/features/projects/services/user_activates_hipchat_spec.rb b/spec/features/projects/services/user_activates_hipchat_spec.rb new file mode 100644 index 00000000000..2f5313c91f9 --- /dev/null +++ b/spec/features/projects/services/user_activates_hipchat_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'User activates HipChat' do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.add_maintainer(user) + sign_in(user) + + visit(project_settings_integrations_path(project)) + + click_link('HipChat') + end + + context 'with standart settings' do + it 'activates service' do + check('Active') + fill_in('Room', with: 'gitlab') + fill_in('Token', with: 'verySecret') + click_button('Save') + + expect(page).to have_content('HipChat activated.') + end + end + + context 'with custom settings' do + it 'activates service' do + check('Active') + fill_in('Room', with: 'gitlab_custom') + fill_in('Token', with: 'secretCustom') + fill_in('Server', with: 'https://chat.example.com') + click_button('Save') + + expect(page).to have_content('HipChat activated.') + end + end +end diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb index b0a838a7d2b..e9c8cf0fe34 100644 --- a/spec/features/projects/services/user_views_services_spec.rb +++ b/spec/features/projects/services/user_views_services_spec.rb @@ -14,6 +14,7 @@ describe 'User views services' do it 'shows the list of available services' do expect(page).to have_content('Project services') expect(page).to have_content('Campfire') + expect(page).to have_content('HipChat') expect(page).to have_content('Assembla') expect(page).to have_content('Pushover') expect(page).to have_content('Atlassian Bamboo') @@ -21,7 +22,5 @@ describe 'User views services' do expect(page).to have_content('Asana') expect(page).to have_content('Irker (IRC gateway)') expect(page).to have_content('Packagist') - expect(page).to have_content('Mattermost') - expect(page).to have_content('Slack') end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index ed557ffd4e3..54369ff75f4 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -223,6 +223,7 @@ project: - packagist_service - pivotaltracker_service - prometheus_service +- hipchat_service - flowdock_service - assembla_service - asana_service diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 773651dd226..4a7accc4c52 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6794,6 +6794,28 @@ "default": false, "wiki_page_events": true }, + { + "id": 93, + "title": "HipChat", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.219Z", + "updated_at": "2016-06-14T15:01:51.219Z", + "active": false, + "properties": { + "notify_only_broken_pipelines": true + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "pipeline_events": true, + "type": "HipchatService", + "category": "common", + "default": false, + "wiki_page_events": true + }, { "id": 91, "title": "Flowdock", diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb new file mode 100644 index 00000000000..b0fd2ceead0 --- /dev/null +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -0,0 +1,408 @@ +require 'spec_helper' + +describe HipchatService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:token) } + end + + context 'when service is inactive' do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:token) } + end + end + + describe "Execute" do + let(:hipchat) { described_class.new } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } + let(:project_name) { project.full_name.gsub(/\s/, '') } + let(:token) { 'verySecret' } + let(:server_url) { 'https://hipchat.example.com'} + let(:push_sample_data) do + Gitlab::DataBuilder::Push.build_sample(project, user) + end + + before do + allow(hipchat).to receive_messages( + project_id: project.id, + project: project, + room: 123456, + server: server_url, + token: token + ) + WebMock.stub_request(:post, api_url) + end + + it 'tests and return errors' do + allow(hipchat).to receive(:execute).and_raise(StandardError, 'no such room') + result = hipchat.test(push_sample_data) + + expect(result[:success]).to be_falsey + expect(result[:result].to_s).to eq('no such room') + end + + it 'uses v1 if version is provided' do + allow(hipchat).to receive(:api_version).and_return('v1') + expect(HipChat::Client).to receive(:new).with( + token, + api_version: 'v1', + server_url: server_url + ).and_return(double(:hipchat_service).as_null_object) + hipchat.execute(push_sample_data) + end + + it 'uses v2 as the version when nothing is provided' do + allow(hipchat).to receive(:api_version).and_return('') + expect(HipChat::Client).to receive(:new).with( + token, + api_version: 'v2', + server_url: server_url + ).and_return(double(:hipchat_service).as_null_object) + hipchat.execute(push_sample_data) + end + + context 'push events' do + it "calls Hipchat API for push events" do + hipchat.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "creates a push message" do + message = hipchat.send(:create_push_message, push_sample_data) + + push_sample_data[:object_attributes] + branch = push_sample_data[:ref].gsub('refs/heads/', '') + expect(message).to include("#{user.name} pushed to branch " \ + "#{branch} of " \ + "#{project_name}") + end + end + + context 'tag_push events' do + let(:push_sample_data) do + Gitlab::DataBuilder::Push.build( + project, + user, + Gitlab::Git::BLANK_SHA, + '1' * 40, + 'refs/tags/test', + []) + end + + it "calls Hipchat API for tag push events" do + hipchat.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "creates a tag push message" do + message = hipchat.send(:create_push_message, push_sample_data) + + push_sample_data[:object_attributes] + expect(message).to eq("#{user.name} pushed new tag " \ + "test to " \ + "#{project_name}\n") + end + end + + context 'issue events' do + let(:issue) { create(:issue, title: 'Awesome issue', description: '**please** fix') } + let(:issue_service) { Issues::CreateService.new(project, user) } + let(:issues_sample_data) { issue_service.hook_data(issue, 'open') } + + it "calls Hipchat API for issue events" do + hipchat.execute(issues_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "creates an issue message" do + message = hipchat.send(:create_issue_message, issues_sample_data) + + obj_attr = issues_sample_data[:object_attributes] + expect(message).to eq("#{user.name} opened " \ + "issue ##{obj_attr["iid"]} in " \ + "#{project_name}: " \ + "Awesome issue" \ + "
      please fix
      ") + end + end + + context 'merge request events' do + let(:merge_request) { create(:merge_request, description: '**please** fix', title: 'Awesome merge request', target_project: project, source_project: project) } + let(:merge_service) { MergeRequests::CreateService.new(project, user) } + let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') } + + it "calls Hipchat API for merge requests events" do + hipchat.execute(merge_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "creates a merge request message" do + message = hipchat.send(:create_merge_request_message, + merge_sample_data) + + obj_attr = merge_sample_data[:object_attributes] + expect(message).to eq("#{user.name} opened " \ + "merge request !#{obj_attr["iid"]} in " \ + "#{project_name}: " \ + "Awesome merge request" \ + "
      please fix
      ") + end + end + + context "Note events" do + let(:user) { create(:user) } + let(:project) { create(:project, :repository, creator: user) } + + context 'when commit comment event triggered' do + let(:commit_note) do + create(:note_on_commit, author: user, project: project, + commit_id: project.repository.commit.id, + note: 'a comment on a commit') + end + + it "calls Hipchat API for commit comment events" do + data = Gitlab::DataBuilder::Note.build(commit_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + commit_id = Commit.truncate_sha(data[:commit][:id]) + title = hipchat.send(:format_title, data[:commit][:message]) + + expect(message).to eq("#{user.name} commented on " \ + "commit #{commit_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
      a comment on a commit
      ") + end + end + + context 'when merge request comment event triggered' do + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let(:merge_request_note) do + create(:note_on_merge_request, noteable: merge_request, + project: project, + note: "merge request **note**") + end + + it "calls Hipchat API for merge request comment events" do + data = Gitlab::DataBuilder::Note.build(merge_request_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + merge_id = data[:merge_request]['iid'] + title = data[:merge_request]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "merge request !#{merge_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
      merge request note
      ") + end + end + + context 'when issue comment event triggered' do + let(:issue) { create(:issue, project: project) } + let(:issue_note) do + create(:note_on_issue, noteable: issue, project: project, + note: "issue **note**") + end + + it "calls Hipchat API for issue comment events" do + data = Gitlab::DataBuilder::Note.build(issue_note, user) + hipchat.execute(data) + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + issue_id = data[:issue]['iid'] + title = data[:issue]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "issue ##{issue_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
      issue note
      ") + end + + context 'with confidential issue' do + before do + issue.update!(confidential: true) + end + + it 'calls Hipchat API with issue comment' do + data = Gitlab::DataBuilder::Note.build(issue_note, user) + hipchat.execute(data) + + message = hipchat.send(:create_message, data) + + expect(message).to include("
      issue note
      ") + end + end + end + + context 'when snippet comment event triggered' do + let(:snippet) { create(:project_snippet, project: project) } + let(:snippet_note) do + create(:note_on_project_snippet, noteable: snippet, + project: project, + note: "snippet note") + end + + it "calls Hipchat API for snippet comment events" do + data = Gitlab::DataBuilder::Note.build(snippet_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + snippet_id = data[:snippet]['id'] + title = data[:snippet]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "snippet ##{snippet_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
      snippet note
      ") + end + end + end + + context 'pipeline events' do + let(:pipeline) { create(:ci_empty_pipeline, user: create(:user)) } + let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } + + context 'for failed' do + before do + pipeline.drop + end + + it "calls Hipchat API" do + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "creates a build message" do + message = hipchat.__send__(:create_pipeline_message, data) + + project_url = project.web_url + project_name = project.full_name.gsub(/\s/, '') + pipeline_attributes = data[:object_attributes] + ref = pipeline_attributes[:ref] + ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch' + duration = pipeline_attributes[:duration] + user_name = data[:user][:name] + + expect(message).to eq("#{project_name}: " \ + "Pipeline ##{pipeline.id} " \ + "of #{ref} #{ref_type} " \ + "by #{user_name} failed in #{duration} second(s)") + end + end + + context 'for succeeded' do + before do + pipeline.succeed + end + + it "calls Hipchat API" do + hipchat.notify_only_broken_pipelines = false + hipchat.execute(data) + expect(WebMock).to have_requested(:post, api_url).once + end + + it "notifies only broken" do + hipchat.notify_only_broken_pipelines = true + hipchat.execute(data) + expect(WebMock).not_to have_requested(:post, api_url).once + end + end + end + + context "#message_options" do + it "is set to the defaults" do + expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' }) + end + + it "sets notify to true" do + allow(hipchat).to receive(:notify).and_return('1') + + expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' }) + end + + it "sets the color" do + allow(hipchat).to receive(:color).and_return('red') + + expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' }) + end + + context 'with a successful build' do + it 'uses the green color' do + data = { object_kind: 'pipeline', + object_attributes: { status: 'success' } } + + expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'green' }) + end + end + + context 'with a failed build' do + it 'uses the red color' do + data = { object_kind: 'pipeline', + object_attributes: { status: 'failed' } } + + expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'red' }) + end + end + end + end + + context 'with UrlBlocker' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:hipchat) { described_class.new(project: project) } + let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } + + describe '#execute' do + before do + hipchat.server = 'http://localhost:9123' + end + + it 'raises UrlBlocker for localhost' do + expect(Gitlab::UrlBlocker).to receive(:validate!).and_call_original + expect { hipchat.execute(push_sample_data) }.to raise_error(Gitlab::HTTP::BlockedUrlError) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 7f8d2ff91fd..9f6a0b53281 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -44,6 +44,7 @@ describe Project do it { is_expected.to have_one(:pipelines_email_service) } it { is_expected.to have_one(:irker_service) } it { is_expected.to have_one(:pivotaltracker_service) } + it { is_expected.to have_one(:hipchat_service) } it { is_expected.to have_one(:flowdock_service) } it { is_expected.to have_one(:assembla_service) } it { is_expected.to have_one(:slack_slash_commands_service) } -- cgit v1.2.1 From 69544c1ebe48f4a73fae3530bfab73378efad7a2 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 9 Apr 2019 15:49:15 +0100 Subject: Fix UrlBlocker spec The newer version of HTTParty we now use will try harder to get a valid URL out of a relative one. Unfortunately, when there's no room (as was the case in this spec), the relative URL begins with `//`, which is actually a protocol-relative URL, not a simple path. Adding a room - which will always be the case in 'real life' - fixes this. --- spec/models/project_services/hipchat_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index b0fd2ceead0..23b65400ce6 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -391,7 +391,7 @@ describe HipchatService do context 'with UrlBlocker' do let(:user) { create(:user) } let(:project) { create(:project, :repository) } - let(:hipchat) { described_class.new(project: project) } + let(:hipchat) { create(:hipchat_service, project: project, properties: { room: 'test' }) } let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } describe '#execute' do -- cgit v1.2.1 From e00c7016b10892d847335d6a3944f34b717861d9 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 9 Apr 2019 16:48:31 +0100 Subject: Add frozen_string_literal to HipChat files --- spec/features/projects/services/user_activates_hipchat_spec.rb | 2 ++ spec/models/project_services/hipchat_service_spec.rb | 2 ++ 2 files changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/services/user_activates_hipchat_spec.rb b/spec/features/projects/services/user_activates_hipchat_spec.rb index 2f5313c91f9..d6b69a5bd68 100644 --- a/spec/features/projects/services/user_activates_hipchat_spec.rb +++ b/spec/features/projects/services/user_activates_hipchat_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'User activates HipChat' do diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 23b65400ce6..fd9e33c1781 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HipchatService do -- cgit v1.2.1 From c5094f361ffdb86c7c33c3e367b33d878efbead8 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 10 Apr 2019 13:54:32 +0200 Subject: Replace individual ESLint rules with jest/recommended --- spec/frontend/.eslintrc.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml index ff18f0e4a2d..e78a38d31f5 100644 --- a/spec/frontend/.eslintrc.yml +++ b/spec/frontend/.eslintrc.yml @@ -3,6 +3,8 @@ env: jest/globals: true plugins: - jest +extends: + - 'plugin:jest/recommended' settings: import/resolver: jest: @@ -12,8 +14,3 @@ globals: loadFixtures: false preloadFixtures: false setFixtures: false -rules: - jest/no-identical-title: error - jest/no-focused-tests: error - jest/valid-describe: error - jest/no-jasmine-globals: error -- cgit v1.2.1 From 81da9337e17d288366d4257e1bbe4ba62cb1b21c Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 10 Apr 2019 10:49:25 -0300 Subject: Remove multi-line suggestions feature flag That's a straightforward feature flag code removal for 11.10 --- spec/lib/banzai/filter/suggestion_filter_spec.rb | 27 ++++-------------------- 1 file changed, 4 insertions(+), 23 deletions(-) (limited to 'spec') diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb index af6f002fa30..9c4650b73de 100644 --- a/spec/lib/banzai/filter/suggestion_filter_spec.rb +++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb @@ -28,30 +28,11 @@ describe Banzai::Filter::SuggestionFilter do let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR } let(:input) { %(
      foo\n
      ) } - context 'feature disabled' do - before do - stub_feature_flags(multi_line_suggestions: false) - end + it 'element has correct data-lang-params' do + doc = filter(input, default_context) + pre = doc.css('pre').first - it 'removes data-lang-params if it matches a multi-line suggestion param' do - doc = filter(input, default_context) - pre = doc.css('pre').first - - expect(pre[data_attr]).to be_nil - end - end - - context 'feature enabled' do - before do - stub_feature_flags(multi_line_suggestions: true) - end - - it 'keeps data-lang-params' do - doc = filter(input, default_context) - pre = doc.css('pre').first - - expect(pre[data_attr]).to eq('-3+2') - end + expect(pre[data_attr]).to eq('-3+2') end end end -- cgit v1.2.1 From 177bc25e3fc5c8bebc7073b545bf9ebf3b1035c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 10 Apr 2019 16:20:29 +0200 Subject: Quarantine flaky specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/issues/issue_sidebar_spec.rb | 2 +- spec/features/issues/user_creates_branch_and_merge_request_spec.rb | 2 +- spec/features/issues/user_interacts_with_awards_spec.rb | 2 +- spec/services/quick_actions/interpret_service_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 3050f23c130..321da8f44d7 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -130,7 +130,7 @@ describe 'Issue Sidebar' do end end - context 'creating a project label', :js do + context 'creating a project label', :js, :quarantine do before do page.within('.block.labels') do click_link 'Create project' diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index cf665cff262..b69fba0db00 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -29,7 +29,7 @@ describe 'User creates branch and merge request on issue page', :js do end # In order to improve tests performance, all UI checks are placed in this test. - it 'shows elements' do + it 'shows elements', :quarantine do button_create_merge_request = find('.js-create-merge-request') button_toggle_dropdown = find('.create-mr-dropdown-wrap .dropdown-toggle') diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index eaeaa04f50d..d117620a2b1 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -75,7 +75,7 @@ describe 'User interacts with awards' do end end - it 'shows the list of award emoji categories' do + it 'shows the list of award emoji categories', :quarantine do page.within('.awards') do page.find('.js-add-award').click end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index c450f89c3cb..95a131e8c86 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -529,7 +529,7 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end - it_behaves_like 'assign command' do + it_behaves_like 'assign command', :quarantine do let(:content) { "/assign @#{developer.username} @#{developer2.username}" } let(:issuable) { merge_request } end -- cgit v1.2.1 From 97552924d0491dc10c3569e606a151ec13cd8bea Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 10 Apr 2019 23:46:16 +0800 Subject: Require possible EE extension --- spec/support/helpers/stub_configuration.rb | 3 +++ spec/support/helpers/stub_object_storage.rb | 3 +++ 2 files changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index cfa9151b2d7..3e507fb133e 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -120,3 +120,6 @@ module StubConfiguration end end end + +require_relative '../../../ee/spec/support/helpers/ee/stub_configuration' if + Dir.exist?("#{__dir__}/../../../ee") diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index 30c8477f16a..d31f9908714 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -73,3 +73,6 @@ module StubObjectStorage EOS end end + +require_relative '../../../ee/spec/support/helpers/ee/stub_object_storage' if + Dir.exist?("#{__dir__}/../../../ee") -- cgit v1.2.1 From 48bc3be69483c88f9720e4b1e8e23f92410415b2 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Thu, 4 Apr 2019 15:20:43 +0800 Subject: Fix naming of test ALso makes it consistent with EE --- spec/services/merge_requests/refresh_service_spec.rb | 4 +--- spec/services/merge_requests/update_service_spec.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 5ed06df7072..7063ca9ac14 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -269,13 +269,11 @@ describe MergeRequests::RefreshService do end end - context 'push to origin repo source branch when an MR was reopened' do + context 'push to origin repo source branch' do let(:refresh_service) { service.new(@project, @user) } let(:notification_service) { spy('notification_service') } before do - @merge_request.update(state: :reopened) - allow(refresh_service).to receive(:execute_hooks) allow(NotificationService).to receive(:new) { notification_service } refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 0525899ebfa..15aea97ff29 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -405,7 +405,7 @@ describe MergeRequests::UpdateService, :mailer do end end - context 'when the issue is relabeled' do + context 'when the merge request is relabeled' do let!(:non_subscriber) { create(:user) } let!(:subscriber) { create(:user) { |u| label.toggle_subscription(u, project) } } -- cgit v1.2.1 From bb225b66758b861d3e8243d130635a6ea65c7d41 Mon Sep 17 00:00:00 2001 From: Ryan Cobb Date: Wed, 10 Apr 2019 11:22:05 -0600 Subject: Adds instance metrics to usage ping This adds influxdb_metrics_enabled and prometheus_metrics_enabled to usage ping. --- spec/lib/gitlab/usage_data_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 549cc5ac057..4e910e67ac2 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -57,6 +57,8 @@ describe Gitlab::UsageData do database avg_cycle_analytics web_ide_commits + influxdb_metrics_enabled + prometheus_metrics_enabled )) end -- cgit v1.2.1 From 674b926ef4dd48146be7fa8a8c37861b285bbe3b Mon Sep 17 00:00:00 2001 From: John Cai Date: Tue, 9 Apr 2019 17:31:20 -0700 Subject: Guard against nil dereferenced_target --- spec/lib/gitlab/git/repository_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index fdb43d1221a..778950c95e4 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -531,6 +531,13 @@ describe Gitlab::Git::Repository, :seed_helper do it 'has valid commit ids as keys' do expect(subject.keys).to all( match(Commit::COMMIT_SHA_PATTERN) ) end + + it 'does not error when dereferenced_target is nil' do + blob_id = repository.blob_at('master', 'README.md').id + repository_rugged.tags.create("refs/tags/blob-tag", blob_id) + + expect { subject }.not_to raise_error + end end describe '#fetch_repository_as_mirror' do -- cgit v1.2.1 From c57529556c2ba3053e3a622efc2f5b6eeaabe009 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Thu, 14 Mar 2019 10:18:18 +0100 Subject: Revert "Merge branch 'tz-reorganise-digests-json' into 'master'" Reverts the loading of emojis and its images to move forward with RC --- spec/javascripts/awards_handler_spec.js | 11 -- spec/javascripts/emoji_spec.js | 189 ++++++++++++-------------------- spec/lib/gitlab/path_regex_spec.rb | 2 +- 3 files changed, 73 insertions(+), 129 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index e10df1b45e7..02200f77ad7 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,16 +1,12 @@ import $ from 'jquery'; import Cookies from 'js-cookie'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; import loadAwardsHandler from '~/awards_handler'; import '~/lib/utils/common_utils'; -import { EMOJI_VERSION } from '~/emoji'; window.gl = window.gl || {}; window.gon = window.gon || {}; let openAndWaitForEmojiMenu; -let mock; let awardsHandler = null; const urlRoot = gon.relative_url_root; @@ -23,13 +19,8 @@ const lazyAssert = function(done, assertFn) { }; describe('AwardsHandler', function() { - const emojiData = getJSONFixture('emojis/emojis.json'); preloadFixtures('snippets/show.html'); - beforeEach(function(done) { - mock = new MockAdapter(axios); - mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData); - loadFixtures('snippets/show.html'); loadAwardsHandler(true) .then(obj => { @@ -62,8 +53,6 @@ describe('AwardsHandler', function() { // restore original url root value gon.relative_url_root = urlRoot; - mock.restore(); - // Undo what we did to the shared $('body').removeAttr('data-page'); diff --git a/spec/javascripts/emoji_spec.js b/spec/javascripts/emoji_spec.js index 0ac375145be..3db4d9800f1 100644 --- a/spec/javascripts/emoji_spec.js +++ b/spec/javascripts/emoji_spec.js @@ -1,6 +1,4 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import { initEmojiMap, glEmojiTag, EMOJI_VERSION } from '~/emoji'; +import { glEmojiTag } from '~/emoji'; import isEmojiUnicodeSupported, { isFlagEmoji, isRainbowFlagEmoji, @@ -9,7 +7,6 @@ import isEmojiUnicodeSupported, { isHorceRacingSkinToneComboEmoji, isPersonZwjEmoji, } from '~/emoji/support/is_emoji_unicode_supported'; -import installGlEmojiElement from '~/behaviors/gl_emoji'; const emptySupportMap = { personZwj: false, @@ -34,35 +31,34 @@ const emojiFixtureMap = { bomb: { name: 'bomb', moji: '💣', - uni: '6.0', + unicodeVersion: '6.0', }, construction_worker_tone5: { name: 'construction_worker_tone5', moji: '👷🏿', - uni: '8.0', + unicodeVersion: '8.0', }, five: { name: 'five', moji: '5️⃣', - uni: '3.0', + unicodeVersion: '3.0', }, grey_question: { name: 'grey_question', moji: '❔', - uni: '6.0', + unicodeVersion: '6.0', }, }; function markupToDomElement(markup) { const div = document.createElement('div'); div.innerHTML = markup; - document.body.appendChild(div); return div.firstElementChild; } -function testGlEmojiImageFallback(element, name) { +function testGlEmojiImageFallback(element, name, src) { expect(element.tagName.toLowerCase()).toBe('img'); - expect(element.getAttribute('src')).toBe(`/-/emojis/${EMOJI_VERSION}/${name}.png`); + expect(element.getAttribute('src')).toBe(src); expect(element.getAttribute('title')).toBe(`:${name}:`); expect(element.getAttribute('alt')).toBe(`:${name}:`); } @@ -72,11 +68,12 @@ const defaults = { sprite: false, }; -function testGlEmojiElement(element, name, uni, unicodeMoji, options = {}) { +function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) { const opts = Object.assign({}, defaults, options); expect(element.tagName.toLowerCase()).toBe('gl-emoji'); expect(element.dataset.name).toBe(name); - expect(element.dataset.uni).toBe(uni); + expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0); + expect(element.dataset.unicodeVersion).toBe(unicodeVersion); const fallbackSpriteClass = `emoji-${name}`; if (opts.sprite) { @@ -89,7 +86,7 @@ function testGlEmojiElement(element, name, uni, unicodeMoji, options = {}) { if (opts.forceFallback && !opts.sprite) { // Check for image fallback - testGlEmojiImageFallback(element.firstElementChild, name); + testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc); } else { // Otherwise make sure things are still unicode text expect(element.textContent.trim()).toBe(unicodeMoji); @@ -97,143 +94,101 @@ function testGlEmojiElement(element, name, uni, unicodeMoji, options = {}) { } describe('gl_emoji', () => { - beforeAll(() => { - installGlEmojiElement(); - }); - - let mock; - const emojiData = getJSONFixture('emojis/emojis.json'); - - beforeEach(function(done) { - mock = new MockAdapter(axios); - mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData); - - initEmojiMap() - .then(() => { - done(); - }) - .catch(() => { - done(); - }); - }); - - afterEach(function() { - mock.restore(); - }); - describe('glEmojiTag', () => { - it('bomb emoji', done => { + it('bomb emoji', () => { const emojiKey = 'bomb'; const markup = glEmojiTag(emojiFixtureMap[emojiKey].name); const glEmojiElement = markupToDomElement(markup); - setTimeout(() => { - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].uni, - emojiFixtureMap[emojiKey].moji, - ); - done(); - }); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + ); }); - it('bomb emoji with image fallback', done => { + it('bomb emoji with image fallback', () => { const emojiKey = 'bomb'; const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { forceFallback: true, }); const glEmojiElement = markupToDomElement(markup); - setTimeout(() => { - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].uni, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - }, - ); - done(); - }); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + }, + ); }); - it('bomb emoji with sprite fallback readiness', done => { + it('bomb emoji with sprite fallback readiness', () => { const emojiKey = 'bomb'; const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { sprite: true, }); const glEmojiElement = markupToDomElement(markup); - setTimeout(() => { - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].uni, - emojiFixtureMap[emojiKey].moji, - { - sprite: true, - }, - ); - done(); - }); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + sprite: true, + }, + ); }); - it('bomb emoji with sprite fallback', done => { + it('bomb emoji with sprite fallback', () => { const emojiKey = 'bomb'; const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { forceFallback: true, sprite: true, }); const glEmojiElement = markupToDomElement(markup); - setTimeout(() => { - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].uni, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - sprite: true, - }, - ); - done(); - }); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + sprite: true, + }, + ); }); - it('question mark when invalid emoji name given', done => { + it('question mark when invalid emoji name given', () => { const name = 'invalid_emoji'; const emojiKey = 'grey_question'; const markup = glEmojiTag(name); const glEmojiElement = markupToDomElement(markup); - setTimeout(() => { - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].uni, - emojiFixtureMap[emojiKey].moji, - ); - done(); - }); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + ); }); - it('question mark with image fallback when invalid emoji name given', done => { + it('question mark with image fallback when invalid emoji name given', () => { const name = 'invalid_emoji'; const emojiKey = 'grey_question'; const markup = glEmojiTag(name, { forceFallback: true, }); const glEmojiElement = markupToDomElement(markup); - setTimeout(() => { - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].uni, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - }, - ); - done(); - }); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + }, + ); }); }); @@ -434,7 +389,7 @@ describe('gl_emoji', () => { const isSupported = isEmojiUnicodeSupported( unicodeSupportMap, emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].uni, + emojiFixtureMap[emojiKey].unicodeVersion, ); expect(isSupported).toBeTruthy(); @@ -446,7 +401,7 @@ describe('gl_emoji', () => { const isSupported = isEmojiUnicodeSupported( unicodeSupportMap, emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].uni, + emojiFixtureMap[emojiKey].unicodeVersion, ); expect(isSupported).toBeFalsy(); @@ -460,7 +415,7 @@ describe('gl_emoji', () => { const isSupported = isEmojiUnicodeSupported( unicodeSupportMap, emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].uni, + emojiFixtureMap[emojiKey].unicodeVersion, ); expect(isSupported).toBeFalsy(); @@ -486,7 +441,7 @@ describe('gl_emoji', () => { const isSupported = isEmojiUnicodeSupported( unicodeSupportMap, emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].uni, + emojiFixtureMap[emojiKey].unicodeVersion, ); expect(isSupported).toBeFalsy(); @@ -504,7 +459,7 @@ describe('gl_emoji', () => { const isSupported = isEmojiUnicodeSupported( unicodeSupportMap, emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].uni, + emojiFixtureMap[emojiKey].unicodeVersion, ); expect(isSupported).toBeTruthy(); @@ -522,7 +477,7 @@ describe('gl_emoji', () => { const isSupported = isEmojiUnicodeSupported( unicodeSupportMap, emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].uni, + emojiFixtureMap[emojiKey].unicodeVersion, ); expect(isSupported).toBeFalsy(); diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 71e69a0d418..857862a2abd 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -107,7 +107,7 @@ describe Gitlab::PathRegex do git = Gitlab.config.git.bin_path tracked = `cd #{Rails.root} && #{git} ls-files public` .split("\n") - .map { |entry| entry.start_with?('public/-/') ? '-' : entry.gsub('public/', '') } + .map { |entry| entry.gsub('public/', '') } .uniq tracked + %w(assets uploads) end -- cgit v1.2.1 From 2075ef7d071286ad156ca13640336572bb20bceb Mon Sep 17 00:00:00 2001 From: Willian Balmant Date: Wed, 10 Apr 2019 20:50:26 +0000 Subject: No leading/trailing spaces when generating heading ids (Fixes #57528) Update based on comments in MR #27025 --- spec/lib/banzai/filter/table_of_contents_filter_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'spec') diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb index 7213cd58ea7..f776e21a89e 100644 --- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb +++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb @@ -58,6 +58,11 @@ describe Banzai::Filter::TableOfContentsFilter do expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-filled-with-punctuation' end + it 'removes any leading or trailing spaces' do + doc = filter(header(1, " \r\n \t Title with spaces \r\n\t ")) + expect(doc.css('h1 a').first.attr('href')).to eq '#title-with-spaces' + end + it 'appends a unique number to duplicates' do doc = filter(header(1, 'One') + header(2, 'One')) -- cgit v1.2.1 From d119d3d1b25aac661e6251addf87b280bd37f0c5 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 11 Apr 2019 06:29:07 +0000 Subject: Align UrlValidator to validate_url gem implementation. Renamed UrlValidator to AddressableUrlValidator to avoid 'url:' naming collision with ActiveModel::Validations::UrlValidator in 'validates' statement. Make use of the options attribute of the parent class ActiveModel::EachValidator. Add more options: allow_nil, allow_blank, message. Renamed 'protocols' option to 'schemes' to match the option naming from UrlValidator. --- .../projects/mirrors_controller_spec.rb | 2 +- spec/lib/gitlab/url_blocker_spec.rb | 6 +- spec/requests/api/commit_statuses_spec.rb | 17 +- .../shared_examples/url_validator_examples.rb | 24 +- spec/validators/addressable_url_validator_spec.rb | 315 +++++++++++++++++++++ spec/validators/public_url_validator_spec.rb | 8 +- spec/validators/url_validator_spec.rb | 226 --------------- 7 files changed, 351 insertions(+), 247 deletions(-) create mode 100644 spec/validators/addressable_url_validator_spec.rb delete mode 100644 spec/validators/url_validator_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb index f2b73956e8d..3ababe18055 100644 --- a/spec/controllers/projects/mirrors_controller_spec.rb +++ b/spec/controllers/projects/mirrors_controller_spec.rb @@ -79,7 +79,7 @@ describe Projects::MirrorsController do do_put(project, remote_mirrors_attributes: remote_mirror_attributes) expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-push-remote-settings')) - expect(flash[:alert]).to match(/Only allowed protocols are/) + expect(flash[:alert]).to match(/Only allowed schemes are/) end it 'does not create a RemoteMirror object' do diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 62970bd8cb6..445a56ab0d8 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -23,10 +23,10 @@ describe Gitlab::UrlBlocker do expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git', ports: ports)).to be true end - it 'returns true for bad protocol' do - expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['https'])).to be false + it 'returns true for bad scheme' do + expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['https'])).to be false expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false - expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true + expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['http'])).to be true end it 'returns true for bad protocol on configured web/SSH host and ports' do diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 9388343c392..b5e45f99109 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -306,7 +306,22 @@ describe API::CommitStatuses do it 'responds with bad request status and validation errors' do expect(response).to have_gitlab_http_status(400) expect(json_response['message']['target_url']) - .to include 'is blocked: Only allowed protocols are http, https' + .to include 'is blocked: Only allowed schemes are http, https' + end + end + + context 'when target URL is an unsupported scheme' do + before do + post api(post_url, developer), params: { + state: 'pending', + target_url: 'git://example.com' + } + end + + it 'responds with bad request status and validation errors' do + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']['target_url']) + .to include 'is blocked: Only allowed schemes are http, https' end end end diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb index 1f7e2f7ff79..25277ccd9aa 100644 --- a/spec/support/shared_examples/url_validator_examples.rb +++ b/spec/support/shared_examples/url_validator_examples.rb @@ -1,15 +1,15 @@ -RSpec.shared_examples 'url validator examples' do |protocols| +RSpec.shared_examples 'url validator examples' do |schemes| let(:validator) { described_class.new(attributes: [:link_url], **options) } let!(:badge) { build(:badge, link_url: 'http://www.example.com') } - subject { validator.validate_each(badge, :link_url, badge.link_url) } + subject { validator.validate(badge) } - describe '#validates_each' do + describe '#validate' do context 'with no options' do let(:options) { {} } - it "allows #{protocols.join(',')} protocols by default" do - expect(validator.send(:default_options)[:protocols]).to eq protocols + it "allows #{schemes.join(',')} schemes by default" do + expect(validator.options[:schemes]).to eq schemes end it 'checks that the url structure is valid' do @@ -17,25 +17,25 @@ RSpec.shared_examples 'url validator examples' do |protocols| subject - expect(badge.errors.empty?).to be false + expect(badge.errors).to be_present end end - context 'with protocols' do - let(:options) { { protocols: %w[http] } } + context 'with schemes' do + let(:options) { { schemes: %w(http) } } - it 'allows urls with the defined protocols' do + it 'allows urls with the defined schemes' do subject - expect(badge.errors.empty?).to be true + expect(badge.errors).to be_empty end - it 'add error if the url protocol does not match the selected ones' do + it 'add error if the url scheme does not match the selected ones' do badge.link_url = 'https://www.example.com' subject - expect(badge.errors.empty?).to be false + expect(badge.errors).to be_present end end end diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb new file mode 100644 index 00000000000..387e84b2d04 --- /dev/null +++ b/spec/validators/addressable_url_validator_spec.rb @@ -0,0 +1,315 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AddressableUrlValidator do + let!(:badge) { build(:badge, link_url: 'http://www.example.com') } + subject { validator.validate(badge) } + + include_examples 'url validator examples', described_class::DEFAULT_OPTIONS[:schemes] + + describe 'validations' do + include_context 'invalid urls' + + let(:validator) { described_class.new(attributes: [:link_url]) } + + it 'returns error when url is nil' do + expect(validator.validate_each(badge, :link_url, nil)).to be_falsey + expect(badge.errors.first[1]).to eq validator.options.fetch(:message) + end + + it 'returns error when url is empty' do + expect(validator.validate_each(badge, :link_url, '')).to be_falsey + expect(badge.errors.first[1]).to eq validator.options.fetch(:message) + end + + it 'does not allow urls with CR or LF characters' do + aggregate_failures do + urls_with_CRLF.each do |url| + expect(validator.validate_each(badge, :link_url, url)[0]).to eq 'is blocked: URI is invalid' + end + end + end + + it 'provides all arguments to UrlBlock validate' do + expect(Gitlab::UrlBlocker) + .to receive(:validate!) + .with(badge.link_url, described_class::BLOCKER_VALIDATE_OPTIONS) + .and_return(true) + + subject + + expect(badge.errors).to be_empty + end + end + + context 'by default' do + let(:validator) { described_class.new(attributes: [:link_url]) } + + it 'does not block urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).to be_empty + end + + it 'does not block urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).to be_empty + end + + it 'does block nil urls' do + badge.link_url = nil + + subject + + expect(badge.errors).to be_present + end + + it 'does block blank urls' do + badge.link_url = '\n\r \n' + + subject + + expect(badge.errors).to be_present + end + + it 'strips urls' do + badge.link_url = "\n\r\n\nhttps://127.0.0.1\r\n\r\n\n\n\n" + + # It's unusual for a validator to modify its arguments. Some extensions, + # such as attr_encrypted, freeze the string to signal that modifications + # will not be persisted, so freeze this string to ensure the scheme is + # compatible with them. + badge.link_url.freeze + + subject + + expect(badge.errors).to be_empty + expect(badge.link_url).to eq('https://127.0.0.1') + end + end + + context 'when message is set' do + let(:message) { 'is blocked: test message' } + let(:validator) { described_class.new(attributes: [:link_url], allow_nil: false, message: message) } + + it 'does block nil url with provided error message' do + expect(validator.validate_each(badge, :link_url, nil)).to be_falsey + expect(badge.errors.first[1]).to eq message + end + end + + context 'when allow_nil is set to true' do + let(:validator) { described_class.new(attributes: [:link_url], allow_nil: true) } + + it 'does not block nil urls' do + badge.link_url = nil + + subject + + expect(badge.errors).to be_empty + end + end + + context 'when allow_blank is set to true' do + let(:validator) { described_class.new(attributes: [:link_url], allow_blank: true) } + + it 'does not block blank urls' do + badge.link_url = "\n\r \n" + + subject + + expect(badge.errors).to be_empty + end + end + + context 'when allow_localhost is set to false' do + let(:validator) { described_class.new(attributes: [:link_url], allow_localhost: false) } + + it 'blocks urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).to be_present + end + + context 'when allow_setting_local_requests is set to true' do + it 'does not block urls pointing to localhost' do + expect(described_class) + .to receive(:allow_setting_local_requests?) + .and_return(true) + + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).to be_empty + end + end + end + + context 'when allow_local_network is set to false' do + let(:validator) { described_class.new(attributes: [:link_url], allow_local_network: false) } + + it 'blocks urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).to be_present + end + + context 'when allow_setting_local_requests is set to true' do + it 'does not block urls pointing to local network' do + expect(described_class) + .to receive(:allow_setting_local_requests?) + .and_return(true) + + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).to be_empty + end + end + end + + context 'when ports is' do + let(:validator) { described_class.new(attributes: [:link_url], ports: ports) } + + context 'empty' do + let(:ports) { [] } + + it 'does not block any port' do + subject + + expect(badge.errors).to be_empty + end + end + + context 'set' do + let(:ports) { [443] } + + it 'blocks urls with a different port' do + subject + + expect(badge.errors).to be_present + end + end + end + + context 'when enforce_user is' do + let(:url) { 'http://$user@example.com'} + let(:validator) { described_class.new(attributes: [:link_url], enforce_user: enforce_user) } + + context 'true' do + let(:enforce_user) { true } + + it 'checks user format' do + badge.link_url = url + + subject + + expect(badge.errors).to be_present + end + end + + context 'false (default)' do + let(:enforce_user) { false } + + it 'does not check user format' do + badge.link_url = url + + subject + + expect(badge.errors).to be_empty + end + end + end + + context 'when ascii_only is' do + let(:url) { 'https://𝕘itⅼαƄ.com/foo/foo.bar'} + let(:validator) { described_class.new(attributes: [:link_url], ascii_only: ascii_only) } + + context 'true' do + let(:ascii_only) { true } + + it 'prevents unicode characters' do + badge.link_url = url + + subject + + expect(badge.errors).to be_present + end + end + + context 'false (default)' do + let(:ascii_only) { false } + + it 'does not prevent unicode characters' do + badge.link_url = url + + subject + + expect(badge.errors).to be_empty + end + end + end + + context 'when enforce_sanitization is' do + let(:validator) { described_class.new(attributes: [:link_url], enforce_sanitization: enforce_sanitization) } + let(:unsafe_url) { "https://replaceme.com/'>" } + let(:safe_url) { 'https://replaceme.com/path/to/somewhere' } + + let(:unsafe_internal_url) do + Gitlab.config.gitlab.protocol + '://' + Gitlab.config.gitlab.host + + "/'>" + end + + context 'true' do + let(:enforce_sanitization) { true } + + it 'prevents unsafe urls' do + badge.link_url = unsafe_url + + subject + + expect(badge.errors).to be_present + end + + it 'prevents unsafe internal urls' do + badge.link_url = unsafe_internal_url + + subject + + expect(badge.errors).to be_present + end + + it 'allows safe urls' do + badge.link_url = safe_url + + subject + + expect(badge.errors).to be_empty + end + end + + context 'false' do + let(:enforce_sanitization) { false } + + it 'allows unsafe urls' do + badge.link_url = unsafe_url + + subject + + expect(badge.errors).to be_empty + end + end + end +end diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb index 710dd3dc38e..f6364fb1dd5 100644 --- a/spec/validators/public_url_validator_spec.rb +++ b/spec/validators/public_url_validator_spec.rb @@ -1,20 +1,20 @@ require 'spec_helper' describe PublicUrlValidator do - include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS + include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes] context 'by default' do let(:validator) { described_class.new(attributes: [:link_url]) } let!(:badge) { build(:badge, link_url: 'http://www.example.com') } - subject { validator.validate_each(badge, :link_url, badge.link_url) } + subject { validator.validate(badge) } it 'blocks urls pointing to localhost' do badge.link_url = 'https://127.0.0.1' subject - expect(badge.errors.empty?).to be false + expect(badge.errors).to be_present end it 'blocks urls pointing to the local network' do @@ -22,7 +22,7 @@ describe PublicUrlValidator do subject - expect(badge.errors.empty?).to be false + expect(badge.errors).to be_present end end end diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb deleted file mode 100644 index 1bb42382e8a..00000000000 --- a/spec/validators/url_validator_spec.rb +++ /dev/null @@ -1,226 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe UrlValidator do - let!(:badge) { build(:badge, link_url: 'http://www.example.com') } - subject { validator.validate_each(badge, :link_url, badge.link_url) } - - include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS - - describe 'validations' do - include_context 'invalid urls' - - let(:validator) { described_class.new(attributes: [:link_url]) } - - it 'returns error when url is nil' do - expect(validator.validate_each(badge, :link_url, nil)).to be_nil - expect(badge.errors.first[1]).to eq 'must be a valid URL' - end - - it 'returns error when url is empty' do - expect(validator.validate_each(badge, :link_url, '')).to be_nil - expect(badge.errors.first[1]).to eq 'must be a valid URL' - end - - it 'does not allow urls with CR or LF characters' do - aggregate_failures do - urls_with_CRLF.each do |url| - expect(validator.validate_each(badge, :link_url, url)[0]).to eq 'is blocked: URI is invalid' - end - end - end - end - - context 'by default' do - let(:validator) { described_class.new(attributes: [:link_url]) } - - it 'does not block urls pointing to localhost' do - badge.link_url = 'https://127.0.0.1' - - subject - - expect(badge.errors.empty?).to be true - end - - it 'does not block urls pointing to the local network' do - badge.link_url = 'https://192.168.1.1' - - subject - - expect(badge.errors.empty?).to be true - end - - it 'strips urls' do - badge.link_url = "\n\r\n\nhttps://127.0.0.1\r\n\r\n\n\n\n" - - # It's unusual for a validator to modify its arguments. Some extensions, - # such as attr_encrypted, freeze the string to signal that modifications - # will not be persisted, so freeze this string to ensure the scheme is - # compatible with them. - badge.link_url.freeze - - subject - - expect(badge.errors).to be_empty - expect(badge.link_url).to eq('https://127.0.0.1') - end - end - - context 'when allow_localhost is set to false' do - let(:validator) { described_class.new(attributes: [:link_url], allow_localhost: false) } - - it 'blocks urls pointing to localhost' do - badge.link_url = 'https://127.0.0.1' - - subject - - expect(badge.errors.empty?).to be false - end - end - - context 'when allow_local_network is set to false' do - let(:validator) { described_class.new(attributes: [:link_url], allow_local_network: false) } - - it 'blocks urls pointing to the local network' do - badge.link_url = 'https://192.168.1.1' - - subject - - expect(badge.errors.empty?).to be false - end - end - - context 'when ports is' do - let(:validator) { described_class.new(attributes: [:link_url], ports: ports) } - - context 'empty' do - let(:ports) { [] } - - it 'does not block any port' do - subject - - expect(badge.errors.empty?).to be true - end - end - - context 'set' do - let(:ports) { [443] } - - it 'blocks urls with a different port' do - subject - - expect(badge.errors.empty?).to be false - end - end - end - - context 'when enforce_user is' do - let(:url) { 'http://$user@example.com'} - let(:validator) { described_class.new(attributes: [:link_url], enforce_user: enforce_user) } - - context 'true' do - let(:enforce_user) { true } - - it 'checks user format' do - badge.link_url = url - - subject - - expect(badge.errors.empty?).to be false - end - end - - context 'false (default)' do - let(:enforce_user) { false } - - it 'does not check user format' do - badge.link_url = url - - subject - - expect(badge.errors.empty?).to be true - end - end - end - - context 'when ascii_only is' do - let(:url) { 'https://𝕘itⅼαƄ.com/foo/foo.bar'} - let(:validator) { described_class.new(attributes: [:link_url], ascii_only: ascii_only) } - - context 'true' do - let(:ascii_only) { true } - - it 'prevents unicode characters' do - badge.link_url = url - - subject - - expect(badge.errors.empty?).to be false - end - end - - context 'false (default)' do - let(:ascii_only) { false } - - it 'does not prevent unicode characters' do - badge.link_url = url - - subject - - expect(badge.errors.empty?).to be true - end - end - end - - context 'when enforce_sanitization is' do - let(:validator) { described_class.new(attributes: [:link_url], enforce_sanitization: enforce_sanitization) } - let(:unsafe_url) { "https://replaceme.com/'>" } - let(:safe_url) { 'https://replaceme.com/path/to/somewhere' } - - let(:unsafe_internal_url) do - Gitlab.config.gitlab.protocol + '://' + Gitlab.config.gitlab.host + - "/'>" - end - - context 'true' do - let(:enforce_sanitization) { true } - - it 'prevents unsafe urls' do - badge.link_url = unsafe_url - - subject - - expect(badge.errors.empty?).to be false - end - - it 'prevents unsafe internal urls' do - badge.link_url = unsafe_internal_url - - subject - - expect(badge.errors.empty?).to be false - end - - it 'allows safe urls' do - badge.link_url = safe_url - - subject - - expect(badge.errors.empty?).to be true - end - end - - context 'false' do - let(:enforce_sanitization) { false } - - it 'allows unsafe urls' do - badge.link_url = unsafe_url - - subject - - expect(badge.errors.empty?).to be true - end - end - end -end -- cgit v1.2.1 From 2f704d83564f20f23c4adf2f6471cd745dd80e2a Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 11 Apr 2019 07:39:14 +0000 Subject: Delete static_fixtures.rb for static HAML fixtures --- .../javascripts/fixtures/environments/table.html.haml | 11 ----------- .../fixtures/static/environments/table.html | 15 +++++++++++++++ spec/javascripts/fixtures/static_fixtures.rb | 19 ------------------- 3 files changed, 15 insertions(+), 30 deletions(-) delete mode 100644 spec/javascripts/fixtures/environments/table.html.haml create mode 100644 spec/javascripts/fixtures/static/environments/table.html delete mode 100644 spec/javascripts/fixtures/static_fixtures.rb (limited to 'spec') diff --git a/spec/javascripts/fixtures/environments/table.html.haml b/spec/javascripts/fixtures/environments/table.html.haml deleted file mode 100644 index 59edc0396d2..00000000000 --- a/spec/javascripts/fixtures/environments/table.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -%table - %thead - %tr - %th Environment - %th Last deployment - %th Job - %th Commit - %th - %th - %tbody - %tr#environment-row diff --git a/spec/javascripts/fixtures/static/environments/table.html b/spec/javascripts/fixtures/static/environments/table.html new file mode 100644 index 00000000000..417af564ff1 --- /dev/null +++ b/spec/javascripts/fixtures/static/environments/table.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + +
      EnvironmentLast deploymentJobCommit
      diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb deleted file mode 100644 index cb4b90cdca5..00000000000 --- a/spec/javascripts/fixtures/static_fixtures.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe ApplicationController, '(Static JavaScript fixtures)', type: :controller do - include JavaScriptFixturesHelpers - - Dir.glob('{,ee/}spec/javascripts/fixtures/**/*.haml').map do |file_path| - it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '')}" do |example| - store_frontend_fixture(render_template(file_path), example.description) - end - end - - private - - def render_template(template_file_name) - controller = ApplicationController.new - controller.prepend_view_path(File.dirname(template_file_name)) - controller.render_to_string(template: File.basename(template_file_name), layout: false) - end -end -- cgit v1.2.1 From 171818df0a72097aa1a804c8213666b3f66b0966 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Thu, 11 Apr 2019 15:49:53 -0800 Subject: Revert "Merge branch '24704-download-repository-path' into 'master'" This reverts commit 6c75bd015cba181f028bc87c396c3d8e43b5dc3e, reversing changes made to 1be7f5aaa38aba79843eae8835be6c99c025e982. --- .../projects/branches/download_buttons_spec.rb | 2 +- .../projects/files/download_buttons_spec.rb | 2 +- .../projects/show/download_buttons_spec.rb | 3 ++- .../projects/tags/download_buttons_spec.rb | 2 +- spec/lib/gitlab/git/repository_spec.rb | 11 +------- spec/lib/gitlab/workhorse_spec.rb | 31 +++++++++------------- 6 files changed, 18 insertions(+), 33 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index 3e75890725e..c8dc72a34ec 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -35,7 +35,7 @@ describe 'Download buttons in branches page' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build') - expect(page).to have_link build.name, href: href + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb index 111972a6b00..03cb3530e2b 100644 --- a/spec/features/projects/files/download_buttons_spec.rb +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -30,7 +30,7 @@ describe 'Projects > Files > Download buttons in files tree' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build') - expect(page).to have_link build.name, href: href + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb index fee5f8001b0..3a2dcc5aa55 100644 --- a/spec/features/projects/show/download_buttons_spec.rb +++ b/spec/features/projects/show/download_buttons_spec.rb @@ -35,10 +35,11 @@ describe 'Projects > Show > Download buttons' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build') - expect(page).to have_link build.name, href: href + expect(page).to have_link "Download '#{build.name}'", href: href end it 'download links have download attribute' do + expect(page).to have_selector('a', text: 'Download') page.all('a', text: 'Download').each do |link| expect(link[:download]).to eq '' end diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index 4c8ec53836a..fbfd8cee7aa 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -36,7 +36,7 @@ describe 'Download buttons in tags page' do it 'shows download artifacts button' do href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build') - expect(page).to have_link build.name, href: href + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index fdb43d1221a..63345a56e42 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -152,14 +152,13 @@ describe Gitlab::Git::Repository, :seed_helper do let(:append_sha) { true } let(:ref) { 'master' } let(:format) { nil } - let(:path) { nil } let(:expected_extension) { 'tar.gz' } let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" } let(:expected_path) { File.join(storage_path, cache_key, expected_filename) } let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" } - subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha, path: path) } + subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha) } it 'sets CommitId to the commit SHA' do expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID) @@ -177,14 +176,6 @@ describe Gitlab::Git::Repository, :seed_helper do expect(metadata['ArchivePath']).to eq(expected_path) end - context 'path is set' do - let(:path) { 'foo/bar' } - - it 'appends the path to the prefix' do - expect(metadata['ArchivePrefix']).to eq("#{expected_prefix}-foo-bar") - end - end - context 'append_sha varies archive path and filename' do where(:append_sha, :ref, :expected_prefix) do sha = SeedRepo::LastCommit::ID diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index f8ce399287a..d02d9be5c5c 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -16,12 +16,20 @@ describe Gitlab::Workhorse do let(:ref) { 'master' } let(:format) { 'zip' } let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path } - let(:path) { 'some/path' } - let(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: nil, path: path) } + let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) } + let(:gitaly_params) do + base_params.merge( + 'GitalyServer' => { + 'address' => Gitlab::GitalyClient.address(project.repository_storage), + 'token' => Gitlab::GitalyClient.token(project.repository_storage) + }, + 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys + ) + end let(:cache_disabled) { false } subject do - described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil, path: path) + described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil) end before do @@ -33,22 +41,7 @@ describe Gitlab::Workhorse do expect(key).to eq('Gitlab-Workhorse-Send-Data') expect(command).to eq('git-archive') - expect(params).to eq({ - 'GitalyServer' => { - address: Gitlab::GitalyClient.address(project.repository_storage), - token: Gitlab::GitalyClient.token(project.repository_storage) - }, - 'ArchivePath' => metadata['ArchivePath'], - 'GetArchiveRequest' => Base64.urlsafe_encode64( - Gitaly::GetArchiveRequest.new( - repository: repository.gitaly_repository, - commit_id: metadata['CommitId'], - prefix: metadata['ArchivePrefix'], - format: Gitaly::GetArchiveRequest::Format::ZIP, - path: path - ).to_proto - ) - }.deep_stringify_keys) + expect(params).to include(gitaly_params) end context 'when archive caching is disabled' do -- cgit v1.2.1 From 1d04ae7458bec082a2f82d0776f89e297719f23c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 11 Apr 2019 09:54:20 +0100 Subject: Fixes Web IDE not loading merge request files Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/60243 --- spec/features/ide/user_opens_merge_request_spec.rb | 21 +++++++++++++++++++++ .../ide/stores/mutations/merge_request_spec.js | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 spec/features/ide/user_opens_merge_request_spec.rb (limited to 'spec') diff --git a/spec/features/ide/user_opens_merge_request_spec.rb b/spec/features/ide/user_opens_merge_request_spec.rb new file mode 100644 index 00000000000..185349219a7 --- /dev/null +++ b/spec/features/ide/user_opens_merge_request_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'IDE merge request', :js do + let(:merge_request) { create(:merge_request, :with_diffs, :simple, source_project: project) } + let(:project) { create(:project, :public, :repository) } + let(:user) { project.owner } + + before do + sign_in(user) + + visit(merge_request_path(merge_request)) + end + + it 'user opens merge request' do + click_link 'Open in Web IDE' + + wait_for_requests + + expect(page).to have_selector('.monaco-diff-editor') + end +end diff --git a/spec/frontend/ide/stores/mutations/merge_request_spec.js b/spec/frontend/ide/stores/mutations/merge_request_spec.js index e30ca22022f..afbe6770c0d 100644 --- a/spec/frontend/ide/stores/mutations/merge_request_spec.js +++ b/spec/frontend/ide/stores/mutations/merge_request_spec.js @@ -32,6 +32,24 @@ describe('IDE store merge request mutations', () => { expect(newMr.title).toBe('mr'); expect(newMr.active).toBeTruthy(); }); + + it('keeps original data', () => { + const versions = ['change']; + const mergeRequest = localState.projects.abcproject.mergeRequests[1]; + + mergeRequest.versions = versions; + + mutations.SET_MERGE_REQUEST(localState, { + projectPath: 'abcproject', + mergeRequestId: 1, + mergeRequest: { + title: ['change'], + }, + }); + + expect(mergeRequest.title).toBe('mr'); + expect(mergeRequest.versions).toEqual(versions); + }); }); describe('SET_MERGE_REQUEST_CHANGES', () => { -- cgit v1.2.1 From 4722b852e3cb1b2018aec8e06cc9994406e7a57c Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Thu, 11 Apr 2019 08:52:40 -0400 Subject: Increase clusters bundle specs timeout --- spec/frontend/clusters/clusters_bundle_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index eea7bd87257..33a35069004 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -12,7 +12,7 @@ import { setTestTimeout } from 'helpers/timeout'; import $ from 'jquery'; describe('Clusters', () => { - setTestTimeout(500); + setTestTimeout(1000); let cluster; let mock; -- cgit v1.2.1 From 4cbca33862bc221bb0f3807d0b7c204e36a13a67 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 11 Apr 2019 15:08:53 +0200 Subject: Increase timeout for notes_spec.js --- spec/javascripts/notes_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 3d2c617e479..394e3343be6 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -670,7 +670,7 @@ describe('Notes', function() { done(); }) .catch(done.fail); - }); + }, 2000); }); describe('postComment with Slash commands', () => { -- cgit v1.2.1 From b27b8dc0c2e38689f519198ea127b60437e13983 Mon Sep 17 00:00:00 2001 From: Willian Balmant Date: Thu, 11 Apr 2019 16:09:03 +0000 Subject: Use strip to remove leading/trailing spaces Change based on comments in MR #27025 --- spec/lib/banzai/filter/table_of_contents_filter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb index f776e21a89e..4a9880ac85a 100644 --- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb +++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb @@ -59,7 +59,7 @@ describe Banzai::Filter::TableOfContentsFilter do end it 'removes any leading or trailing spaces' do - doc = filter(header(1, " \r\n \t Title with spaces \r\n\t ")) + doc = filter(header(1, " \r\n\tTitle with spaces\r\n\t ")) expect(doc.css('h1 a').first.attr('href')).to eq '#title-with-spaces' end -- cgit v1.2.1 From 2c98d6eef193c3562dc3b36529d434cc9eb6e8f2 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Mon, 8 Apr 2019 14:02:22 -0500 Subject: Allow arrays to specify fields graphql_query_for --- spec/requests/api/graphql/gitlab_schema_spec.rb | 2 +- spec/support/helpers/graphql_helpers.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index f95f460fd14..b63b4fb34df 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -5,7 +5,7 @@ describe 'GitlabSchema configurations' do it 'shows an error if complexity is too high' do project = create(:project, :repository) - query = graphql_query_for('project', { 'fullPath' => project.full_path }, "id\nname\ndescription") + query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index f59f42ee902..2f4e6e4c934 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -83,7 +83,8 @@ module GraphqlHelpers end def wrap_fields(fields) - return unless fields.strip.present? + fields = Array.wrap(fields).join("\n") + return unless fields.present? <<~FIELDS { -- cgit v1.2.1 From b7f7638310e8a0069ad5f5038ba82b7aeddc2b07 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 14 Mar 2019 20:52:01 +0000 Subject: Improve project settings page Prioritize and simplify project settings content. --- .../settings/user_renames_a_project_spec.rb | 37 ++++++++++++++-------- spec/features/projects_spec.rb | 15 +++++++++ .../dirty_submit_form_shared_examples.rb | 14 ++++---- 3 files changed, 44 insertions(+), 22 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb index 64c9af4b706..d3979b79910 100644 --- a/spec/features/projects/settings/user_renames_a_project_spec.rb +++ b/spec/features/projects/settings/user_renames_a_project_spec.rb @@ -9,24 +9,33 @@ describe 'Projects > Settings > User renames a project' do visit edit_project_path(project) end - def rename_project(project, name: nil, path: nil) - fill_in('project_name', with: name) if name - fill_in('Path', with: path) if path - click_button('Rename project') + def change_path(project, path) + within('.advanced-settings') do + fill_in('Path', with: path) + click_button('Change path') + end + project.reload wait_for_edit_project_page_reload + end + + def change_name(project, name) + within('.general-settings') do + fill_in('Project name', with: name) + click_button('Save changes') + end project.reload + wait_for_edit_project_page_reload end def wait_for_edit_project_page_reload - expect(find('.project-edit-container')).to have_content('Rename repository') + expect(find('.advanced-settings')).to have_content('Change path') end context 'with invalid characters' do - it 'shows errors for invalid project path/name' do - rename_project(project, name: 'foo&bar', path: 'foo&bar') - expect(page).to have_field 'Project name', with: 'foo&bar' + it 'shows errors for invalid project path' do + change_path(project, 'foo&bar') + expect(page).to have_field 'Path', with: 'foo&bar' - expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'" end end @@ -42,13 +51,13 @@ describe 'Projects > Settings > User renames a project' do context 'when changing project name' do it 'renames the repository' do - rename_project(project, name: 'bar') + change_name(project, 'bar') expect(find('.breadcrumbs')).to have_content(project.name) end context 'with emojis' do it 'shows error for invalid project name' do - rename_project(project, name: '🚀 foo bar ☁️') + change_name(project, '🚀 foo bar ☁️') expect(page).to have_field 'Project name', with: '🚀 foo bar ☁️' expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'." end @@ -67,7 +76,7 @@ describe 'Projects > Settings > User renames a project' do end it 'the project is accessible via the new path' do - rename_project(project, path: 'bar') + change_path(project, 'bar') new_path = namespace_project_path(project.namespace, 'bar') visit new_path @@ -77,7 +86,7 @@ describe 'Projects > Settings > User renames a project' do it 'the project is accessible via a redirect from the old path' do old_path = project_path(project) - rename_project(project, path: 'bar') + change_path(project, 'bar') new_path = namespace_project_path(project.namespace, 'bar') visit old_path @@ -88,7 +97,7 @@ describe 'Projects > Settings > User renames a project' do context 'and a new project is added with the same path' do it 'overrides the redirect' do old_path = project_path(project) - rename_project(project, path: 'bar') + change_path(project, 'bar') new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz') visit old_path diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index dbf0d427976..ff4e6197746 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -373,6 +373,21 @@ describe 'Project' do end end + describe 'edit' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:path) { edit_project_path(project) } + + before do + project.add_maintainer(user) + sign_in(user) + visit path + end + + it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' }, + { form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }] + end + def remove_with_confirm(button_text, confirm_with) click_button button_text fill_in 'confirm_name_input', with: confirm_with diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb index 52a2ee49495..4e45e2921e7 100644 --- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb +++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb @@ -1,18 +1,17 @@ shared_examples 'dirty submit form' do |selector_args| selectors = selector_args.is_a?(Array) ? selector_args : [selector_args] - def expect_disabled_state(form, submit, is_disabled = true) + def expect_disabled_state(form, submit_selector, is_disabled = true) disabled_selector = is_disabled == true ? '[disabled]' : ':not([disabled])' - form.find(".js-dirty-submit#{disabled_selector}", match: :first) - - expect(submit.disabled?).to be is_disabled + form.find("#{submit_selector}#{disabled_selector}") end selectors.each do |selector| it "disables #{selector[:form]} submit until there are changes on #{selector[:input]}", :js do form = find(selector[:form]) - submit = form.first('.js-dirty-submit') + submit_selector = selector[:submit] || 'input[type="submit"]' + submit = form.first(submit_selector) input = form.first(selector[:input]) is_radio = input[:type] == 'radio' is_checkbox = input[:type] == 'checkbox' @@ -22,15 +21,14 @@ shared_examples 'dirty submit form' do |selector_args| original_checkable = input if is_checkbox expect(submit.disabled?).to be true - expect(input.checked?).to be false is_checkable ? input.click : input.set("#{original_value} changes") - expect_disabled_state(form, submit, false) + expect_disabled_state(form, submit_selector, false) is_checkable ? original_checkable.click : input.set(original_value) - expect_disabled_state(form, submit) + expect_disabled_state(form, submit_selector) end end end -- cgit v1.2.1 From e68f94cda77d4039a15c74dd94a528966cef0557 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Tue, 2 Apr 2019 20:10:49 +0300 Subject: Rename selector for framework notes --- spec/features/projects/commit/comments/user_adds_comment_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb index 29442a58ea4..586e2e33112 100644 --- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb +++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb @@ -138,7 +138,7 @@ describe "User adds a comment on a commit", :js do click_button("Comment") end - page.within(".diff-file:nth-of-type(1) .notes_content.parallel.old") do + page.within(".diff-file:nth-of-type(1) .notes-content.parallel.old") do expect(page).to have_content(old_comment) end @@ -152,7 +152,7 @@ describe "User adds a comment on a commit", :js do wait_for_requests - expect(all(".diff-file:nth-of-type(1) .notes_content.parallel.new")[1].text).to have_content(new_comment) + expect(all(".diff-file:nth-of-type(1) .notes-content.parallel.new")[1].text).to have_content(new_comment) end end -- cgit v1.2.1 From 85b29c1c2fa3b94d7371cf454c485457a0756cb1 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 12 Apr 2019 00:17:24 +1200 Subject: Add frozen_string_literal to spec/services Probably useful as we often move these files to "new" files. --- spec/services/access_token_validation_service_spec.rb | 2 ++ spec/services/application_settings/update_service_spec.rb | 2 ++ spec/services/auth/container_registry_authentication_service_spec.rb | 2 ++ spec/services/base_count_service_spec.rb | 2 ++ spec/services/boards/create_service_spec.rb | 2 ++ spec/services/boards/issues/create_service_spec.rb | 2 ++ spec/services/boards/issues/list_service_spec.rb | 2 ++ spec/services/boards/issues/move_service_spec.rb | 2 ++ spec/services/boards/list_service_spec.rb | 2 ++ spec/services/boards/lists/create_service_spec.rb | 2 ++ spec/services/boards/lists/destroy_service_spec.rb | 2 ++ spec/services/boards/lists/generate_service_spec.rb | 2 ++ spec/services/boards/lists/list_service_spec.rb | 2 ++ spec/services/boards/lists/move_service_spec.rb | 2 ++ spec/services/chat_names/authorize_user_service_spec.rb | 2 ++ spec/services/chat_names/find_user_service_spec.rb | 2 ++ spec/services/ci/archive_trace_service_spec.rb | 2 ++ spec/services/ci/compare_test_reports_service_spec.rb | 2 ++ spec/services/ci/create_pipeline_service_spec.rb | 2 ++ spec/services/ci/destroy_expired_job_artifacts_service_spec.rb | 2 ++ spec/services/ci/ensure_stage_service_spec.rb | 2 ++ spec/services/ci/extract_sections_from_build_trace_service_spec.rb | 2 ++ spec/services/ci/pipeline_trigger_service_spec.rb | 2 ++ spec/services/ci/play_build_service_spec.rb | 2 ++ spec/services/ci/process_pipeline_service_spec.rb | 2 ++ spec/services/ci/register_job_service_spec.rb | 2 ++ spec/services/ci/retry_build_service_spec.rb | 2 ++ spec/services/ci/retry_pipeline_service_spec.rb | 2 ++ spec/services/ci/run_scheduled_build_service_spec.rb | 2 ++ spec/services/ci/stop_environments_service_spec.rb | 2 ++ spec/services/ci/update_build_queue_service_spec.rb | 2 ++ spec/services/ci/update_runner_service_spec.rb | 2 ++ .../clusters/applications/check_ingress_ip_address_service_spec.rb | 2 ++ .../clusters/applications/check_installation_progress_service_spec.rb | 2 ++ spec/services/clusters/applications/install_service_spec.rb | 2 ++ spec/services/clusters/create_service_spec.rb | 2 ++ spec/services/clusters/gcp/fetch_operation_service_spec.rb | 2 ++ spec/services/clusters/gcp/provision_service_spec.rb | 2 ++ spec/services/clusters/gcp/verify_provision_status_service_spec.rb | 2 ++ spec/services/clusters/update_service_spec.rb | 2 ++ spec/services/cohorts_service_spec.rb | 2 ++ spec/services/compare_service_spec.rb | 2 ++ spec/services/create_branch_service_spec.rb | 2 ++ spec/services/create_snippet_service_spec.rb | 2 ++ spec/services/delete_branch_service_spec.rb | 2 ++ spec/services/delete_merged_branches_service_spec.rb | 2 ++ spec/services/deploy_keys/create_service_spec.rb | 2 ++ spec/services/deploy_tokens/create_service_spec.rb | 2 ++ spec/services/discussions/resolve_service_spec.rb | 2 ++ spec/services/discussions/update_diff_position_service_spec.rb | 2 ++ spec/services/emails/confirm_service_spec.rb | 2 ++ spec/services/event_create_service_spec.rb | 2 ++ spec/services/events/render_service_spec.rb | 2 ++ spec/services/files/create_service_spec.rb | 2 ++ spec/services/files/delete_service_spec.rb | 2 ++ spec/services/files/multi_service_spec.rb | 2 ++ spec/services/files/update_service_spec.rb | 2 ++ spec/services/git/branch_hooks_service_spec.rb | 2 ++ spec/services/git/branch_push_service_spec.rb | 2 ++ spec/services/git/tag_hooks_service_spec.rb | 2 ++ spec/services/git/tag_push_service_spec.rb | 2 ++ spec/services/gpg_keys/create_service_spec.rb | 2 ++ spec/services/gravatar_service_spec.rb | 2 ++ spec/services/groups/create_service_spec.rb | 2 ++ spec/services/groups/destroy_service_spec.rb | 2 ++ spec/services/groups/nested_create_service_spec.rb | 2 ++ spec/services/groups/transfer_service_spec.rb | 2 ++ spec/services/groups/update_service_spec.rb | 2 ++ spec/services/import_export_clean_up_service_spec.rb | 2 ++ spec/services/issuable/bulk_update_service_spec.rb | 2 ++ spec/services/issuable/common_system_notes_service_spec.rb | 2 ++ spec/services/issuable/destroy_service_spec.rb | 2 ++ spec/services/issues/build_service_spec.rb | 2 ++ spec/services/issues/close_service_spec.rb | 2 ++ spec/services/issues/create_service_spec.rb | 2 ++ spec/services/issues/duplicate_service_spec.rb | 2 ++ spec/services/issues/move_service_spec.rb | 2 ++ spec/services/issues/related_branches_service_spec.rb | 2 ++ spec/services/issues/reopen_service_spec.rb | 2 ++ spec/services/issues/resolve_discussions_spec.rb | 2 ++ spec/services/issues/update_service_spec.rb | 2 ++ spec/services/keys/create_service_spec.rb | 2 ++ spec/services/keys/destroy_service_spec.rb | 2 ++ spec/services/keys/last_used_service_spec.rb | 2 ++ spec/services/labels/create_service_spec.rb | 2 ++ spec/services/labels/find_or_create_service_spec.rb | 2 ++ spec/services/labels/promote_service_spec.rb | 2 ++ spec/services/labels/transfer_service_spec.rb | 2 ++ spec/services/labels/update_service_spec.rb | 2 ++ spec/services/lfs/file_transformer_spec.rb | 2 ++ spec/services/lfs/lock_file_service_spec.rb | 2 ++ spec/services/lfs/locks_finder_service_spec.rb | 2 ++ spec/services/lfs/unlock_file_service_spec.rb | 2 ++ spec/services/members/approve_access_request_service_spec.rb | 2 ++ spec/services/members/create_service_spec.rb | 2 ++ spec/services/members/destroy_service_spec.rb | 2 ++ spec/services/members/request_access_service_spec.rb | 2 ++ spec/services/members/update_service_spec.rb | 2 ++ spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb | 2 ++ spec/services/merge_requests/assign_issues_service_spec.rb | 2 ++ spec/services/merge_requests/build_service_spec.rb | 2 ++ spec/services/merge_requests/close_service_spec.rb | 2 ++ spec/services/merge_requests/conflicts/list_service_spec.rb | 2 ++ spec/services/merge_requests/conflicts/resolve_service_spec.rb | 2 ++ spec/services/merge_requests/create_from_issue_service_spec.rb | 2 ++ spec/services/merge_requests/create_service_spec.rb | 2 ++ spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb | 2 ++ spec/services/merge_requests/ff_merge_service_spec.rb | 2 ++ spec/services/merge_requests/get_urls_service_spec.rb | 2 ++ spec/services/merge_requests/merge_service_spec.rb | 2 ++ .../merge_requests/merge_when_pipeline_succeeds_service_spec.rb | 2 ++ spec/services/merge_requests/post_merge_service_spec.rb | 2 ++ spec/services/merge_requests/rebase_service_spec.rb | 2 ++ spec/services/merge_requests/refresh_service_spec.rb | 2 ++ spec/services/merge_requests/reload_diffs_service_spec.rb | 2 ++ spec/services/merge_requests/reopen_service_spec.rb | 2 ++ .../merge_requests/resolved_discussion_notification_service_spec.rb | 2 ++ spec/services/merge_requests/squash_service_spec.rb | 2 ++ spec/services/merge_requests/update_service_spec.rb | 2 ++ spec/services/milestones/close_service_spec.rb | 2 ++ spec/services/milestones/create_service_spec.rb | 2 ++ spec/services/milestones/destroy_service_spec.rb | 2 ++ spec/services/milestones/promote_service_spec.rb | 2 ++ spec/services/note_summary_spec.rb | 2 ++ spec/services/notes/build_service_spec.rb | 2 ++ spec/services/notes/create_service_spec.rb | 2 ++ spec/services/notes/destroy_service_spec.rb | 2 ++ spec/services/notes/post_process_service_spec.rb | 2 ++ spec/services/notes/quick_actions_service_spec.rb | 2 ++ spec/services/notes/render_service_spec.rb | 2 ++ spec/services/notes/resolve_service_spec.rb | 2 ++ spec/services/notes/update_service_spec.rb | 2 ++ spec/services/notification_recipient_service_spec.rb | 2 ++ spec/services/notification_service_spec.rb | 2 ++ spec/services/preview_markdown_service_spec.rb | 2 ++ spec/services/projects/after_import_service_spec.rb | 2 ++ spec/services/projects/autocomplete_service_spec.rb | 2 ++ spec/services/projects/batch_open_issues_count_service_spec.rb | 2 ++ spec/services/projects/cleanup_service_spec.rb | 2 ++ spec/services/projects/count_service_spec.rb | 2 ++ spec/services/projects/create_from_template_service_spec.rb | 2 ++ spec/services/projects/create_service_spec.rb | 2 ++ spec/services/projects/destroy_service_spec.rb | 2 ++ spec/services/projects/detect_repository_languages_service_spec.rb | 2 ++ spec/services/projects/download_service_spec.rb | 2 ++ spec/services/projects/enable_deploy_key_service_spec.rb | 2 ++ spec/services/projects/fork_service_spec.rb | 2 ++ spec/services/projects/forks_count_service_spec.rb | 2 ++ spec/services/projects/gitlab_projects_import_service_spec.rb | 2 ++ spec/services/projects/group_links/create_service_spec.rb | 2 ++ spec/services/projects/group_links/destroy_service_spec.rb | 2 ++ .../projects/hashed_storage/migrate_attachments_service_spec.rb | 2 ++ .../services/projects/hashed_storage/migrate_repository_service_spec.rb | 2 ++ spec/services/projects/hashed_storage/migration_service_spec.rb | 2 ++ spec/services/projects/housekeeping_service_spec.rb | 2 ++ spec/services/projects/import_export/export_service_spec.rb | 2 ++ spec/services/projects/import_service_spec.rb | 2 ++ .../projects/lfs_pointers/lfs_download_link_list_service_spec.rb | 2 ++ spec/services/projects/lfs_pointers/lfs_download_service_spec.rb | 2 ++ spec/services/projects/lfs_pointers/lfs_import_service_spec.rb | 2 ++ spec/services/projects/lfs_pointers/lfs_link_service_spec.rb | 2 ++ spec/services/projects/move_access_service_spec.rb | 2 ++ spec/services/projects/move_deploy_keys_projects_service_spec.rb | 2 ++ spec/services/projects/move_forks_service_spec.rb | 2 ++ spec/services/projects/move_lfs_objects_projects_service_spec.rb | 2 ++ spec/services/projects/move_notification_settings_service_spec.rb | 2 ++ spec/services/projects/move_project_authorizations_service_spec.rb | 2 ++ spec/services/projects/move_project_group_links_service_spec.rb | 2 ++ spec/services/projects/move_project_members_service_spec.rb | 2 ++ spec/services/projects/move_users_star_projects_service_spec.rb | 2 ++ spec/services/projects/open_issues_count_service_spec.rb | 2 ++ spec/services/projects/open_merge_requests_count_service_spec.rb | 2 ++ spec/services/projects/overwrite_project_service_spec.rb | 2 ++ spec/services/projects/participants_service_spec.rb | 2 ++ spec/services/projects/propagate_service_template_spec.rb | 2 ++ spec/services/projects/repository_languages_service_spec.rb | 2 ++ spec/services/projects/transfer_service_spec.rb | 2 ++ spec/services/projects/unlink_fork_service_spec.rb | 2 ++ spec/services/projects/update_pages_configuration_service_spec.rb | 2 ++ spec/services/projects/update_pages_service_spec.rb | 2 ++ spec/services/projects/update_remote_mirror_service_spec.rb | 2 ++ spec/services/projects/update_service_spec.rb | 2 ++ spec/services/projects/update_statistics_service_spec.rb | 2 ++ spec/services/prometheus/adapter_service_spec.rb | 2 ++ spec/services/protected_branches/create_service_spec.rb | 2 ++ spec/services/protected_branches/destroy_service_spec.rb | 2 ++ spec/services/protected_branches/update_service_spec.rb | 2 ++ spec/services/protected_tags/create_service_spec.rb | 2 ++ spec/services/protected_tags/destroy_service_spec.rb | 2 ++ spec/services/protected_tags/update_service_spec.rb | 2 ++ spec/services/push_event_payload_service_spec.rb | 2 ++ spec/services/releases/create_service_spec.rb | 2 ++ spec/services/releases/destroy_service_spec.rb | 2 ++ spec/services/releases/update_service_spec.rb | 2 ++ spec/services/repair_ldap_blocked_user_service_spec.rb | 2 ++ spec/services/repository_archive_clean_up_service_spec.rb | 2 ++ spec/services/reset_project_cache_service_spec.rb | 2 ++ spec/services/search/global_service_spec.rb | 2 ++ spec/services/search/group_service_spec.rb | 2 ++ spec/services/search/snippet_service_spec.rb | 2 ++ spec/services/search_service_spec.rb | 2 ++ spec/services/spam_service_spec.rb | 2 ++ spec/services/submit_usage_ping_service_spec.rb | 2 ++ spec/services/system_hooks_service_spec.rb | 2 ++ spec/services/tags/create_service_spec.rb | 2 ++ spec/services/tags/destroy_service_spec.rb | 2 ++ spec/services/test_hooks/project_service_spec.rb | 2 ++ spec/services/test_hooks/system_service_spec.rb | 2 ++ spec/services/todo_service_spec.rb | 2 ++ spec/services/todos/destroy/confidential_issue_service_spec.rb | 2 ++ spec/services/todos/destroy/entity_leave_service_spec.rb | 2 ++ spec/services/todos/destroy/group_private_service_spec.rb | 2 ++ spec/services/todos/destroy/private_features_service_spec.rb | 2 ++ spec/services/todos/destroy/project_private_service_spec.rb | 2 ++ spec/services/update_deployment_service_spec.rb | 2 ++ spec/services/update_merge_request_metrics_service_spec.rb | 2 ++ spec/services/update_snippet_service_spec.rb | 2 ++ spec/services/upload_service_spec.rb | 2 ++ spec/services/user_project_access_changed_service_spec.rb | 2 ++ spec/services/users/activity_service_spec.rb | 2 ++ spec/services/users/build_service_spec.rb | 2 ++ spec/services/users/create_service_spec.rb | 2 ++ spec/services/users/destroy_service_spec.rb | 2 ++ spec/services/users/last_push_event_service_spec.rb | 2 ++ spec/services/users/migrate_to_ghost_user_service_spec.rb | 2 ++ spec/services/users/refresh_authorized_projects_service_spec.rb | 2 ++ spec/services/users/respond_to_terms_service_spec.rb | 2 ++ spec/services/users/update_service_spec.rb | 2 ++ spec/services/verify_pages_domain_service_spec.rb | 2 ++ spec/services/web_hook_service_spec.rb | 2 ++ spec/services/wiki_pages/create_service_spec.rb | 2 ++ spec/services/wiki_pages/destroy_service_spec.rb | 2 ++ spec/services/wiki_pages/update_service_spec.rb | 2 ++ 233 files changed, 466 insertions(+) (limited to 'spec') diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb index 38a3f522504..b2a8da6c4c6 100644 --- a/spec/services/access_token_validation_service_spec.rb +++ b/spec/services/access_token_validation_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AccessTokenValidationService do diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index 258e5635113..a641828faa5 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ApplicationSettings::UpdateService do diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index c9d85e96750..e24fe60f059 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Auth::ContainerRegistryAuthenticationService do diff --git a/spec/services/base_count_service_spec.rb b/spec/services/base_count_service_spec.rb index 090b2dcdd43..275bec9982d 100644 --- a/spec/services/base_count_service_spec.rb +++ b/spec/services/base_count_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BaseCountService, :use_clean_rails_memory_store_caching do diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index a715261cd6c..7d4fb04c6c0 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::CreateService do diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb index f0179e35652..33637419f83 100644 --- a/spec/services/boards/issues/create_service_spec.rb +++ b/spec/services/boards/issues/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Issues::CreateService do diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index aaad29536af..40878e24cb4 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Issues::ListService do diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 6020f0771e5..16e2a2fba6b 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Issues::MoveService do diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb index 7518e9e9b75..c9d372ea166 100644 --- a/spec/services/boards/list_service_spec.rb +++ b/spec/services/boards/list_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::ListService do diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index 7d3f5f86deb..295ec2c8156 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Lists::CreateService do diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index 3c4eb6b3fc5..b936ef3837f 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Lists::DestroyService do diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 82dbd1ee744..77b42392470 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Lists::GenerateService do diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index 24e04eed642..2ebfd295fa2 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Lists::ListService do diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index 16dfb2ae6af..f8fc70ef2d6 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Boards::Lists::MoveService do diff --git a/spec/services/chat_names/authorize_user_service_spec.rb b/spec/services/chat_names/authorize_user_service_spec.rb index d88b2504133..41cbac4e8e9 100644 --- a/spec/services/chat_names/authorize_user_service_spec.rb +++ b/spec/services/chat_names/authorize_user_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatNames::AuthorizeUserService do diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb index 5734b10109a..9d26f98cd56 100644 --- a/spec/services/chat_names/find_user_service_spec.rb +++ b/spec/services/chat_names/find_user_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb index 8e9cb65f3bc..44a77c29086 100644 --- a/spec/services/ci/archive_trace_service_spec.rb +++ b/spec/services/ci/archive_trace_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::ArchiveTraceService, '#execute' do diff --git a/spec/services/ci/compare_test_reports_service_spec.rb b/spec/services/ci/compare_test_reports_service_spec.rb index a26c970a8f0..f5edd3a552d 100644 --- a/spec/services/ci/compare_test_reports_service_spec.rb +++ b/spec/services/ci/compare_test_reports_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::CompareTestReportsService do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 101b91e9cd8..6f8a76e9d56 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::CreatePipelineService do diff --git a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb index 80d82ba3ac9..fc5450ab33d 100644 --- a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb +++ b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state do diff --git a/spec/services/ci/ensure_stage_service_spec.rb b/spec/services/ci/ensure_stage_service_spec.rb index d17e30763d7..43bbd2130a4 100644 --- a/spec/services/ci/ensure_stage_service_spec.rb +++ b/spec/services/ci/ensure_stage_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::EnsureStageService, '#execute' do diff --git a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb index 28f2fa7903a..03c67c611fe 100644 --- a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb +++ b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::ExtractSectionsFromBuildTraceService, '#execute' do diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb index f4ff818c479..76251b5b557 100644 --- a/spec/services/ci/pipeline_trigger_service_spec.rb +++ b/spec/services/ci/pipeline_trigger_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PipelineTriggerService do diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb index 330ec81e87d..634f865e2d8 100644 --- a/spec/services/ci/play_build_service_spec.rb +++ b/spec/services/ci/play_build_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PlayBuildService, '#execute' do diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 6674d89518e..cadb519ccee 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::ProcessPipelineService, '#execute' do diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 20181387612..874945c8585 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Ci diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 17e2b17a499..fc630b7098a 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::RetryBuildService do diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 75042b29bea..e42de3cd48f 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::RetryPipelineService, '#execute' do diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb index be2aad33ef4..ab8d9f4ba2e 100644 --- a/spec/services/ci/run_scheduled_build_service_spec.rb +++ b/spec/services/ci/run_scheduled_build_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::RunScheduledBuildService do diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb index cdd3d851f61..31b8d540356 100644 --- a/spec/services/ci/stop_environments_service_spec.rb +++ b/spec/services/ci/stop_environments_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::StopEnvironmentsService do diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb index ca0c6be5da6..4b869385128 100644 --- a/spec/services/ci/update_build_queue_service_spec.rb +++ b/spec/services/ci/update_build_queue_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::UpdateBuildQueueService do diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb index 7cc04c92d27..2b07dad7248 100644 --- a/spec/services/ci/update_runner_service_spec.rb +++ b/spec/services/ci/update_runner_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::UpdateRunnerService do diff --git a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb index 80fc48d1b07..84bca76e69b 100644 --- a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb +++ b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Applications::CheckIngressIpAddressService do diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb index 1cca89d31d7..8ad90aaf720 100644 --- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Applications::CheckInstallationProgressService, '#execute' do diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb index db0c33a95b2..494c5892210 100644 --- a/spec/services/clusters/applications/install_service_spec.rb +++ b/spec/services/clusters/applications/install_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Applications::InstallService do diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb index 274880f2c49..ecf0a9c9dce 100644 --- a/spec/services/clusters/create_service_spec.rb +++ b/spec/services/clusters/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::CreateService do diff --git a/spec/services/clusters/gcp/fetch_operation_service_spec.rb b/spec/services/clusters/gcp/fetch_operation_service_spec.rb index 55f123ee786..23da8004a7d 100644 --- a/spec/services/clusters/gcp/fetch_operation_service_spec.rb +++ b/spec/services/clusters/gcp/fetch_operation_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Gcp::FetchOperationService do diff --git a/spec/services/clusters/gcp/provision_service_spec.rb b/spec/services/clusters/gcp/provision_service_spec.rb index c0bdac40938..dfd15690a1f 100644 --- a/spec/services/clusters/gcp/provision_service_spec.rb +++ b/spec/services/clusters/gcp/provision_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Gcp::ProvisionService do diff --git a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb index 2ee2fa51f63..9611b2080ba 100644 --- a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb +++ b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Gcp::VerifyProvisionStatusService do diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index b2e6ebecd4a..21b37f88fd8 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::UpdateService do diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb index 77595d7ba2d..2c012f080dd 100644 --- a/spec/services/cohorts_service_spec.rb +++ b/spec/services/cohorts_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CohortsService do diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb index 9e15eae8c13..0e4ef69ec19 100644 --- a/spec/services/compare_service_spec.rb +++ b/spec/services/compare_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CompareService do diff --git a/spec/services/create_branch_service_spec.rb b/spec/services/create_branch_service_spec.rb index 38096a080a7..0d34c7f9a82 100644 --- a/spec/services/create_branch_service_spec.rb +++ b/spec/services/create_branch_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreateBranchService do diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb index b6ab6b8271c..f6b6989b955 100644 --- a/spec/services/create_snippet_service_spec.rb +++ b/spec/services/create_snippet_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreateSnippetService do diff --git a/spec/services/delete_branch_service_spec.rb b/spec/services/delete_branch_service_spec.rb index 9c9fba030e7..43a70d33d2d 100644 --- a/spec/services/delete_branch_service_spec.rb +++ b/spec/services/delete_branch_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteBranchService do diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb index 0de02576203..dffc2bd93ee 100644 --- a/spec/services/delete_merged_branches_service_spec.rb +++ b/spec/services/delete_merged_branches_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteMergedBranchesService do diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb index 7a604c0cadd..a55f1561194 100644 --- a/spec/services/deploy_keys/create_service_spec.rb +++ b/spec/services/deploy_keys/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployKeys::CreateService do diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb index 1bd0356a73b..886ffd4593d 100644 --- a/spec/services/deploy_tokens/create_service_spec.rb +++ b/spec/services/deploy_tokens/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployTokens::CreateService do diff --git a/spec/services/discussions/resolve_service_spec.rb b/spec/services/discussions/resolve_service_spec.rb index 4e0d4749239..5b99430cb75 100644 --- a/spec/services/discussions/resolve_service_spec.rb +++ b/spec/services/discussions/resolve_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Discussions::ResolveService do diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb index 2b84206318f..60ec83e9062 100644 --- a/spec/services/discussions/update_diff_position_service_spec.rb +++ b/spec/services/discussions/update_diff_position_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Discussions::UpdateDiffPositionService do diff --git a/spec/services/emails/confirm_service_spec.rb b/spec/services/emails/confirm_service_spec.rb index 2b2c31e2521..6a274ca9dfe 100644 --- a/spec/services/emails/confirm_service_spec.rb +++ b/spec/services/emails/confirm_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Emails::ConfirmService do diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 443665c9959..9f2c3fec62c 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EventCreateService do diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb index 075cb45e46c..a623a05a56d 100644 --- a/spec/services/events/render_service_spec.rb +++ b/spec/services/events/render_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Events::RenderService do diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb index 751b7160276..195f56a2909 100644 --- a/spec/services/files/create_service_spec.rb +++ b/spec/services/files/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Files::CreateService do diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb index 309802ce733..b849def06fc 100644 --- a/spec/services/files/delete_service_spec.rb +++ b/spec/services/files/delete_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Files::DeleteService do diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb index 6842fa9f435..0f51c72019e 100644 --- a/spec/services/files/multi_service_spec.rb +++ b/spec/services/files/multi_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Files::MultiService do diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb index 23db35c2418..37869b176ef 100644 --- a/spec/services/files/update_service_spec.rb +++ b/spec/services/files/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Files::UpdateService do diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb index bb87267db7d..4895e762602 100644 --- a/spec/services/git/branch_hooks_service_spec.rb +++ b/spec/services/git/branch_hooks_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Git::BranchHooksService do diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index 322e40a8112..ad21f710833 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Git::BranchPushService, services: true do diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb index 8f91ce3b4c5..f4c02932f98 100644 --- a/spec/services/git/tag_hooks_service_spec.rb +++ b/spec/services/git/tag_hooks_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Git::TagHooksService, :service do diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb index 5e89a912060..418952b52da 100644 --- a/spec/services/git/tag_push_service_spec.rb +++ b/spec/services/git/tag_push_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Git::TagPushService do diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb index 1cd2625531e..8dfc9f19439 100644 --- a/spec/services/gpg_keys/create_service_spec.rb +++ b/spec/services/gpg_keys/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GpgKeys::CreateService do diff --git a/spec/services/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb index d2cc53fe0ee..9ce1df0f76f 100644 --- a/spec/services/gravatar_service_spec.rb +++ b/spec/services/gravatar_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GravatarService do diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index fe6a8691ae0..54836c9adbf 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Groups::CreateService, '#execute' do diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index 98114ea4919..1ab2e994b7e 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Groups::DestroyService do diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb index 75d6ddb0a2c..13acf9e055b 100644 --- a/spec/services/groups/nested_create_service_spec.rb +++ b/spec/services/groups/nested_create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Groups::NestedCreateService do diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index 0bc67dbb4a1..b5708ebba76 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Groups::TransferService, :postgresql do diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index d87a7dd234d..d081c20f669 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Groups::UpdateService do diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb index d5fcef1246f..51720e786dc 100644 --- a/spec/services/import_export_clean_up_service_spec.rb +++ b/spec/services/import_export_clean_up_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ImportExportCleanUpService do diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index 363b7266940..aebc5ba2874 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issuable::BulkUpdateService do diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb index 0edc9016c96..7e40ac9ff4d 100644 --- a/spec/services/issuable/common_system_notes_service_spec.rb +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issuable::CommonSystemNotesService do diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb index 15d1bb73ca3..dd6a966c145 100644 --- a/spec/services/issuable/destroy_service_spec.rb +++ b/spec/services/issuable/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issuable::DestroyService do diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 74f1e83b362..140b78f9b7a 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper.rb' describe Issues::BuildService do diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 5e38d0aeb6a..fce9eed8b08 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issues::CloseService do diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index d50412b6d2c..b7bedc2f97e 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issues::CreateService do diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb index 089e77cc88b..2c9581b993c 100644 --- a/spec/services/issues/duplicate_service_spec.rb +++ b/spec/services/issues/duplicate_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issues::DuplicateService do diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 1e088bc7d9b..7483688d631 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issues::MoveService do diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb index c2e1eba6a63..eae35f12560 100644 --- a/spec/services/issues/related_branches_service_spec.rb +++ b/spec/services/issues/related_branches_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issues::RelatedBranchesService do diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 2a56075419b..f04029e64aa 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issues::ReopenService do diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb index b6cfc09da65..f12a3820b8d 100644 --- a/spec/services/issues/resolve_discussions_spec.rb +++ b/spec/services/issues/resolve_discussions_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper.rb' describe Issues::ResolveDiscussions do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 1c8a4b608d5..22f5607cb9c 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -1,4 +1,6 @@ # coding: utf-8 +# frozen_string_literal: true + require 'spec_helper' describe Issues::UpdateService, :mailer do diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb index bcb436c1e46..1f8b402cf08 100644 --- a/spec/services/keys/create_service_spec.rb +++ b/spec/services/keys/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Keys::CreateService do diff --git a/spec/services/keys/destroy_service_spec.rb b/spec/services/keys/destroy_service_spec.rb index 28ac72ddd42..ca4bbd50c03 100644 --- a/spec/services/keys/destroy_service_spec.rb +++ b/spec/services/keys/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Keys::DestroyService do diff --git a/spec/services/keys/last_used_service_spec.rb b/spec/services/keys/last_used_service_spec.rb index 8e553c2f1fa..c675df39f4d 100644 --- a/spec/services/keys/last_used_service_spec.rb +++ b/spec/services/keys/last_used_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Keys::LastUsedService do diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb index 438e6dbc628..f057c4401e7 100644 --- a/spec/services/labels/create_service_spec.rb +++ b/spec/services/labels/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Labels::CreateService do diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb index 7af514a5bea..438d895392b 100644 --- a/spec/services/labels/find_or_create_service_spec.rb +++ b/spec/services/labels/find_or_create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Labels::FindOrCreateService do diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb index c4c7f33e36a..d86281b751c 100644 --- a/spec/services/labels/promote_service_spec.rb +++ b/spec/services/labels/promote_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Labels::PromoteService do diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index 80bac590a11..e29c6aeef5b 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Labels::TransferService do diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb index c3fe33045fa..045e8af1135 100644 --- a/spec/services/labels/update_service_spec.rb +++ b/spec/services/labels/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Labels::UpdateService do diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb index e8938338cb7..2c6e86069f3 100644 --- a/spec/services/lfs/file_transformer_spec.rb +++ b/spec/services/lfs/file_transformer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Lfs::FileTransformer do diff --git a/spec/services/lfs/lock_file_service_spec.rb b/spec/services/lfs/lock_file_service_spec.rb index 3e58eea2501..15dbc3231a3 100644 --- a/spec/services/lfs/lock_file_service_spec.rb +++ b/spec/services/lfs/lock_file_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Lfs::LockFileService do diff --git a/spec/services/lfs/locks_finder_service_spec.rb b/spec/services/lfs/locks_finder_service_spec.rb index e409b77babf..0fc2c593d94 100644 --- a/spec/services/lfs/locks_finder_service_spec.rb +++ b/spec/services/lfs/locks_finder_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Lfs::LocksFinderService do diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb index fe42ca41633..8e36617c0d6 100644 --- a/spec/services/lfs/unlock_file_service_spec.rb +++ b/spec/services/lfs/unlock_file_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Lfs::UnlockFileService do diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb index 5c30f5b6a61..f56c31e51f6 100644 --- a/spec/services/members/approve_access_request_service_spec.rb +++ b/spec/services/members/approve_access_request_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Members::ApproveAccessRequestService do diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index 3bc05182932..2c4fb131ed9 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Members::CreateService do diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 91bf4dccd77..52f9a305d8f 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Members::DestroyService do diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb index e93ba5a85c0..2e5275eb3f2 100644 --- a/spec/services/members/request_access_service_spec.rb +++ b/spec/services/members/request_access_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Members::RequestAccessService do diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb index 599ed39ca37..a8b28127df2 100644 --- a/spec/services/members/update_service_spec.rb +++ b/spec/services/members/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Members::UpdateService do diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index 39a2ef579dd..f26b67f902d 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::AddTodoWhenBuildFailsService do diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb index bda6383a346..c0b57b9092d 100644 --- a/spec/services/merge_requests/assign_issues_service_spec.rb +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::AssignIssuesService do diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 057e8137a4e..5c3b209086c 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::BuildService do diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 706bcea8199..aa7dfda4950 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::CloseService do diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index c81fa95e4b7..68a9c0a8b86 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::Conflicts::ListService do diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb index 7edf8a96c94..74f20094081 100644 --- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb +++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::Conflicts::ResolveService do diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb index 20bf1cbb8b6..a0ac7dba89d 100644 --- a/spec/services/merge_requests/create_from_issue_service_spec.rb +++ b/spec/services/merge_requests/create_from_issue_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::CreateFromIssueService do diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 30271e04c8e..c795176a1e4 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::CreateService do diff --git a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb index 6268c149fc6..a1c86467f34 100644 --- a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb +++ b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index a87d8b8752c..3b1096c51cb 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::FfMergeService do diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb index 274624aa8bb..0933c6d4336 100644 --- a/spec/services/merge_requests/get_urls_service_spec.rb +++ b/spec/services/merge_requests/get_urls_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe MergeRequests::GetUrlsService do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index b0b3273e3dc..2fbe5468b21 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::MergeService do diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 8b7db1b2f1f..96f61f3f103 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::MergeWhenPipelineSucceedsService do diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index 2cebefee5d6..7b87913ab8b 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::PostMergeService do diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index 427a2d63a88..0d9523a4c2c 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::RebaseService do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7063ca9ac14..d20b2d81763 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::RefreshService do diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb index 5acd01828cb..cc21348ab11 100644 --- a/spec/services/merge_requests/reload_diffs_service_spec.rb +++ b/spec/services/merge_requests/reload_diffs_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index 8b6db1ce33e..7a98437f724 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::ReopenService do diff --git a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb index e3fd906fe7b..0a10a9ee13b 100644 --- a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb +++ b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::ResolvedDiscussionNotificationService do diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb index 2713652873e..cb278eec692 100644 --- a/spec/services/merge_requests/squash_service_spec.rb +++ b/spec/services/merge_requests/squash_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::SquashService do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 15aea97ff29..ba4c9ce60f3 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequests::UpdateService, :mailer do diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index 3f7a544ea0a..55e705063b2 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestones::CloseService do diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb index 0c91112026f..97f6e947539 100644 --- a/spec/services/milestones/create_service_spec.rb +++ b/spec/services/milestones/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestones::CreateService do diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb index 9d2be30c636..3a22e4d4f92 100644 --- a/spec/services/milestones/destroy_service_spec.rb +++ b/spec/services/milestones/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestones::DestroyService do diff --git a/spec/services/milestones/promote_service_spec.rb b/spec/services/milestones/promote_service_spec.rb index df212d912e9..22c7e9dde30 100644 --- a/spec/services/milestones/promote_service_spec.rb +++ b/spec/services/milestones/promote_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestones::PromoteService do diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb index f6ee15f750c..e59731207a5 100644 --- a/spec/services/note_summary_spec.rb +++ b/spec/services/note_summary_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NoteSummary do diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb index 96fff20f7fb..984658cbd19 100644 --- a/spec/services/notes/build_service_spec.rb +++ b/spec/services/notes/build_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::BuildService do diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index bcbb8950910..494ca95f66d 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::CreateService do diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb index b1f4e87e8ea..9faf1299ef2 100644 --- a/spec/services/notes/destroy_service_spec.rb +++ b/spec/services/notes/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::DestroyService do diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb index 5aae0d711c3..99db7897664 100644 --- a/spec/services/notes/post_process_service_spec.rb +++ b/spec/services/notes/post_process_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::PostProcessService do diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 9efdf96bc64..7cb0bd41f13 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::QuickActionsService do diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb index f771620bc0d..ad69721d876 100644 --- a/spec/services/notes/render_service_spec.rb +++ b/spec/services/notes/render_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::RenderService do diff --git a/spec/services/notes/resolve_service_spec.rb b/spec/services/notes/resolve_service_spec.rb index b54d40a7a5c..3f82e1dbdc0 100644 --- a/spec/services/notes/resolve_service_spec.rb +++ b/spec/services/notes/resolve_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::ResolveService do diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index fd9bff46a06..717eb97fa5a 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Notes::UpdateService do diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb index cea5ea125b9..9c2283f555b 100644 --- a/spec/services/notification_recipient_service_spec.rb +++ b/spec/services/notification_recipient_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NotificationRecipientService do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index ac4aabf3fbd..8e34406133a 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NotificationService, :mailer do diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index a1d31464e07..f7261cd7125 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PreviewMarkdownService do diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb index 765b4ffae8f..95c11f71c5e 100644 --- a/spec/services/projects/after_import_service_spec.rb +++ b/spec/services/projects/after_import_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::AfterImportService do diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index 373fe7cb7dd..2f70c8ea94d 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::AutocompleteService do diff --git a/spec/services/projects/batch_open_issues_count_service_spec.rb b/spec/services/projects/batch_open_issues_count_service_spec.rb index 599aaf62080..e978334d68b 100644 --- a/spec/services/projects/batch_open_issues_count_service_spec.rb +++ b/spec/services/projects/batch_open_issues_count_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::BatchOpenIssuesCountService do diff --git a/spec/services/projects/cleanup_service_spec.rb b/spec/services/projects/cleanup_service_spec.rb index 3d4587ce2a1..29eabc86327 100644 --- a/spec/services/projects/cleanup_service_spec.rb +++ b/spec/services/projects/cleanup_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::CleanupService do diff --git a/spec/services/projects/count_service_spec.rb b/spec/services/projects/count_service_spec.rb index 183f6128c7b..e345b508f53 100644 --- a/spec/services/projects/count_service_spec.rb +++ b/spec/services/projects/count_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::CountService do diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb index da078dd36c6..6c244d23877 100644 --- a/spec/services/projects/create_from_template_service_spec.rb +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::CreateFromTemplateService do diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e1ec932918e..bd7a0c68766 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::CreateService, '#execute' do diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index d3a8ee46f85..3af7ee3ad50 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::DestroyService do diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb index b38bd62c9f0..e3e561c971c 100644 --- a/spec/services/projects/detect_repository_languages_service_spec.rb +++ b/spec/services/projects/detect_repository_languages_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index da236052ebf..f25233ceeb1 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::DownloadService do diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb index 835dae68fcd..64de373d7f6 100644 --- a/spec/services/projects/enable_deploy_key_service_spec.rb +++ b/spec/services/projects/enable_deploy_key_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::EnableDeployKeyService do diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 23ec29cce7b..ec3f1782e8f 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::ForkService do diff --git a/spec/services/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb index 9f8e7ee18a8..7e35648e9ff 100644 --- a/spec/services/projects/forks_count_service_spec.rb +++ b/spec/services/projects/forks_count_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::ForksCountService do diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb index b5f2c826c97..78580bfa604 100644 --- a/spec/services/projects/gitlab_projects_import_service_spec.rb +++ b/spec/services/projects/gitlab_projects_import_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::GitlabProjectsImportService do diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb index 68fd82b4cbe..6ded57f961c 100644 --- a/spec/services/projects/group_links/create_service_spec.rb +++ b/spec/services/projects/group_links/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::GroupLinks::CreateService, '#execute' do diff --git a/spec/services/projects/group_links/destroy_service_spec.rb b/spec/services/projects/group_links/destroy_service_spec.rb index 336ee01ae50..d78ab78c3d8 100644 --- a/spec/services/projects/group_links/destroy_service_spec.rb +++ b/spec/services/projects/group_links/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::GroupLinks::DestroyService, '#execute' do diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index efe15139717..32ebec318f2 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::HashedStorage::MigrateAttachmentsService do diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index 42b0d256cbf..5b778f16b5a 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::HashedStorage::MigrateRepositoryService do diff --git a/spec/services/projects/hashed_storage/migration_service_spec.rb b/spec/services/projects/hashed_storage/migration_service_spec.rb index b4647586363..e3191cd7ebc 100644 --- a/spec/services/projects/hashed_storage/migration_service_spec.rb +++ b/spec/services/projects/hashed_storage/migration_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::HashedStorage::MigrationService do diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 12ae9105627..368cf123c3e 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::HousekeepingService do diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb index f9e5530bc9d..404bb55629a 100644 --- a/spec/services/projects/import_export/export_service_spec.rb +++ b/spec/services/projects/import_export/export_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::ImportExport::ExportService do diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index 7faf0fc2868..7f233a52f50 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::ImportService do diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb index f222c52199f..f1c0f5b9576 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::LfsPointers::LfsDownloadLinkListService do diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index 876beb39801..cde3f2d6155 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::LfsPointers::LfsDownloadService do diff --git a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb index 5a75fb38dec..5c9ca99df7c 100644 --- a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::LfsPointers::LfsImportService do diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb index b7b153655db..5caa9de732e 100644 --- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::LfsPointers::LfsLinkService do diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb index 88d9d93c33b..efa34c84522 100644 --- a/spec/services/projects/move_access_service_spec.rb +++ b/spec/services/projects/move_access_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveAccessService do diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb index c548edf39a8..a5d28fb0fbf 100644 --- a/spec/services/projects/move_deploy_keys_projects_service_spec.rb +++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveDeployKeysProjectsService do diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb index f4a5a7f9fc2..8f9f048d5ff 100644 --- a/spec/services/projects/move_forks_service_spec.rb +++ b/spec/services/projects/move_forks_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveForksService do diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb index 517a24a982a..114509229c5 100644 --- a/spec/services/projects/move_lfs_objects_projects_service_spec.rb +++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveLfsObjectsProjectsService do diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb index 24d69eef86a..54d85404bf6 100644 --- a/spec/services/projects/move_notification_settings_service_spec.rb +++ b/spec/services/projects/move_notification_settings_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveNotificationSettingsService do diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb index b4408393624..fe3ba31c881 100644 --- a/spec/services/projects/move_project_authorizations_service_spec.rb +++ b/spec/services/projects/move_project_authorizations_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveProjectAuthorizationsService do diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb index 7ca8cf304fe..6140d679929 100644 --- a/spec/services/projects/move_project_group_links_service_spec.rb +++ b/spec/services/projects/move_project_group_links_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveProjectGroupLinksService do diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb index c8c0eac1f13..bdd5cd6a87a 100644 --- a/spec/services/projects/move_project_members_service_spec.rb +++ b/spec/services/projects/move_project_members_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveProjectMembersService do diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb index e0545c5a21b..cde188f9f5f 100644 --- a/spec/services/projects/move_users_star_projects_service_spec.rb +++ b/spec/services/projects/move_users_star_projects_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::MoveUsersStarProjectsService do diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb index 562c14a8df8..8efa34765d0 100644 --- a/spec/services/projects/open_issues_count_service_spec.rb +++ b/spec/services/projects/open_issues_count_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::OpenIssuesCountService do diff --git a/spec/services/projects/open_merge_requests_count_service_spec.rb b/spec/services/projects/open_merge_requests_count_service_spec.rb index 9f49b9ec6a2..0d8227f7db5 100644 --- a/spec/services/projects/open_merge_requests_count_service_spec.rb +++ b/spec/services/projects/open_merge_requests_count_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::OpenMergeRequestsCountService do diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb index c7900629f5f..def39ad3789 100644 --- a/spec/services/projects/overwrite_project_service_spec.rb +++ b/spec/services/projects/overwrite_project_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::OverwriteProjectService do diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index 8455b9bc3cf..4def83513a4 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::ParticipantsService do diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb index f4c59735c43..f93e5aae82a 100644 --- a/spec/services/projects/propagate_service_template_spec.rb +++ b/spec/services/projects/propagate_service_template_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::PropagateServiceTemplate do diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb index 61c1b8c5ec1..09c61363ad2 100644 --- a/spec/services/projects/repository_languages_service_spec.rb +++ b/spec/services/projects/repository_languages_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::RepositoryLanguagesService do diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 4efd360cb30..9acc3657fa9 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::TransferService do diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index 014aab44281..a1175bf7123 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::UnlinkForkService do diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb index 7f5ef3129d7..363d3df0f84 100644 --- a/spec/services/projects/update_pages_configuration_service_spec.rb +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::UpdatePagesConfigurationService do diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 8b70845befe..7c91f0bbe6e 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Projects::UpdatePagesService do diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb index c1e5f788146..be2811ab1e7 100644 --- a/spec/services/projects/update_remote_mirror_service_spec.rb +++ b/spec/services/projects/update_remote_mirror_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::UpdateRemoteMirrorService do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 95eb17b5e3a..c37147e7d3b 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::UpdateService do diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb index 7e351c9ce54..5000ea58e5f 100644 --- a/spec/services/projects/update_statistics_service_spec.rb +++ b/spec/services/projects/update_statistics_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::UpdateStatisticsService do diff --git a/spec/services/prometheus/adapter_service_spec.rb b/spec/services/prometheus/adapter_service_spec.rb index 505e2935e93..5e972a966eb 100644 --- a/spec/services/prometheus/adapter_service_spec.rb +++ b/spec/services/prometheus/adapter_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Prometheus::AdapterService do diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb index 79b744142c6..82d24ec43f6 100644 --- a/spec/services/protected_branches/create_service_spec.rb +++ b/spec/services/protected_branches/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranches::CreateService do diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb index 4a391b6c25c..3287eb9a59b 100644 --- a/spec/services/protected_branches/destroy_service_spec.rb +++ b/spec/services/protected_branches/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranches::DestroyService do diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb index 3f6f8e09565..7967ff81075 100644 --- a/spec/services/protected_branches/update_service_spec.rb +++ b/spec/services/protected_branches/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranches::UpdateService do diff --git a/spec/services/protected_tags/create_service_spec.rb b/spec/services/protected_tags/create_service_spec.rb index b16acf1d36c..e58a539eb6f 100644 --- a/spec/services/protected_tags/create_service_spec.rb +++ b/spec/services/protected_tags/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedTags::CreateService do diff --git a/spec/services/protected_tags/destroy_service_spec.rb b/spec/services/protected_tags/destroy_service_spec.rb index e12f53a2221..52d1d1caa34 100644 --- a/spec/services/protected_tags/destroy_service_spec.rb +++ b/spec/services/protected_tags/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedTags::DestroyService do diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb index 2ece4e3b07b..ca5109aca9c 100644 --- a/spec/services/protected_tags/update_service_spec.rb +++ b/spec/services/protected_tags/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedTags::UpdateService do diff --git a/spec/services/push_event_payload_service_spec.rb b/spec/services/push_event_payload_service_spec.rb index 81956200bff..855b10c0259 100644 --- a/spec/services/push_event_payload_service_spec.rb +++ b/spec/services/push_event_payload_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PushEventPayloadService do diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb index 0efe37f1167..e26676cdd55 100644 --- a/spec/services/releases/create_service_spec.rb +++ b/spec/services/releases/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Releases::CreateService do diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb index 28663ca8853..f4c901e6585 100644 --- a/spec/services/releases/destroy_service_spec.rb +++ b/spec/services/releases/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Releases::DestroyService do diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb index 6c68f364739..14e6a5f13c8 100644 --- a/spec/services/releases/update_service_spec.rb +++ b/spec/services/releases/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Releases::UpdateService do diff --git a/spec/services/repair_ldap_blocked_user_service_spec.rb b/spec/services/repair_ldap_blocked_user_service_spec.rb index bf79cfe74b7..9918bb8e054 100644 --- a/spec/services/repair_ldap_blocked_user_service_spec.rb +++ b/spec/services/repair_ldap_blocked_user_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepairLdapBlockedUserService do diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb index ab1c638fc39..60a14d7a107 100644 --- a/spec/services/repository_archive_clean_up_service_spec.rb +++ b/spec/services/repository_archive_clean_up_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryArchiveCleanUpService do diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb index 1490ad5fe3b..a4db4481c36 100644 --- a/spec/services/reset_project_cache_service_spec.rb +++ b/spec/services/reset_project_cache_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ResetProjectCacheService do diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb index 980545b8083..62a70ccd3da 100644 --- a/spec/services/search/global_service_spec.rb +++ b/spec/services/search/global_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Search::GlobalService do diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb index cbc553a60cf..aac2f3fe4cb 100644 --- a/spec/services/search/group_service_spec.rb +++ b/spec/services/search/group_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Search::GroupService do diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb index 8ad162ad66e..430c71880a3 100644 --- a/spec/services/search/snippet_service_spec.rb +++ b/spec/services/search/snippet_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Search::SnippetService do diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index e5e036c7d44..48065bf596a 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SearchService do diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb index 61312d55b84..b9e5e844c1f 100644 --- a/spec/services/spam_service_spec.rb +++ b/spec/services/spam_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SpamService do diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb index c8a6fc1a99b..78df9bf96bf 100644 --- a/spec/services/submit_usage_ping_service_spec.rb +++ b/spec/services/submit_usage_ping_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SubmitUsagePingService do diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 81b2c17fdb5..5dc7394b84f 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SystemHooksService do diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb index e112cdc8881..ee558f90d6f 100644 --- a/spec/services/tags/create_service_spec.rb +++ b/spec/services/tags/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Tags::CreateService do diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb index a541d300595..b46bd77eafe 100644 --- a/spec/services/tags/destroy_service_spec.rb +++ b/spec/services/tags/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Tags::DestroyService do diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb index 19e1c5ff3b2..8d30f5018dd 100644 --- a/spec/services/test_hooks/project_service_spec.rb +++ b/spec/services/test_hooks/project_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TestHooks::ProjectService do diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb index 74d7715e50f..799b57eb04e 100644 --- a/spec/services/test_hooks/system_service_spec.rb +++ b/spec/services/test_hooks/system_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TestHooks::SystemService do diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 89411b2e908..9ee23f3eb48 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodoService do diff --git a/spec/services/todos/destroy/confidential_issue_service_spec.rb b/spec/services/todos/destroy/confidential_issue_service_spec.rb index 3294f7509aa..78b6744b426 100644 --- a/spec/services/todos/destroy/confidential_issue_service_spec.rb +++ b/spec/services/todos/destroy/confidential_issue_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Todos::Destroy::ConfidentialIssueService do diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb index 4b238280848..1447b9d4126 100644 --- a/spec/services/todos/destroy/entity_leave_service_spec.rb +++ b/spec/services/todos/destroy/entity_leave_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Todos::Destroy::EntityLeaveService do diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb index 5cefbdd35ab..a1798686d7c 100644 --- a/spec/services/todos/destroy/group_private_service_spec.rb +++ b/spec/services/todos/destroy/group_private_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Todos::Destroy::GroupPrivateService do diff --git a/spec/services/todos/destroy/private_features_service_spec.rb b/spec/services/todos/destroy/private_features_service_spec.rb index be8b5bb3979..7831e3a47e0 100644 --- a/spec/services/todos/destroy/private_features_service_spec.rb +++ b/spec/services/todos/destroy/private_features_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Todos::Destroy::PrivateFeaturesService do diff --git a/spec/services/todos/destroy/project_private_service_spec.rb b/spec/services/todos/destroy/project_private_service_spec.rb index 128d3487514..7c0c76b6c29 100644 --- a/spec/services/todos/destroy/project_private_service_spec.rb +++ b/spec/services/todos/destroy/project_private_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Todos::Destroy::ProjectPrivateService do diff --git a/spec/services/update_deployment_service_spec.rb b/spec/services/update_deployment_service_spec.rb index 3c55dd9659a..e1643233321 100644 --- a/spec/services/update_deployment_service_spec.rb +++ b/spec/services/update_deployment_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UpdateDeploymentService do diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb index 812dd42934d..12a2b287c72 100644 --- a/spec/services/update_merge_request_metrics_service_spec.rb +++ b/spec/services/update_merge_request_metrics_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe MergeRequestMetricsService do diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb index ef535c5cf1f..23ea4e003f8 100644 --- a/spec/services/update_snippet_service_spec.rb +++ b/spec/services/update_snippet_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UpdateSnippetService do diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb index 4a809d5bf18..504e61f9903 100644 --- a/spec/services/upload_service_spec.rb +++ b/spec/services/upload_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UploadService do diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb index 87a90378e2b..902ed723e09 100644 --- a/spec/services/user_project_access_changed_service_spec.rb +++ b/spec/services/user_project_access_changed_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UserProjectAccessChangedService do diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index 3c0a4ac8e18..48fc247f699 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::ActivityService do diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb index b7b9817efdb..aed5d2598ef 100644 --- a/spec/services/users/build_service_spec.rb +++ b/spec/services/users/build_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::BuildService do diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb index 24dac569678..a139dc01314 100644 --- a/spec/services/users/create_service_spec.rb +++ b/spec/services/users/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::CreateService do diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 450e76d5f58..1c79af34538 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::DestroyService do diff --git a/spec/services/users/last_push_event_service_spec.rb b/spec/services/users/last_push_event_service_spec.rb index 2b6c0267a0f..424e9e2f8ef 100644 --- a/spec/services/users/last_push_event_service_spec.rb +++ b/spec/services/users/last_push_event_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::LastPushEventService do diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb index 68b0f79c6d1..b808fa6d91a 100644 --- a/spec/services/users/migrate_to_ghost_user_service_spec.rb +++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::MigrateToGhostUserService do diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index 122b96ef216..0287a24808d 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::RefreshAuthorizedProjectsService do diff --git a/spec/services/users/respond_to_terms_service_spec.rb b/spec/services/users/respond_to_terms_service_spec.rb index fb08dd10b87..d840706e8a5 100644 --- a/spec/services/users/respond_to_terms_service_spec.rb +++ b/spec/services/users/respond_to_terms_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::RespondToTermsService do diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index 529c8485202..e04c05418b0 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Users::UpdateService do diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb index b60b13bb3fc..e5c7b5bb9a7 100644 --- a/spec/services/verify_pages_domain_service_spec.rb +++ b/spec/services/verify_pages_domain_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe VerifyPagesDomainService do diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 747e04fb18c..75ba2479b63 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WebHookService do diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb index 259f445247e..84510dcf700 100644 --- a/spec/services/wiki_pages/create_service_spec.rb +++ b/spec/services/wiki_pages/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WikiPages::CreateService do diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb index 2938126914b..c74eac4dad6 100644 --- a/spec/services/wiki_pages/destroy_service_spec.rb +++ b/spec/services/wiki_pages/destroy_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WikiPages::DestroyService do diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb index 2399db7d3d4..19866bd3bfc 100644 --- a/spec/services/wiki_pages/update_service_spec.rb +++ b/spec/services/wiki_pages/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WikiPages::UpdateService do -- cgit v1.2.1 From 65b4b0cf468744b4e7ed7a874b1eb920317c0d43 Mon Sep 17 00:00:00 2001 From: Cindy Pallares Date: Thu, 11 Apr 2019 17:38:28 -0500 Subject: Fix typo in activity lease --- spec/services/users/activity_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index 3c0a4ac8e18..7b11d9e06e9 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -76,7 +76,7 @@ describe Users::ActivityService do let(:last_activity_on) { nil } it 'does not update last_activity_on' do - stub_exclusive_lease_taken("acitvity_service:#{user.id}", timeout: 1.minute.to_i) + stub_exclusive_lease_taken("activity_service:#{user.id}", timeout: 1.minute.to_i) expect { subject.execute }.not_to change(user, :last_activity_on) end -- cgit v1.2.1 From 9779798ac45c2d33175435eec590f025ca9f03a1 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 11 Apr 2019 22:20:45 -0500 Subject: Resolve CE/EE diff in text_utility_spec --- spec/frontend/lib/utils/text_utility_spec.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 3f331055a32..17fdbf606b2 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -23,14 +23,6 @@ describe('text_utility', () => { }); }); - describe('capitalizeFirstCharacter', () => { - it('returns string with first letter capitalized', () => { - expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab'); - expect(textUtils.highCountTrim(105)).toBe('99+'); - expect(textUtils.highCountTrim(100)).toBe('99+'); - }); - }); - describe('humanize', () => { it('should remove underscores and uppercase the first letter', () => { expect(textUtils.humanize('foo_bar')).toEqual('Foo bar'); @@ -63,6 +55,12 @@ describe('text_utility', () => { }); }); + describe('capitalizeFirstCharacter', () => { + it('returns string with first letter capitalized', () => { + expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab'); + }); + }); + describe('slugifyWithHyphens', () => { it('should replaces whitespaces with hyphens and convert to lower case', () => { expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string'); -- cgit v1.2.1 From 208338fde6750ad327fb8ab57877869435520436 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Fri, 12 Apr 2019 12:19:45 +0800 Subject: Add ApplicationRecord#safe_ensure_unique method Port of https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/866 to CE excluding the migration and service changes as they don't apply to CE. --- spec/models/application_record_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'spec') diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb index fd25132ed3a..cc90a998d3f 100644 --- a/spec/models/application_record_spec.rb +++ b/spec/models/application_record_spec.rb @@ -11,6 +11,25 @@ describe ApplicationRecord do end end + describe '.safe_ensure_unique' do + let(:model) { build(:suggestion) } + let(:klass) { model.class } + + before do + allow(model).to receive(:save).and_raise(ActiveRecord::RecordNotUnique) + end + + it 'returns false when ActiveRecord::RecordNotUnique is raised' do + expect(model).to receive(:save).once + expect(klass.safe_ensure_unique { model.save }).to be_falsey + end + + it 'retries based on retry count specified' do + expect(model).to receive(:save).exactly(3).times + expect(klass.safe_ensure_unique(retries: 2) { model.save }).to be_falsey + end + end + describe '.safe_find_or_create_by' do it 'creates the user avoiding race conditions' do expect(Suggestion).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique) -- cgit v1.2.1 From dc00fa90e95916719bfcfeb5bdca9f0b590ca467 Mon Sep 17 00:00:00 2001 From: Sara Ahbabou Date: Fri, 12 Apr 2019 05:00:50 +0000 Subject: Squashed commits and modified visibility level output Added changelog and rebased --- spec/lib/gitlab/visibility_level_spec.rb | 8 ++++++++ spec/services/groups/create_service_spec.rb | 11 +++++++++++ 2 files changed, 19 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb index 2c1146ceff5..0a170a157fe 100644 --- a/spec/lib/gitlab/visibility_level_spec.rb +++ b/spec/lib/gitlab/visibility_level_spec.rb @@ -85,4 +85,12 @@ describe Gitlab::VisibilityLevel do .to eq(described_class::PRIVATE) end end + + describe '.valid_level?' do + it 'returns true when visibility is valid' do + expect(described_class.valid_level?(described_class::PRIVATE)).to be_truthy + expect(described_class.valid_level?(described_class::INTERNAL)).to be_truthy + expect(described_class.valid_level?(described_class::PUBLIC)).to be_truthy + end + end end diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index fe6a8691ae0..cd061afbfd5 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -88,6 +88,17 @@ describe Groups::CreateService, '#execute' do end end + describe "when visibility level is passed as a string" do + let(:service) { described_class.new(user, group_params) } + let(:group_params) { { path: 'group_path', visibility: 'public' } } + + it "assigns the correct visibility level" do + group = service.execute + + expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + describe 'creating a mattermost team' do let!(:params) { group_params.merge(create_chat_team: "true") } let!(:service) { described_class.new(user, params) } -- cgit v1.2.1 From 3ef5666783b220422283e4d5acbcbbbfac1e935a Mon Sep 17 00:00:00 2001 From: Wei-Meng Lee Date: Fri, 12 Apr 2019 11:59:09 +0800 Subject: Only show in autocomplete when author active --- spec/finders/autocomplete/users_finder_spec.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb index abd0d6b5185..bcde115b1a6 100644 --- a/spec/finders/autocomplete/users_finder_spec.rb +++ b/spec/finders/autocomplete/users_finder_spec.rb @@ -26,9 +26,17 @@ describe Autocomplete::UsersFinder do it { is_expected.to match_array([project.owner]) } context 'when author_id passed' do - let(:params) { { author_id: user2.id } } + context 'and author is active' do + let(:params) { { author_id: user1.id } } - it { is_expected.to match_array([project.owner, user2]) } + it { is_expected.to match_array([project.owner, user1]) } + end + + context 'and author is blocked' do + let(:params) { { author_id: user2.id } } + + it { is_expected.to match_array([project.owner]) } + end end end @@ -104,9 +112,9 @@ describe Autocomplete::UsersFinder do end context 'when filtered by author_id' do - let(:params) { { author_id: user2.id } } + let(:params) { { author_id: user1.id } } - it { is_expected.to match_array([user2, user1, external_user, omniauth_user, current_user]) } + it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) } end end end -- cgit v1.2.1 From 45ef6fd77710b29dc3fb1d413eb819ad47f154e5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 12 Apr 2019 09:32:05 +0100 Subject: Fixed fluid layout preference not being respected in diffs Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/52916 --- spec/javascripts/diffs/components/app_spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 3ce69bc3c20..1aabf3c2132 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -75,6 +75,14 @@ describe('diffs/components/app', () => { expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false); }); + it('does not add container-limiting classes when isFluidLayout', () => { + createComponent({ isFluidLayout: true }, ({ state }) => { + state.diffs.isParallelView = false; + }); + + expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false); + }); + it('displays loading icon on loading', () => { createComponent({}, ({ state }) => { state.diffs.isLoading = true; -- cgit v1.2.1 From 5f36bb928bf448a30436c978cf6dc7a85f98dbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 12 Apr 2019 10:50:24 +0200 Subject: Fix a few specs after updating capybara and selenium-webdriver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/admin/admin_hooks_spec.rb | 10 +++++++--- .../projects/members/user_requests_access_spec.rb | 3 ++- spec/features/u2f_spec.rb | 20 -------------------- .../master_manages_access_requests_shared_example.rb | 4 +--- 4 files changed, 10 insertions(+), 27 deletions(-) (limited to 'spec') diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 25ed3bdc88e..ce780789f5a 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -73,22 +73,26 @@ describe 'Admin::Hooks' do end describe 'Remove existing hook', :js do + let(:hook_url) { generate(:url) } + before do - create(:system_hook) + create(:system_hook, url: hook_url) end context 'removes existing hook' do it 'from hooks list page' do visit admin_hooks_path - expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1) + accept_confirm { click_link 'Remove' } + expect(page).not_to have_content(hook_url) end it 'from hook edit page' do visit admin_hooks_path click_link 'Edit' - expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1) + accept_confirm { click_link 'Remove' } + expect(page).not_to have_content(hook_url) end end end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index 50ba67f0ffc..f26941ab567 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -62,8 +62,9 @@ describe 'Projects > Members > User requests access', :js do accept_confirm { click_link 'Withdraw Access Request' } - expect(project.requesters.exists?(user_id: user)).to be_falsey expect(page).to have_content 'Your access request to the project has been withdrawn.' + expect(page).not_to have_content 'Withdraw Access Request' + expect(page).to have_content 'Request Access' end def open_project_settings_menu diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index ae9b65d1a39..ea02f36d9d0 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -246,26 +246,6 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do end end end - - describe "when two-factor authentication is disabled" do - let(:user) { create(:user) } - - before do - user = gitlab_sign_in(:user) - user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - manage_two_factor_authentication - expect(page).to have_content("Your U2F device needs to be set up.") - register_u2f_device - end - - it "deletes u2f registrations" do - visit profile_two_factor_auth_path - expect do - accept_confirm { click_on "Disable" } - end.to change { U2fRegistration.count }.by(-1) - end - end end describe 'fallback code authentication' do diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb index 75ad948e42c..d87e5fcaa88 100644 --- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb +++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb @@ -19,7 +19,7 @@ RSpec.shared_examples 'Maintainer manages access requests' do expect_visible_access_request(entity, user) - accept_confirm { click_on 'Grant access' } + click_on 'Grant access' expect_no_visible_access_request(entity, user) @@ -40,13 +40,11 @@ RSpec.shared_examples 'Maintainer manages access requests' do end def expect_visible_access_request(entity, user) - expect(entity.requesters.exists?(user_id: user)).to be_truthy expect(page).to have_content "Users requesting access to #{entity.name} 1" expect(page).to have_content user.name end def expect_no_visible_access_request(entity, user) - expect(entity.requesters.exists?(user_id: user)).to be_falsy expect(page).not_to have_content "Users requesting access to #{entity.name}" end end -- cgit v1.2.1 From 5b28ee5f337a4b95273557553de9f500d7195fe6 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Tue, 9 Apr 2019 16:09:42 +0530 Subject: Add time tracking information to Issue Boards sidebar Add time tracking progress bar to Issue Boards sidebar. --- spec/features/boards/sidebar_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'spec') diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index b358c6b9c34..8f34fddbf06 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -222,6 +222,21 @@ describe 'Issue Boards', :js do end end + context 'time tracking' do + before do + issue2.timelogs.create(time_spent: 14400, user: user) + issue2.update!(time_estimate: 28800) + end + + it 'shows time tracking progress bar' do + click_card(card) + + page.within('.time-tracking') do + expect(find('.time-tracking-content .compare-meter')['data-original-title']).to eq('Time remaining: 4h') + end + end + end + context 'due date' do it 'updates due date' do click_card(card) -- cgit v1.2.1 From f38f160a1b0212d5ce0c0402ae07011466753f44 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 12 Apr 2019 10:27:19 +0000 Subject: Enable RSpec/ExampleWording cop --- spec/requests/api/pipelines_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 0d46463312b..26158231444 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe API::Pipelines do @@ -493,7 +495,7 @@ describe API::Pipelines do context 'pipeline created is not created by the developer user' do let(:api_user) { create(:user) } - it 'should not return pipeline variables' do + it 'does not return pipeline variables' do subject expect(response).to have_gitlab_http_status(403) @@ -502,7 +504,7 @@ describe API::Pipelines do end context 'user is not a project member' do - it 'should not return pipeline variables' do + it 'does not return pipeline variables' do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member) expect(response).to have_gitlab_http_status(404) -- cgit v1.2.1 From 8dfec0cedd4d623226ee9d058323a13470631bce Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 12 Apr 2019 13:22:11 +0000 Subject: Do not rescue errors from state transitions As this are un-expected errors which we should hear about from Sentry. Still rescue StandardError when operating a Helm action as we can get non Kubeclient errors such as SSL certificate or network errors --- spec/services/clusters/applications/install_service_spec.rb | 6 ++---- spec/services/clusters/applications/patch_service_spec.rb | 6 ++---- spec/services/clusters/applications/upgrade_service_spec.rb | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb index db0c33a95b2..14739ff9e7c 100644 --- a/spec/services/clusters/applications/install_service_spec.rb +++ b/spec/services/clusters/applications/install_service_spec.rb @@ -58,7 +58,7 @@ describe Clusters::Applications::InstallService do let(:error) { StandardError.new('something bad happened') } before do - expect(application).to receive(:make_installing!).once.and_raise(error) + expect(helm_client).to receive(:install).with(install_command).and_raise(error) end include_examples 'logs kubernetes errors' do @@ -68,12 +68,10 @@ describe Clusters::Applications::InstallService do end it 'make the application errored' do - expect(helm_client).not_to receive(:install) - service.execute expect(application).to be_errored - expect(application.status_reason).to eq("Can't start installation process.") + expect(application.status_reason).to eq('Failed to install.') end end end diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb index 10b1379a127..3ebe0540837 100644 --- a/spec/services/clusters/applications/patch_service_spec.rb +++ b/spec/services/clusters/applications/patch_service_spec.rb @@ -66,16 +66,14 @@ describe Clusters::Applications::PatchService do end before do - expect(application).to receive(:make_updating!).once.and_raise(error) + expect(helm_client).to receive(:update).with(update_command).and_raise(error) end it 'make the application errored' do - expect(helm_client).not_to receive(:update) - service.execute expect(application).to be_update_errored - expect(application.status_reason).to eq("Can't start update process.") + expect(application.status_reason).to eq('Failed to update.') end end end diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb index dd2e6e94e4f..a80b1d9127c 100644 --- a/spec/services/clusters/applications/upgrade_service_spec.rb +++ b/spec/services/clusters/applications/upgrade_service_spec.rb @@ -60,7 +60,7 @@ describe Clusters::Applications::UpgradeService do let(:error) { StandardError.new('something bad happened') } before do - expect(application).to receive(:make_updating!).once.and_raise(error) + expect(helm_client).to receive(:update).with(install_command).and_raise(error) end include_examples 'logs kubernetes errors' do @@ -70,12 +70,10 @@ describe Clusters::Applications::UpgradeService do end it 'make the application errored' do - expect(helm_client).not_to receive(:update) - service.execute expect(application).to be_update_errored - expect(application.status_reason).to eq("Can't start upgrade process.") + expect(application.status_reason).to eq('Failed to upgrade.') end end end -- cgit v1.2.1 From cfb94bca6f804c0dc762c5df2ed0a2c0d7c4c973 Mon Sep 17 00:00:00 2001 From: Igor Date: Fri, 12 Apr 2019 13:25:17 +0000 Subject: Fix grouping wiki pages by directory If a page has the same name as a directory the WikiPage#group_by_directory method can have a wrong behaviour This commit fixes it --- spec/models/wiki_page_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index cacdb0e0595..d5c85c11195 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -22,6 +22,7 @@ describe WikiPage do create_page('dir_1/dir_1_1/page_3', 'content') create_page('page_1', 'content') create_page('dir_1/page_2', 'content') + create_page('dir_2', 'page with dir name') create_page('dir_2/page_5', 'content') create_page('page_6', 'content') create_page('dir_2/page_4', 'content') @@ -29,6 +30,7 @@ describe WikiPage do let(:page_1) { wiki.find_page('page_1') } let(:page_6) { wiki.find_page('page_6') } + let(:page_dir_2) { wiki.find_page('dir_2') } let(:dir_1) do WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')]) @@ -44,7 +46,7 @@ describe WikiPage do context 'sort by title' do let(:grouped_entries) { described_class.group_by_directory(wiki.pages) } - let(:expected_grouped_entries) { [dir_1_1, dir_1, dir_2, page_1, page_6] } + let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] } it 'returns an array with pages and directories' do grouped_entries.each_with_index do |page_or_dir, i| @@ -59,7 +61,7 @@ describe WikiPage do context 'sort by created_at' do let(:grouped_entries) { described_class.group_by_directory(wiki.pages(sort: 'created_at')) } - let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, dir_2, page_6] } + let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] } it 'returns an array with pages and directories' do grouped_entries.each_with_index do |page_or_dir, i| @@ -73,7 +75,7 @@ describe WikiPage do end it 'returns an array with retained order with directories at the top' do - expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6'] + expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6'] grouped_entries = described_class.group_by_directory(wiki.pages) -- cgit v1.2.1 From e42ecb1c07690a606c1eabb4c5a6f56725c586b7 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 11 Apr 2019 21:14:02 -0500 Subject: Remove slugify text utility **Why?** This function was not being used and it's documentation was out of date. --- spec/frontend/lib/utils/text_utility_spec.js | 6 ------ 1 file changed, 6 deletions(-) (limited to 'spec') diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 17fdbf606b2..a63631e09d2 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -49,12 +49,6 @@ describe('text_utility', () => { }); }); - describe('slugify', () => { - it('should remove accents and convert to lower case', () => { - expect(textUtils.slugify('João')).toEqual('joão'); - }); - }); - describe('capitalizeFirstCharacter', () => { it('returns string with first letter capitalized', () => { expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab'); -- cgit v1.2.1 From 2ea409f6e18150615610e09979bda73fc5bff9e8 Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Wed, 10 Apr 2019 14:35:16 -0400 Subject: Fix bug where MR Popover wont go away Add a check to make sure eventListener is not added twice --- spec/frontend/mr_popover/index_spec.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spec/frontend/mr_popover/index_spec.js (limited to 'spec') diff --git a/spec/frontend/mr_popover/index_spec.js b/spec/frontend/mr_popover/index_spec.js new file mode 100644 index 00000000000..8c33e52a04b --- /dev/null +++ b/spec/frontend/mr_popover/index_spec.js @@ -0,0 +1,30 @@ +import * as createDefaultClient from '~/lib/graphql'; +import { setHTMLFixture } from '../helpers/fixtures'; +import initMRPopovers from '~/mr_popover/index'; + +createDefaultClient.default = jest.fn(); + +describe('initMRPopovers', () => { + let mr1; + let mr2; + + beforeEach(() => { + setHTMLFixture(` +
      MR1
      +
      MR2
      + `); + + mr1 = document.querySelector('#one'); + mr2 = document.querySelector('#two'); + + mr1.addEventListener = jest.fn(); + mr2.addEventListener = jest.fn(); + }); + + it('does not add the same event listener twice', () => { + initMRPopovers([mr1, mr1, mr2]); + + expect(mr1.addEventListener).toHaveBeenCalledTimes(1); + expect(mr2.addEventListener).toHaveBeenCalledTimes(1); + }); +}); -- cgit v1.2.1 From 9dca4f127f8fbc3c74a55c5dbd075ea3533f56b2 Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Wed, 10 Apr 2019 16:39:03 -0400 Subject: Fix isDesktop function to return proper value --- spec/javascripts/breakpoints_spec.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/breakpoints_spec.js b/spec/javascripts/breakpoints_spec.js index 5ee777fee3f..fc0d9eb907a 100644 --- a/spec/javascripts/breakpoints_spec.js +++ b/spec/javascripts/breakpoints_spec.js @@ -10,4 +10,18 @@ describe('breakpoints', () => { expect(bp.getBreakpointSize()).toBe(key); }); }); + + describe('isDesktop', () => { + it('returns true when screen size is medium', () => { + spyOn(bp, 'windowWidth').and.returnValue(breakpoints.md + 10); + + expect(bp.isDesktop()).toBe(true); + }); + + it('returns false when screen size is small', () => { + spyOn(bp, 'windowWidth').and.returnValue(breakpoints.sm + 10); + + expect(bp.isDesktop()).toBe(false); + }); + }); }); -- cgit v1.2.1 From a3966d92cff14f7baf1f4f94902461480c9023de Mon Sep 17 00:00:00 2001 From: Constance Okoghenun Date: Fri, 12 Apr 2019 15:32:24 +0000 Subject: CE backport of Add Snowplow tracking to notes Bacport of Snowplow tracking for - Reply comment button - Start discussion and Comment buttons --- spec/frontend/lib/utils/text_utility_spec.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index a63631e09d2..0878c1de095 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -144,6 +144,12 @@ describe('text_utility', () => { }); }); + describe('slugifyWithUnderscore', () => { + it('should replaces whitespaces with underscore and convert to lower case', () => { + expect(textUtils.slugifyWithUnderscore('My Input String')).toEqual('my_input_string'); + }); + }); + describe('truncateNamespace', () => { it(`should return the root namespace if the namespace only includes one level`, () => { expect(textUtils.truncateNamespace('a / b')).toBe('a'); -- cgit v1.2.1 From b9b2897bb507ac3a140e2c19f3d79094c3f5156b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 12 Apr 2019 12:46:00 -0300 Subject: Always return the deployment in the execute method --- spec/services/update_deployment_service_spec.rb | 37 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/services/update_deployment_service_spec.rb b/spec/services/update_deployment_service_spec.rb index 3c55dd9659a..54959768a27 100644 --- a/spec/services/update_deployment_service_spec.rb +++ b/spec/services/update_deployment_service_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe UpdateDeploymentService do let(:user) { create(:user) } + let(:project) { create(:project, :repository) } let(:options) { { name: 'production' } } let(:job) do @@ -13,24 +14,22 @@ describe UpdateDeploymentService do project: project) end - let(:project) { create(:project, :repository) } - let(:environment) { deployment.environment } let(:deployment) { job.deployment } - let(:service) { described_class.new(deployment) } + let(:environment) { deployment.environment } + + subject(: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 + service.execute expect(store.get(environment.etag_cache_key)).not_to eq(old_value) end @@ -40,14 +39,30 @@ describe UpdateDeploymentService do .to receive(:create_ref) .with(deployment.ref, deployment.send(:ref_path)) - subject + service.execute end it 'updates merge request metrics' do expect_any_instance_of(Deployment) .to receive(:update_merge_request_metrics!) - subject + service.execute + end + + it 'returns the deployment' do + expect(subject.execute).to eq(deployment) + end + + it 'returns the deployment when could not save the environment' do + allow(environment).to receive(:save).and_return(false) + + expect(subject.execute).to eq(deployment) + end + + it 'returns the deployment when environment is stopped' do + allow(environment).to receive(:stopped?).and_return(true) + + expect(subject.execute).to eq(deployment) end context 'when start action is defined' do @@ -59,7 +74,7 @@ describe UpdateDeploymentService do end it 'makes environment available' do - subject + service.execute expect(environment.reload).to be_available end @@ -78,11 +93,11 @@ describe UpdateDeploymentService do end it 'does not create a new environment' do - expect { subject }.not_to change { Environment.count } + expect { subject.execute }.not_to change { Environment.count } end it 'updates external url' do - subject + subject.execute expect(subject.environment.name).to eq('review-apps/master') expect(subject.environment.external_url).to eq('http://master.review-apps.gitlab.com') -- cgit v1.2.1 From 34243489af3e52ca87d6f1bc578bd9ba4e9d565e Mon Sep 17 00:00:00 2001 From: drew cimino Date: Thu, 4 Apr 2019 19:41:54 +0100 Subject: Don't render artifact download links on the project tags page for builds with expired artifacts --- spec/models/ci/pipeline_spec.rb | 15 ++++---- spec/views/projects/tags/index.html.haml_spec.rb | 44 +++++++++++++++++++++--- 2 files changed, 48 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index f3e78630c1b..2b6b12650af 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2705,18 +2705,19 @@ describe Ci::Pipeline, :mailer do end describe '#latest_builds_with_artifacts' do - let!(:pipeline) { create(:ci_pipeline, :success) } - - let!(:build) do - create(:ci_build, :success, :artifacts, pipeline: pipeline) - end + let!(:fresh_build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + let!(:stale_build) { create(:ci_build, :success, :expired, :artifacts, pipeline: pipeline) } it 'returns an Array' do expect(pipeline.latest_builds_with_artifacts).to be_an_instance_of(Array) end - it 'returns the latest builds' do - expect(pipeline.latest_builds_with_artifacts).to eq([build]) + it 'returns the latest builds with non-expired artifacts' do + expect(pipeline.latest_builds_with_artifacts).to contain_exactly(fresh_build) + end + + it 'does not return builds with expired artifacts' do + expect(pipeline.latest_builds_with_artifacts).not_to include(stale_build) end it 'memoizes the returned relation' do diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb index cb97d17988c..32e90e222d9 100644 --- a/spec/views/projects/tags/index.html.haml_spec.rb +++ b/spec/views/projects/tags/index.html.haml_spec.rb @@ -1,20 +1,56 @@ require 'spec_helper' describe 'projects/tags/index' do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository) } + let(:tags) { TagsFinder.new(project.repository, {}).execute } + let(:git_tag) { project.repository.tags.last } + let(:release) { create(:release, project: project, sha: git_tag.target_commit.sha) } + let(:pipeline) { create(:ci_pipeline, :success, project: project, ref: git_tag.name, sha: release.sha) } + + let(:artifacts_download_header_regex) { %r(
    • '; + + const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag); + + expect(highlightedTag).toEqual(liTag); + }); + + it('should highlight search query in li element', () => { + const liTag = '
    • string
    • '; + const query = 's'; + + const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query); + + expect(highlightedTag).toEqual('
    • string
    • '); + }); + + it('should highlight search query with special char in li element', () => { + const liTag = '
    • te.st
    • '; + const query = '.'; + + const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query); + + expect(highlightedTag).toEqual('
    • te.st
    • '); + }); + }); + describe('isLoading', () => { it('should be true with loading data object item', () => { expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true); -- cgit v1.2.1 From e7e9929e0d9c7f208394ccb61239228216fe0be8 Mon Sep 17 00:00:00 2001 From: rpereira2 Date: Thu, 25 Apr 2019 16:06:17 +0530 Subject: Fix a bug in shared_examples definition --- spec/controllers/projects/settings/operations_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb index 7df63266f71..0dd73972098 100644 --- a/spec/controllers/projects/settings/operations_controller_spec.rb +++ b/spec/controllers/projects/settings/operations_controller_spec.rb @@ -11,7 +11,7 @@ describe Projects::Settings::OperationsController do project.add_maintainer(user) end - shared_context 'PATCHable' do + shared_examples 'PATCHable' do let(:operations_update_service) { instance_double(::Projects::Operations::UpdateService) } let(:operations_url) { project_settings_operations_url(project) } -- cgit v1.2.1 From 56115b649400b47c7d9f530d300c4106a9dacf1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 25 Apr 2019 12:56:02 +0200 Subject: Put a failing spec in quarantine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/issuables/markdown_references/jira_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb index 8eaccfc0949..cc04798248c 100644 --- a/spec/features/issuables/markdown_references/jira_spec.rb +++ b/spec/features/issuables/markdown_references/jira_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -describe "Jira", :js do +describe "Jira", :js, :quarantine do let(:user) { create(:user) } let(:actual_project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project) } -- cgit v1.2.1 From f48605c796bf9098370a258e00fce41f1770ff74 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Sun, 30 Dec 2018 22:31:35 -0500 Subject: Use all keyword args for DataBuilder::Push.build() --- spec/lib/gitlab/data_builder/push_spec.rb | 11 +++++++---- spec/models/project_services/hipchat_service_spec.rb | 11 +++++------ spec/support/shared_examples/models/chat_service_spec.rb | 2 +- .../slack_mattermost_notifications_shared_examples.rb | 6 +++--- 4 files changed, 16 insertions(+), 14 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index 0c4decc6518..46ad674a1eb 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -23,9 +23,12 @@ describe Gitlab::DataBuilder::Push do describe '.build' do let(:data) do - described_class.build(project, user, Gitlab::Git::BLANK_SHA, - '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b', - 'refs/tags/v1.1.0') + described_class.build( + project: project, + user: user, + oldrev: Gitlab::Git::BLANK_SHA, + newrev: '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b', + ref: 'refs/tags/v1.1.0') end it { expect(data).to be_a(Hash) } @@ -47,7 +50,7 @@ describe Gitlab::DataBuilder::Push do include_examples 'deprecated repository hook data' it 'does not raise an error when given nil commits' do - expect { described_class.build(spy, spy, spy, spy, 'refs/tags/v1.1.0', nil) } + expect { described_class.build(project: spy, user: spy, ref: 'refs/tags/v1.1.0', commits: nil) } .not_to raise_error end end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index fd9e33c1781..a04b984c1f6 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -98,12 +98,11 @@ describe HipchatService do context 'tag_push events' do let(:push_sample_data) do Gitlab::DataBuilder::Push.build( - project, - user, - Gitlab::Git::BLANK_SHA, - '1' * 40, - 'refs/tags/test', - []) + project: project, + user: user, + oldrev: Gitlab::Git::BLANK_SHA, + newrev: '1' * 40, + ref: 'refs/tags/test') end it "calls Hipchat API for tag push events" do diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_spec.rb index cf1d52a9616..2711c23f916 100644 --- a/spec/support/shared_examples/models/chat_service_spec.rb +++ b/spec/support/shared_examples/models/chat_service_spec.rb @@ -64,7 +64,7 @@ shared_examples_for "chat service" do |service_name| context "with not default branch" do let(:sample_data) do - Gitlab::DataBuilder::Push.build(project, user, nil, nil, "not-the-default-branch") + Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch") end context "when notify_only_default_branch enabled" do diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb index 940c24c8d67..d2492f6cee5 100644 --- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb @@ -267,7 +267,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do it 'does not notify push events if they are not for the default branch' do ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test" - push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref) chat_service.execute(push_sample_data) @@ -284,7 +284,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do it 'still notifies about pushed tags' do ref = "#{Gitlab::Git::TAG_REF_PREFIX}test" - push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref) chat_service.execute(push_sample_data) @@ -299,7 +299,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do it 'notifies about all push events' do ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test" - push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref) chat_service.execute(push_sample_data) -- cgit v1.2.1 From d152a74ce393d76b1fbbf1b0a304125f3c9457c4 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 25 Apr 2019 11:35:33 +0200 Subject: Move all static fixtures to static directory --- spec/javascripts/fixtures/images/green_box.png | Bin 1306 -> 0 bytes spec/javascripts/fixtures/images/red_box.png | Bin 1305 -> 0 bytes spec/javascripts/fixtures/one_white_pixel.png | Bin 68 -> 0 bytes spec/javascripts/fixtures/projects.json | 445 --------------------- .../fixtures/static/images/green_box.png | Bin 0 -> 1306 bytes .../fixtures/static/images/one_white_pixel.png | Bin 0 -> 68 bytes .../javascripts/fixtures/static/images/red_box.png | Bin 0 -> 1305 bytes spec/javascripts/fixtures/static/projects.json | 445 +++++++++++++++++++++ spec/javascripts/gl_dropdown_spec.js | 4 +- spec/javascripts/test_constants.js | 6 +- .../project_selector/project_list_item_spec.js | 4 +- .../project_selector/project_selector_spec.js | 4 +- 12 files changed, 454 insertions(+), 454 deletions(-) delete mode 100644 spec/javascripts/fixtures/images/green_box.png delete mode 100644 spec/javascripts/fixtures/images/red_box.png delete mode 100644 spec/javascripts/fixtures/one_white_pixel.png delete mode 100644 spec/javascripts/fixtures/projects.json create mode 100644 spec/javascripts/fixtures/static/images/green_box.png create mode 100644 spec/javascripts/fixtures/static/images/one_white_pixel.png create mode 100644 spec/javascripts/fixtures/static/images/red_box.png create mode 100644 spec/javascripts/fixtures/static/projects.json (limited to 'spec') diff --git a/spec/javascripts/fixtures/images/green_box.png b/spec/javascripts/fixtures/images/green_box.png deleted file mode 100644 index cd1ff9f9ade..00000000000 Binary files a/spec/javascripts/fixtures/images/green_box.png and /dev/null differ diff --git a/spec/javascripts/fixtures/images/red_box.png b/spec/javascripts/fixtures/images/red_box.png deleted file mode 100644 index 73b2927da0f..00000000000 Binary files a/spec/javascripts/fixtures/images/red_box.png and /dev/null differ diff --git a/spec/javascripts/fixtures/one_white_pixel.png b/spec/javascripts/fixtures/one_white_pixel.png deleted file mode 100644 index 073fcf40a18..00000000000 Binary files a/spec/javascripts/fixtures/one_white_pixel.png and /dev/null differ diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json deleted file mode 100644 index 68a150f602a..00000000000 --- a/spec/javascripts/fixtures/projects.json +++ /dev/null @@ -1,445 +0,0 @@ -[{ - "id": 9, - "description": "", - "default_branch": null, - "tag_list": [], - "public": true, - "archived": false, - "visibility_level": 20, - "ssh_url_to_repo": "phil@localhost:root/test.git", - "http_url_to_repo": "http://localhost:3000/root/test.git", - "web_url": "http://localhost:3000/root/test", - "owner": { - "name": "Administrator", - "username": "root", - "id": 1, - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", - "web_url": "http://localhost:3000/u/root" - }, - "name": "test", - "name_with_namespace": "Administrator / test", - "path": "test", - "path_with_namespace": "root/test", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-14T19:08:05.364Z", - "last_activity_at": "2016-01-14T19:08:07.418Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 1, - "name": "root", - "path": "root", - "owner_id": 1, - "created_at": "2016-01-13T20:19:44.439Z", - "updated_at": "2016-01-13T20:19:44.439Z", - "description": "", - "avatar": null - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": false, - "open_issues_count": 0, - "permissions": { - "project_access": null, - "group_access": null - } -}, { - "id": 8, - "description": "Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.", - "default_branch": "master", - "tag_list": [], - "public": false, - "archived": false, - "visibility_level": 0, - "ssh_url_to_repo": "phil@localhost:h5bp/html5-boilerplate.git", - "http_url_to_repo": "http://localhost:3000/h5bp/html5-boilerplate.git", - "web_url": "http://localhost:3000/h5bp/html5-boilerplate", - "name": "Html5 Boilerplate", - "name_with_namespace": "H5bp / Html5 Boilerplate", - "path": "html5-boilerplate", - "path_with_namespace": "h5bp/html5-boilerplate", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:57.525Z", - "last_activity_at": "2016-01-13T20:27:57.280Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 5, - "name": "H5bp", - "path": "h5bp", - "owner_id": null, - "created_at": "2016-01-13T20:19:57.239Z", - "updated_at": "2016-01-13T20:19:57.239Z", - "description": "Tempore accusantium possimus aut libero.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": false, - "open_issues_count": 5, - "permissions": { - "project_access": { - "access_level": 10, - "notification_level": 3 - }, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -}, { - "id": 7, - "description": "Modi odio mollitia dolorem qui.", - "default_branch": "master", - "tag_list": [], - "public": false, - "archived": false, - "visibility_level": 0, - "ssh_url_to_repo": "phil@localhost:twitter/typeahead-js.git", - "http_url_to_repo": "http://localhost:3000/twitter/typeahead-js.git", - "web_url": "http://localhost:3000/twitter/typeahead-js", - "name": "Typeahead.Js", - "name_with_namespace": "Twitter / Typeahead.Js", - "path": "typeahead-js", - "path_with_namespace": "twitter/typeahead-js", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:56.212Z", - "last_activity_at": "2016-01-13T20:27:51.496Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 4, - "name": "Twitter", - "path": "twitter", - "owner_id": null, - "created_at": "2016-01-13T20:19:54.480Z", - "updated_at": "2016-01-13T20:19:54.480Z", - "description": "Id voluptatem ipsa maiores omnis repudiandae et et.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": true, - "open_issues_count": 4, - "permissions": { - "project_access": null, - "group_access": { - "access_level": 10, - "notification_level": 3 - } - } -}, { - "id": 6, - "description": "Omnis asperiores ipsa et beatae quidem necessitatibus quia.", - "default_branch": "master", - "tag_list": [], - "public": true, - "archived": false, - "visibility_level": 20, - "ssh_url_to_repo": "phil@localhost:twitter/flight.git", - "http_url_to_repo": "http://localhost:3000/twitter/flight.git", - "web_url": "http://localhost:3000/twitter/flight", - "name": "Flight", - "name_with_namespace": "Twitter / Flight", - "path": "flight", - "path_with_namespace": "twitter/flight", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:54.754Z", - "last_activity_at": "2016-01-13T20:27:50.502Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 4, - "name": "Twitter", - "path": "twitter", - "owner_id": null, - "created_at": "2016-01-13T20:19:54.480Z", - "updated_at": "2016-01-13T20:19:54.480Z", - "description": "Id voluptatem ipsa maiores omnis repudiandae et et.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": true, - "open_issues_count": 4, - "permissions": { - "project_access": null, - "group_access": { - "access_level": 10, - "notification_level": 3 - } - } -}, { - "id": 5, - "description": "Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.", - "default_branch": "master", - "tag_list": [], - "public": false, - "archived": false, - "visibility_level": 0, - "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-test.git", - "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-test.git", - "web_url": "http://localhost:3000/gitlab-org/gitlab-test", - "name": "Gitlab Test", - "name_with_namespace": "Gitlab Org / Gitlab Test", - "path": "gitlab-test", - "path_with_namespace": "gitlab-org/gitlab-test", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:53.202Z", - "last_activity_at": "2016-01-13T20:27:41.626Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 3, - "name": "Gitlab Org", - "path": "gitlab-org", - "owner_id": null, - "created_at": "2016-01-13T20:19:48.851Z", - "updated_at": "2016-01-13T20:19:48.851Z", - "description": "Magni mollitia quod quidem soluta nesciunt impedit.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": false, - "open_issues_count": 5, - "permissions": { - "project_access": null, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -}, { - "id": 4, - "description": "Aut molestias quas est ut aperiam officia quod libero.", - "default_branch": "master", - "tag_list": [], - "public": true, - "archived": false, - "visibility_level": 20, - "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-shell.git", - "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-shell.git", - "web_url": "http://localhost:3000/gitlab-org/gitlab-shell", - "name": "Gitlab Shell", - "name_with_namespace": "Gitlab Org / Gitlab Shell", - "path": "gitlab-shell", - "path_with_namespace": "gitlab-org/gitlab-shell", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:51.882Z", - "last_activity_at": "2016-01-13T20:27:35.678Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 3, - "name": "Gitlab Org", - "path": "gitlab-org", - "owner_id": null, - "created_at": "2016-01-13T20:19:48.851Z", - "updated_at": "2016-01-13T20:19:48.851Z", - "description": "Magni mollitia quod quidem soluta nesciunt impedit.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": false, - "open_issues_count": 5, - "permissions": { - "project_access": { - "access_level": 20, - "notification_level": 3 - }, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -}, { - "id": 3, - "description": "Excepturi molestiae quia repellendus omnis est illo illum eligendi.", - "default_branch": "master", - "tag_list": [], - "public": true, - "archived": false, - "visibility_level": 20, - "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ci.git", - "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ci.git", - "web_url": "http://localhost:3000/gitlab-org/gitlab-ci", - "name": "Gitlab Ci", - "name_with_namespace": "Gitlab Org / Gitlab Ci", - "path": "gitlab-ci", - "path_with_namespace": "gitlab-org/gitlab-ci", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:50.346Z", - "last_activity_at": "2016-01-13T20:27:30.115Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 3, - "name": "Gitlab Org", - "path": "gitlab-org", - "owner_id": null, - "created_at": "2016-01-13T20:19:48.851Z", - "updated_at": "2016-01-13T20:19:48.851Z", - "description": "Magni mollitia quod quidem soluta nesciunt impedit.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": false, - "open_issues_count": 3, - "permissions": { - "project_access": null, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -}, { - "id": 2, - "description": "Adipisci quaerat dignissimos enim sed ipsam dolorem quia.", - "default_branch": "master", - "tag_list": [], - "public": false, - "archived": false, - "visibility_level": 10, - "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ce.git", - "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ce.git", - "web_url": "http://localhost:3000/gitlab-org/gitlab-ce", - "name": "Gitlab Ce", - "name_with_namespace": "Gitlab Org / Gitlab Ce", - "path": "gitlab-ce", - "path_with_namespace": "gitlab-org/gitlab-ce", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:49.065Z", - "last_activity_at": "2016-01-13T20:26:58.454Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 3, - "name": "Gitlab Org", - "path": "gitlab-org", - "owner_id": null, - "created_at": "2016-01-13T20:19:48.851Z", - "updated_at": "2016-01-13T20:19:48.851Z", - "description": "Magni mollitia quod quidem soluta nesciunt impedit.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": false, - "open_issues_count": 5, - "permissions": { - "project_access": { - "access_level": 30, - "notification_level": 3 - }, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -}, { - "id": 1, - "description": "Vel voluptatem maxime saepe ex quia.", - "default_branch": "master", - "tag_list": [], - "public": false, - "archived": false, - "visibility_level": 0, - "ssh_url_to_repo": "phil@localhost:documentcloud/underscore.git", - "http_url_to_repo": "http://localhost:3000/documentcloud/underscore.git", - "web_url": "http://localhost:3000/documentcloud/underscore", - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "builds_enabled": true, - "snippets_enabled": false, - "created_at": "2016-01-13T20:19:45.862Z", - "last_activity_at": "2016-01-13T20:25:03.106Z", - "shared_runners_enabled": true, - "creator_id": 1, - "namespace": { - "id": 2, - "name": "Documentcloud", - "path": "documentcloud", - "owner_id": null, - "created_at": "2016-01-13T20:19:44.464Z", - "updated_at": "2016-01-13T20:19:44.464Z", - "description": "Aut impedit perferendis fuga et ipsa repellat cupiditate et.", - "avatar": { - "url": null - } - }, - "avatar_url": null, - "star_count": 0, - "forks_count": 0, - "only_allow_merge_if_pipeline_succeeds": false, - "open_issues_count": 5, - "permissions": { - "project_access": null, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -}] diff --git a/spec/javascripts/fixtures/static/images/green_box.png b/spec/javascripts/fixtures/static/images/green_box.png new file mode 100644 index 00000000000..cd1ff9f9ade Binary files /dev/null and b/spec/javascripts/fixtures/static/images/green_box.png differ diff --git a/spec/javascripts/fixtures/static/images/one_white_pixel.png b/spec/javascripts/fixtures/static/images/one_white_pixel.png new file mode 100644 index 00000000000..073fcf40a18 Binary files /dev/null and b/spec/javascripts/fixtures/static/images/one_white_pixel.png differ diff --git a/spec/javascripts/fixtures/static/images/red_box.png b/spec/javascripts/fixtures/static/images/red_box.png new file mode 100644 index 00000000000..73b2927da0f Binary files /dev/null and b/spec/javascripts/fixtures/static/images/red_box.png differ diff --git a/spec/javascripts/fixtures/static/projects.json b/spec/javascripts/fixtures/static/projects.json new file mode 100644 index 00000000000..68a150f602a --- /dev/null +++ b/spec/javascripts/fixtures/static/projects.json @@ -0,0 +1,445 @@ +[{ + "id": 9, + "description": "", + "default_branch": null, + "tag_list": [], + "public": true, + "archived": false, + "visibility_level": 20, + "ssh_url_to_repo": "phil@localhost:root/test.git", + "http_url_to_repo": "http://localhost:3000/root/test.git", + "web_url": "http://localhost:3000/root/test", + "owner": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", + "web_url": "http://localhost:3000/u/root" + }, + "name": "test", + "name_with_namespace": "Administrator / test", + "path": "test", + "path_with_namespace": "root/test", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-14T19:08:05.364Z", + "last_activity_at": "2016-01-14T19:08:07.418Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 1, + "name": "root", + "path": "root", + "owner_id": 1, + "created_at": "2016-01-13T20:19:44.439Z", + "updated_at": "2016-01-13T20:19:44.439Z", + "description": "", + "avatar": null + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": false, + "open_issues_count": 0, + "permissions": { + "project_access": null, + "group_access": null + } +}, { + "id": 8, + "description": "Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.", + "default_branch": "master", + "tag_list": [], + "public": false, + "archived": false, + "visibility_level": 0, + "ssh_url_to_repo": "phil@localhost:h5bp/html5-boilerplate.git", + "http_url_to_repo": "http://localhost:3000/h5bp/html5-boilerplate.git", + "web_url": "http://localhost:3000/h5bp/html5-boilerplate", + "name": "Html5 Boilerplate", + "name_with_namespace": "H5bp / Html5 Boilerplate", + "path": "html5-boilerplate", + "path_with_namespace": "h5bp/html5-boilerplate", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:57.525Z", + "last_activity_at": "2016-01-13T20:27:57.280Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 5, + "name": "H5bp", + "path": "h5bp", + "owner_id": null, + "created_at": "2016-01-13T20:19:57.239Z", + "updated_at": "2016-01-13T20:19:57.239Z", + "description": "Tempore accusantium possimus aut libero.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": false, + "open_issues_count": 5, + "permissions": { + "project_access": { + "access_level": 10, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +}, { + "id": 7, + "description": "Modi odio mollitia dolorem qui.", + "default_branch": "master", + "tag_list": [], + "public": false, + "archived": false, + "visibility_level": 0, + "ssh_url_to_repo": "phil@localhost:twitter/typeahead-js.git", + "http_url_to_repo": "http://localhost:3000/twitter/typeahead-js.git", + "web_url": "http://localhost:3000/twitter/typeahead-js", + "name": "Typeahead.Js", + "name_with_namespace": "Twitter / Typeahead.Js", + "path": "typeahead-js", + "path_with_namespace": "twitter/typeahead-js", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:56.212Z", + "last_activity_at": "2016-01-13T20:27:51.496Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 4, + "name": "Twitter", + "path": "twitter", + "owner_id": null, + "created_at": "2016-01-13T20:19:54.480Z", + "updated_at": "2016-01-13T20:19:54.480Z", + "description": "Id voluptatem ipsa maiores omnis repudiandae et et.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": true, + "open_issues_count": 4, + "permissions": { + "project_access": null, + "group_access": { + "access_level": 10, + "notification_level": 3 + } + } +}, { + "id": 6, + "description": "Omnis asperiores ipsa et beatae quidem necessitatibus quia.", + "default_branch": "master", + "tag_list": [], + "public": true, + "archived": false, + "visibility_level": 20, + "ssh_url_to_repo": "phil@localhost:twitter/flight.git", + "http_url_to_repo": "http://localhost:3000/twitter/flight.git", + "web_url": "http://localhost:3000/twitter/flight", + "name": "Flight", + "name_with_namespace": "Twitter / Flight", + "path": "flight", + "path_with_namespace": "twitter/flight", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:54.754Z", + "last_activity_at": "2016-01-13T20:27:50.502Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 4, + "name": "Twitter", + "path": "twitter", + "owner_id": null, + "created_at": "2016-01-13T20:19:54.480Z", + "updated_at": "2016-01-13T20:19:54.480Z", + "description": "Id voluptatem ipsa maiores omnis repudiandae et et.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": true, + "open_issues_count": 4, + "permissions": { + "project_access": null, + "group_access": { + "access_level": 10, + "notification_level": 3 + } + } +}, { + "id": 5, + "description": "Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.", + "default_branch": "master", + "tag_list": [], + "public": false, + "archived": false, + "visibility_level": 0, + "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-test.git", + "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-test.git", + "web_url": "http://localhost:3000/gitlab-org/gitlab-test", + "name": "Gitlab Test", + "name_with_namespace": "Gitlab Org / Gitlab Test", + "path": "gitlab-test", + "path_with_namespace": "gitlab-org/gitlab-test", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:53.202Z", + "last_activity_at": "2016-01-13T20:27:41.626Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 3, + "name": "Gitlab Org", + "path": "gitlab-org", + "owner_id": null, + "created_at": "2016-01-13T20:19:48.851Z", + "updated_at": "2016-01-13T20:19:48.851Z", + "description": "Magni mollitia quod quidem soluta nesciunt impedit.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": false, + "open_issues_count": 5, + "permissions": { + "project_access": null, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +}, { + "id": 4, + "description": "Aut molestias quas est ut aperiam officia quod libero.", + "default_branch": "master", + "tag_list": [], + "public": true, + "archived": false, + "visibility_level": 20, + "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-shell.git", + "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-shell.git", + "web_url": "http://localhost:3000/gitlab-org/gitlab-shell", + "name": "Gitlab Shell", + "name_with_namespace": "Gitlab Org / Gitlab Shell", + "path": "gitlab-shell", + "path_with_namespace": "gitlab-org/gitlab-shell", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:51.882Z", + "last_activity_at": "2016-01-13T20:27:35.678Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 3, + "name": "Gitlab Org", + "path": "gitlab-org", + "owner_id": null, + "created_at": "2016-01-13T20:19:48.851Z", + "updated_at": "2016-01-13T20:19:48.851Z", + "description": "Magni mollitia quod quidem soluta nesciunt impedit.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": false, + "open_issues_count": 5, + "permissions": { + "project_access": { + "access_level": 20, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +}, { + "id": 3, + "description": "Excepturi molestiae quia repellendus omnis est illo illum eligendi.", + "default_branch": "master", + "tag_list": [], + "public": true, + "archived": false, + "visibility_level": 20, + "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ci.git", + "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ci.git", + "web_url": "http://localhost:3000/gitlab-org/gitlab-ci", + "name": "Gitlab Ci", + "name_with_namespace": "Gitlab Org / Gitlab Ci", + "path": "gitlab-ci", + "path_with_namespace": "gitlab-org/gitlab-ci", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:50.346Z", + "last_activity_at": "2016-01-13T20:27:30.115Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 3, + "name": "Gitlab Org", + "path": "gitlab-org", + "owner_id": null, + "created_at": "2016-01-13T20:19:48.851Z", + "updated_at": "2016-01-13T20:19:48.851Z", + "description": "Magni mollitia quod quidem soluta nesciunt impedit.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": false, + "open_issues_count": 3, + "permissions": { + "project_access": null, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +}, { + "id": 2, + "description": "Adipisci quaerat dignissimos enim sed ipsam dolorem quia.", + "default_branch": "master", + "tag_list": [], + "public": false, + "archived": false, + "visibility_level": 10, + "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ce.git", + "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ce.git", + "web_url": "http://localhost:3000/gitlab-org/gitlab-ce", + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:49.065Z", + "last_activity_at": "2016-01-13T20:26:58.454Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 3, + "name": "Gitlab Org", + "path": "gitlab-org", + "owner_id": null, + "created_at": "2016-01-13T20:19:48.851Z", + "updated_at": "2016-01-13T20:19:48.851Z", + "description": "Magni mollitia quod quidem soluta nesciunt impedit.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": false, + "open_issues_count": 5, + "permissions": { + "project_access": { + "access_level": 30, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +}, { + "id": 1, + "description": "Vel voluptatem maxime saepe ex quia.", + "default_branch": "master", + "tag_list": [], + "public": false, + "archived": false, + "visibility_level": 0, + "ssh_url_to_repo": "phil@localhost:documentcloud/underscore.git", + "http_url_to_repo": "http://localhost:3000/documentcloud/underscore.git", + "web_url": "http://localhost:3000/documentcloud/underscore", + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "builds_enabled": true, + "snippets_enabled": false, + "created_at": "2016-01-13T20:19:45.862Z", + "last_activity_at": "2016-01-13T20:25:03.106Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 2, + "name": "Documentcloud", + "path": "documentcloud", + "owner_id": null, + "created_at": "2016-01-13T20:19:44.464Z", + "updated_at": "2016-01-13T20:19:44.464Z", + "description": "Aut impedit perferendis fuga et ipsa repellat cupiditate et.", + "avatar": { + "url": null + } + }, + "avatar_url": null, + "star_count": 0, + "forks_count": 0, + "only_allow_merge_if_pipeline_succeeds": false, + "open_issues_count": 5, + "permissions": { + "project_access": null, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +}] diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 57e31d933ca..8c7820ddb52 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -6,7 +6,7 @@ import '~/lib/utils/common_utils'; describe('glDropdown', function describeDropdown() { preloadFixtures('static/gl_dropdown.html'); - loadJSONFixtures('projects.json'); + loadJSONFixtures('static/projects.json'); const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-item'; @@ -67,7 +67,7 @@ describe('glDropdown', function describeDropdown() { loadFixtures('static/gl_dropdown.html'); this.dropdownContainerElement = $('.dropdown.inline'); this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); - this.projectsData = getJSONFixture('projects.json'); + this.projectsData = getJSONFixture('static/projects.json'); }); afterEach(() => { diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js index a820dd2d09c..24b5512b053 100644 --- a/spec/javascripts/test_constants.js +++ b/spec/javascripts/test_constants.js @@ -1,7 +1,7 @@ export const FIXTURES_PATH = '/base/spec/javascripts/fixtures'; export const TEST_HOST = 'http://test.host'; -export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/one_white_pixel.png`; +export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`; -export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/images/green_box.png`; -export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/images/red_box.png`; +export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`; +export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`; diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js index b95183747bb..268ced38f40 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js @@ -9,8 +9,8 @@ describe('ProjectListItem component', () => { let wrapper; let vm; let options; - loadJSONFixtures('projects.json'); - const project = getJSONFixture('projects.json')[0]; + loadJSONFixtures('static/projects.json'); + const project = getJSONFixture('static/projects.json')[0]; beforeEach(() => { options = { diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js index ba9ec8f2f19..34c0cd435cd 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -8,8 +8,8 @@ import { trimText } from 'spec/helpers/vue_component_helper'; describe('ProjectSelector component', () => { let wrapper; let vm; - loadJSONFixtures('projects.json'); - const allProjects = getJSONFixture('projects.json'); + loadJSONFixtures('static/projects.json'); + const allProjects = getJSONFixture('static/projects.json'); const searchResults = allProjects.slice(0, 5); let selected = []; selected = selected.concat(allProjects.slice(0, 3)).concat(allProjects.slice(5, 8)); -- cgit v1.2.1 From c005c97914f1bdff33b0ad77b2e2e855044ee39b Mon Sep 17 00:00:00 2001 From: Martin Hobert Date: Thu, 25 Apr 2019 14:21:38 +0000 Subject: test(Refactored notes tests from Karma to Jest): fix #58829 Added changelog entry Added merge request id --- .../components/notes/placeholder_note_spec.js | 51 +++++++++++++++++++ .../notes/placeholder_system_note_spec.js | 27 ++++++++++ .../components/notes/system_note_spec.js | 59 ++++++++++++++++++++++ .../components/notes/placeholder_note_spec.js | 51 ------------------- .../notes/placeholder_system_note_spec.js | 27 ---------- .../components/notes/system_note_spec.js | 59 ---------------------- 6 files changed, 137 insertions(+), 137 deletions(-) create mode 100644 spec/frontend/vue_shared/components/notes/placeholder_note_spec.js create mode 100644 spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js create mode 100644 spec/frontend/vue_shared/components/notes/system_note_spec.js delete mode 100644 spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js delete mode 100644 spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js delete mode 100644 spec/javascripts/vue_shared/components/notes/system_note_spec.js (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js new file mode 100644 index 00000000000..eafff7f681e --- /dev/null +++ b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js @@ -0,0 +1,51 @@ +import Vue from 'vue'; +import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; +import createStore from '~/notes/stores'; +import { userDataMock } from '../../../../javascripts/notes/mock_data'; + +describe('issue placeholder system note component', () => { + let store; + let vm; + + beforeEach(() => { + const Component = Vue.extend(issuePlaceholderNote); + store = createStore(); + store.dispatch('setUserData', userDataMock); + vm = new Component({ + store, + propsData: { note: { body: 'Foo' } }, + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('user information', () => { + it('should render user avatar with link', () => { + expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual( + userDataMock.path, + ); + + expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual( + `${userDataMock.avatar_url}?width=40`, + ); + }); + }); + + describe('note content', () => { + it('should render note header information', () => { + expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual( + userDataMock.path, + ); + + expect( + vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim(), + ).toEqual(`@${userDataMock.username}`); + }); + + it('should render note body', () => { + expect(vm.$el.querySelector('.note-text p').textContent.trim()).toEqual('Foo'); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js new file mode 100644 index 00000000000..976e38c15ee --- /dev/null +++ b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; + +describe('placeholder system note component', () => { + let PlaceholderSystemNote; + let vm; + + beforeEach(() => { + PlaceholderSystemNote = Vue.extend(placeholderSystemNote); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render system note placeholder with plain text', () => { + vm = mountComponent(PlaceholderSystemNote, { + note: { body: 'This is a placeholder' }, + }); + + expect(vm.$el.tagName).toEqual('LI'); + expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual( + 'This is a placeholder', + ); + }); +}); diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js new file mode 100644 index 00000000000..adcb1c858aa --- /dev/null +++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js @@ -0,0 +1,59 @@ +import Vue from 'vue'; +import issueSystemNote from '~/vue_shared/components/notes/system_note.vue'; +import createStore from '~/notes/stores'; + +describe('system note component', () => { + let vm; + let props; + + beforeEach(() => { + props = { + note: { + id: '1424', + author: { + id: 1, + name: 'Root', + username: 'root', + state: 'active', + avatar_url: 'path', + path: '/root', + }, + note_html: '

      closed

      ', + system_note_icon_name: 'status_closed', + created_at: '2017-08-02T10:51:58.559Z', + }, + }; + + const store = createStore(); + store.dispatch('setTargetNoteHash', `note_${props.note.id}`); + + const Component = Vue.extend(issueSystemNote); + vm = new Component({ + store, + propsData: props, + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a list item with correct id', () => { + expect(vm.$el.getAttribute('id')).toEqual(`note_${props.note.id}`); + }); + + it('should render target class is note is target note', () => { + expect(vm.$el.classList).toContain('target'); + }); + + it('should render svg icon', () => { + expect(vm.$el.querySelector('.timeline-icon svg')).toBeDefined(); + }); + + // Redcarpet Markdown renderer wraps text in `

      ` tags + // we need to strip them because they break layout of commit lists in system notes: + // https://gitlab.com/gitlab-org/gitlab-ce/uploads/b07a10670919254f0220d3ff5c1aa110/jqzI.png + it('removes wrapping paragraph from note HTML', () => { + expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('closed'); + }); +}); diff --git a/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js b/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js deleted file mode 100644 index 45f131194ca..00000000000 --- a/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js +++ /dev/null @@ -1,51 +0,0 @@ -import Vue from 'vue'; -import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; -import createStore from '~/notes/stores'; -import { userDataMock } from '../../../notes/mock_data'; - -describe('issue placeholder system note component', () => { - let store; - let vm; - - beforeEach(() => { - const Component = Vue.extend(issuePlaceholderNote); - store = createStore(); - store.dispatch('setUserData', userDataMock); - vm = new Component({ - store, - propsData: { note: { body: 'Foo' } }, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('user information', () => { - it('should render user avatar with link', () => { - expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual( - userDataMock.path, - ); - - expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual( - `${userDataMock.avatar_url}?width=40`, - ); - }); - }); - - describe('note content', () => { - it('should render note header information', () => { - expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual( - userDataMock.path, - ); - - expect( - vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim(), - ).toEqual(`@${userDataMock.username}`); - }); - - it('should render note body', () => { - expect(vm.$el.querySelector('.note-text p').textContent.trim()).toEqual('Foo'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js b/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js deleted file mode 100644 index 6013e85811a..00000000000 --- a/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import Vue from 'vue'; -import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('placeholder system note component', () => { - let PlaceholderSystemNote; - let vm; - - beforeEach(() => { - PlaceholderSystemNote = Vue.extend(placeholderSystemNote); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render system note placeholder with plain text', () => { - vm = mountComponent(PlaceholderSystemNote, { - note: { body: 'This is a placeholder' }, - }); - - expect(vm.$el.tagName).toEqual('LI'); - expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual( - 'This is a placeholder', - ); - }); -}); diff --git a/spec/javascripts/vue_shared/components/notes/system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js deleted file mode 100644 index adcb1c858aa..00000000000 --- a/spec/javascripts/vue_shared/components/notes/system_note_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import Vue from 'vue'; -import issueSystemNote from '~/vue_shared/components/notes/system_note.vue'; -import createStore from '~/notes/stores'; - -describe('system note component', () => { - let vm; - let props; - - beforeEach(() => { - props = { - note: { - id: '1424', - author: { - id: 1, - name: 'Root', - username: 'root', - state: 'active', - avatar_url: 'path', - path: '/root', - }, - note_html: '

      closed

      ', - system_note_icon_name: 'status_closed', - created_at: '2017-08-02T10:51:58.559Z', - }, - }; - - const store = createStore(); - store.dispatch('setTargetNoteHash', `note_${props.note.id}`); - - const Component = Vue.extend(issueSystemNote); - vm = new Component({ - store, - propsData: props, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render a list item with correct id', () => { - expect(vm.$el.getAttribute('id')).toEqual(`note_${props.note.id}`); - }); - - it('should render target class is note is target note', () => { - expect(vm.$el.classList).toContain('target'); - }); - - it('should render svg icon', () => { - expect(vm.$el.querySelector('.timeline-icon svg')).toBeDefined(); - }); - - // Redcarpet Markdown renderer wraps text in `

      ` tags - // we need to strip them because they break layout of commit lists in system notes: - // https://gitlab.com/gitlab-org/gitlab-ce/uploads/b07a10670919254f0220d3ff5c1aa110/jqzI.png - it('removes wrapping paragraph from note HTML', () => { - expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('closed'); - }); -}); -- cgit v1.2.1 From 396dbbd415e12d469f6aecaac4144e74fb8e7ec0 Mon Sep 17 00:00:00 2001 From: Martin Hobert Date: Thu, 25 Apr 2019 16:33:40 +0200 Subject: refactor(issue): Refactored issue tests from Karma to Jest re #58827 Added changelog Added sync false, and removed unneeded destroyers --- .../components/issue/issue_assignees_spec.js | 114 ++++++++++ .../components/issue/issue_milestone_spec.js | 172 +++++++++++++++ .../components/issue/issue_warning_spec.js | 59 ++++++ .../components/issue/related_issuable_item_spec.js | 198 +++++++++++++++++ .../components/issue/issue_assignees_spec.js | 114 ---------- .../components/issue/issue_milestone_spec.js | 234 --------------------- .../components/issue/issue_warning_spec.js | 55 ----- .../components/issue/related_issuable_item_spec.js | 194 ----------------- 8 files changed, 543 insertions(+), 597 deletions(-) create mode 100644 spec/frontend/vue_shared/components/issue/issue_assignees_spec.js create mode 100644 spec/frontend/vue_shared/components/issue/issue_milestone_spec.js create mode 100644 spec/frontend/vue_shared/components/issue/issue_warning_spec.js create mode 100644 spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js delete mode 100644 spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js delete mode 100644 spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js delete mode 100644 spec/javascripts/vue_shared/components/issue/issue_warning_spec.js delete mode 100644 spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js new file mode 100644 index 00000000000..d1de98f4a15 --- /dev/null +++ b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js @@ -0,0 +1,114 @@ +import Vue from 'vue'; + +import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; +import { mockAssigneesList } from '../../../../javascripts/boards/mock_data'; + +const createComponent = (assignees = mockAssigneesList, cssClass = '') => { + const Component = Vue.extend(IssueAssignees); + + return mountComponent(Component, { + assignees, + cssClass, + }); +}; + +describe('IssueAssigneesComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('data', () => { + it('returns default data props', () => { + expect(vm.maxVisibleAssignees).toBe(2); + expect(vm.maxAssigneeAvatars).toBe(3); + expect(vm.maxAssignees).toBe(99); + }); + }); + + describe('computed', () => { + describe('countOverLimit', () => { + it('should return difference between assignees count and maxVisibleAssignees', () => { + expect(vm.countOverLimit).toBe(mockAssigneesList.length - vm.maxVisibleAssignees); + }); + }); + + describe('assigneesToShow', () => { + it('should return assignees containing only 2 items when count more than maxAssigneeAvatars', () => { + expect(vm.assigneesToShow.length).toBe(2); + }); + + it('should return all assignees as it is when count less than maxAssigneeAvatars', () => { + vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees + + expect(vm.assigneesToShow.length).toBe(3); + }); + }); + + describe('assigneesCounterTooltip', () => { + it('should return string containing count of remaining assignees when count more than maxAssigneeAvatars', () => { + expect(vm.assigneesCounterTooltip).toBe('3 more assignees'); + }); + }); + + describe('shouldRenderAssigneesCounter', () => { + it('should return `false` when assignees count less than maxAssigneeAvatars', () => { + vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees + + expect(vm.shouldRenderAssigneesCounter).toBe(false); + }); + + it('should return `true` when assignees count more than maxAssigneeAvatars', () => { + expect(vm.shouldRenderAssigneesCounter).toBe(true); + }); + }); + + describe('assigneeCounterLabel', () => { + it('should return count of additional assignees total assignees count more than maxAssigneeAvatars', () => { + expect(vm.assigneeCounterLabel).toBe('+3'); + }); + }); + }); + + describe('methods', () => { + describe('avatarUrlTitle', () => { + it('returns string containing alt text for assignee avatar', () => { + expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Avatar for Terrell Graham'); + }); + }); + }); + + describe('template', () => { + it('renders component root element with class `issue-assignees`', () => { + expect(vm.$el.classList.contains('issue-assignees')).toBe(true); + }); + + it('renders assignee avatars', () => { + expect(vm.$el.querySelectorAll('.user-avatar-link').length).toBe(2); + }); + + it('renders assignee tooltips', () => { + const tooltipText = vm.$el + .querySelectorAll('.user-avatar-link')[0] + .querySelector('.js-assignee-tooltip').innerText; + + expect(tooltipText).toContain('Assignee'); + expect(tooltipText).toContain('Terrell Graham'); + expect(tooltipText).toContain('@monserrate.gleichner'); + }); + + it('renders additional assignees count', () => { + const avatarCounterEl = vm.$el.querySelector('.avatar-counter'); + + expect(avatarCounterEl.innerText.trim()).toBe('+3'); + expect(avatarCounterEl.getAttribute('data-original-title')).toBe('3 more assignees'); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js new file mode 100644 index 00000000000..2e93ec412b9 --- /dev/null +++ b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js @@ -0,0 +1,172 @@ +import Vue from 'vue'; +import { mount } from '@vue/test-utils'; + +import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue'; + +import { mockMilestone } from '../../../../javascripts/boards/mock_data'; + +const createComponent = (milestone = mockMilestone) => { + const Component = Vue.extend(IssueMilestone); + + return mount(Component, { + propsData: { + milestone, + }, + sync: false, + }); +}; + +describe('IssueMilestoneComponent', () => { + let wrapper; + let vm; + + beforeEach(done => { + wrapper = createComponent(); + + ({ vm } = wrapper); + + Vue.nextTick(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('computed', () => { + describe('isMilestoneStarted', () => { + it('should return `false` when milestoneStart prop is not defined', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + start_date: '', + }), + }); + + expect(wrapper.vm.isMilestoneStarted).toBe(false); + }); + + it('should return `true` when milestone start date is past current date', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + start_date: '1990-07-22', + }), + }); + + expect(wrapper.vm.isMilestoneStarted).toBe(true); + }); + }); + + describe('isMilestonePastDue', () => { + it('should return `false` when milestoneDue prop is not defined', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + due_date: '', + }), + }); + + expect(wrapper.vm.isMilestonePastDue).toBe(false); + }); + + it('should return `true` when milestone due is past current date', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + due_date: '1990-07-22', + }), + }); + + expect(wrapper.vm.isMilestonePastDue).toBe(true); + }); + }); + + describe('milestoneDatesAbsolute', () => { + it('returns string containing absolute milestone due date', () => { + expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)'); + }); + + it('returns string containing absolute milestone start date when due date is not present', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + due_date: '', + }), + }); + + expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)'); + }); + + it('returns empty string when both milestone start and due dates are not present', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + start_date: '', + due_date: '', + }), + }); + + expect(wrapper.vm.milestoneDatesAbsolute).toBe(''); + }); + }); + + describe('milestoneDatesHuman', () => { + it('returns string containing milestone due date when date is yet to be due', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + due_date: `${new Date().getFullYear() + 10}-01-01`, + }), + }); + + expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining'); + }); + + it('returns string containing milestone start date when date has already started and due date is not present', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + start_date: '1990-07-22', + due_date: '', + }), + }); + + expect(wrapper.vm.milestoneDatesHuman).toContain('Started'); + }); + + it('returns string containing milestone start date when date is yet to start and due date is not present', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + start_date: `${new Date().getFullYear() + 10}-01-01`, + due_date: '', + }), + }); + + expect(wrapper.vm.milestoneDatesHuman).toContain('Starts'); + }); + + it('returns empty string when milestone start and due dates are not present', () => { + wrapper.setProps({ + milestone: Object.assign({}, mockMilestone, { + start_date: '', + due_date: '', + }), + }); + + expect(wrapper.vm.milestoneDatesHuman).toBe(''); + }); + }); + }); + + describe('template', () => { + it('renders component root element with class `issue-milestone-details`', () => { + expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true); + }); + + it('renders milestone icon', () => { + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock'); + }); + + it('renders milestone title', () => { + expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title); + }); + + it('renders milestone tooltip', () => { + expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain( + mockMilestone.title, + ); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js new file mode 100644 index 00000000000..4a8de5fc4f1 --- /dev/null +++ b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js @@ -0,0 +1,59 @@ +import Vue from 'vue'; +import issueWarning from '~/vue_shared/components/issue/issue_warning.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; + +const IssueWarning = Vue.extend(issueWarning); + +function formatWarning(string) { + // Replace newlines with a space then replace multiple spaces with one space + return string + .trim() + .replace(/\n/g, ' ') + .replace(/\s\s+/g, ' '); +} + +describe('Issue Warning Component', () => { + describe('isLocked', () => { + it('should render locked issue warning information', () => { + const vm = mountComponent(IssueWarning, { + isLocked: true, + }); + + expect( + vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), + ).toMatch(/lock$/); + expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( + 'This issue is locked. Only project members can comment.', + ); + }); + }); + + describe('isConfidential', () => { + it('should render confidential issue warning information', () => { + const vm = mountComponent(IssueWarning, { + isConfidential: true, + }); + + expect( + vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), + ).toMatch(/eye-slash$/); + expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( + 'This is a confidential issue. Your comment will not be visible to the public.', + ); + }); + }); + + describe('isLocked and isConfidential', () => { + it('should render locked and confidential issue warning information', () => { + const vm = mountComponent(IssueWarning, { + isLocked: true, + isConfidential: true, + }); + + expect(vm.$el.querySelector('.icon')).toBeFalsy(); + expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( + "This issue is confidential and locked. People without permission will never get a notification and won't be able to comment.", + ); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js new file mode 100644 index 00000000000..e43d5301a50 --- /dev/null +++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js @@ -0,0 +1,198 @@ +import Vue from 'vue'; +import { formatDate } from '~/lib/utils/datetime_utility'; +import { mount, createLocalVue } from '@vue/test-utils'; +import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; +import { + defaultAssignees, + defaultMilestone, +} from '../../../../javascripts/vue_shared/components/issue/related_issuable_mock_data'; + +describe('RelatedIssuableItem', () => { + let wrapper; + const props = { + idKey: 1, + displayReference: 'gitlab-org/gitlab-test#1', + pathIdSeparator: '#', + path: `${gl.TEST_HOST}/path`, + title: 'title', + confidential: true, + dueDate: '1990-12-31', + weight: 10, + createdAt: '2018-12-01T00:00:00.00Z', + milestone: defaultMilestone, + assignees: defaultAssignees, + eventNamespace: 'relatedIssue', + }; + const slots = { + dueDate: '

      ', + weight: '
      ', + }; + + beforeEach(() => { + const localVue = createLocalVue(); + + wrapper = mount(localVue.extend(RelatedIssuableItem), { + localVue, + slots, + sync: false, + propsData: props, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('contains issuable-info-container class when canReorder is false', () => { + expect(wrapper.props('canReorder')).toBe(false); + expect(wrapper.find('.issuable-info-container').exists()).toBe(true); + }); + + it('does not render token state', () => { + expect(wrapper.find('.text-secondary svg').exists()).toBe(false); + }); + + it('does not render remove button', () => { + expect(wrapper.find({ ref: 'removeButton' }).exists()).toBe(false); + }); + + describe('token title', () => { + it('links to computedPath', () => { + expect(wrapper.find('.item-title a').attributes('href')).toEqual(wrapper.props('path')); + }); + + it('renders confidential icon', () => { + expect(wrapper.find('.confidential-icon').exists()).toBe(true); + }); + + it('renders title', () => { + expect(wrapper.find('.item-title a').text()).toEqual(props.title); + }); + }); + + describe('token state', () => { + let tokenState; + + beforeEach(done => { + wrapper.setProps({ state: 'opened' }); + + Vue.nextTick(() => { + tokenState = wrapper.find('.issue-token-state-icon-open'); + + done(); + }); + }); + + it('renders if hasState', () => { + expect(tokenState.exists()).toBe(true); + }); + + it('renders state title', () => { + const stateTitle = tokenState.attributes('data-original-title'); + const formatedCreateDate = formatDate(props.createdAt); + + expect(stateTitle).toContain('Opened'); + + expect(stateTitle).toContain(`${formatedCreateDate}`); + }); + + it('renders aria label', () => { + expect(tokenState.attributes('aria-label')).toEqual('opened'); + }); + + it('renders open icon when open state', () => { + expect(tokenState.classes('issue-token-state-icon-open')).toBe(true); + }); + + it('renders close icon when close state', done => { + wrapper.setProps({ + state: 'closed', + closedAt: '2018-12-01T00:00:00.00Z', + }); + + Vue.nextTick(() => { + expect(tokenState.classes('issue-token-state-icon-closed')).toBe(true); + + done(); + }); + }); + }); + + describe('token metadata', () => { + let tokenMetadata; + + beforeEach(done => { + Vue.nextTick(() => { + tokenMetadata = wrapper.find('.item-meta'); + + done(); + }); + }); + + it('renders item path and ID', () => { + const pathAndID = tokenMetadata.find('.item-path-id').text(); + + expect(pathAndID).toContain('gitlab-org/gitlab-test'); + expect(pathAndID).toContain('#1'); + }); + + it('renders milestone icon and name', () => { + const milestoneIcon = tokenMetadata.find('.item-milestone svg use'); + const milestoneTitle = tokenMetadata.find('.item-milestone .milestone-title'); + + expect(milestoneIcon.attributes('href')).toContain('clock'); + expect(milestoneTitle.text()).toContain('Milestone title'); + }); + + it('renders due date component', () => { + expect(tokenMetadata.find('.js-due-date-slot').exists()).toBe(true); + }); + + it('renders weight component', () => { + expect(tokenMetadata.find('.js-weight-slot').exists()).toBe(true); + }); + }); + + describe('token assignees', () => { + it('renders assignees avatars', () => { + expect(wrapper.findAll('.item-assignees .user-avatar-link').length).toBe(2); + expect(wrapper.find('.item-assignees .avatar-counter').text()).toContain('+2'); + }); + }); + + describe('remove button', () => { + let removeBtn; + + beforeEach(done => { + wrapper.setProps({ canRemove: true }); + Vue.nextTick(() => { + removeBtn = wrapper.find({ ref: 'removeButton' }); + + done(); + }); + }); + + it('renders if canRemove', () => { + expect(removeBtn.exists()).toBe(true); + }); + + it('renders disabled button when removeDisabled', done => { + wrapper.vm.removeDisabled = true; + + Vue.nextTick(() => { + expect(removeBtn.attributes('disabled')).toEqual('disabled'); + + done(); + }); + }); + + it('triggers onRemoveRequest when clicked', () => { + removeBtn.trigger('click'); + + const { relatedIssueRemoveRequest } = wrapper.emitted(); + + expect(relatedIssueRemoveRequest.length).toBe(1); + expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js b/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js deleted file mode 100644 index 9eac75fac96..00000000000 --- a/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import Vue from 'vue'; - -import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { mockAssigneesList } from 'spec/boards/mock_data'; - -const createComponent = (assignees = mockAssigneesList, cssClass = '') => { - const Component = Vue.extend(IssueAssignees); - - return mountComponent(Component, { - assignees, - cssClass, - }); -}; - -describe('IssueAssigneesComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('data', () => { - it('returns default data props', () => { - expect(vm.maxVisibleAssignees).toBe(2); - expect(vm.maxAssigneeAvatars).toBe(3); - expect(vm.maxAssignees).toBe(99); - }); - }); - - describe('computed', () => { - describe('countOverLimit', () => { - it('should return difference between assignees count and maxVisibleAssignees', () => { - expect(vm.countOverLimit).toBe(mockAssigneesList.length - vm.maxVisibleAssignees); - }); - }); - - describe('assigneesToShow', () => { - it('should return assignees containing only 2 items when count more than maxAssigneeAvatars', () => { - expect(vm.assigneesToShow.length).toBe(2); - }); - - it('should return all assignees as it is when count less than maxAssigneeAvatars', () => { - vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees - - expect(vm.assigneesToShow.length).toBe(3); - }); - }); - - describe('assigneesCounterTooltip', () => { - it('should return string containing count of remaining assignees when count more than maxAssigneeAvatars', () => { - expect(vm.assigneesCounterTooltip).toBe('3 more assignees'); - }); - }); - - describe('shouldRenderAssigneesCounter', () => { - it('should return `false` when assignees count less than maxAssigneeAvatars', () => { - vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees - - expect(vm.shouldRenderAssigneesCounter).toBe(false); - }); - - it('should return `true` when assignees count more than maxAssigneeAvatars', () => { - expect(vm.shouldRenderAssigneesCounter).toBe(true); - }); - }); - - describe('assigneeCounterLabel', () => { - it('should return count of additional assignees total assignees count more than maxAssigneeAvatars', () => { - expect(vm.assigneeCounterLabel).toBe('+3'); - }); - }); - }); - - describe('methods', () => { - describe('avatarUrlTitle', () => { - it('returns string containing alt text for assignee avatar', () => { - expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Avatar for Terrell Graham'); - }); - }); - }); - - describe('template', () => { - it('renders component root element with class `issue-assignees`', () => { - expect(vm.$el.classList.contains('issue-assignees')).toBe(true); - }); - - it('renders assignee avatars', () => { - expect(vm.$el.querySelectorAll('.user-avatar-link').length).toBe(2); - }); - - it('renders assignee tooltips', () => { - const tooltipText = vm.$el - .querySelectorAll('.user-avatar-link')[0] - .querySelector('.js-assignee-tooltip').innerText; - - expect(tooltipText).toContain('Assignee'); - expect(tooltipText).toContain('Terrell Graham'); - expect(tooltipText).toContain('@monserrate.gleichner'); - }); - - it('renders additional assignees count', () => { - const avatarCounterEl = vm.$el.querySelector('.avatar-counter'); - - expect(avatarCounterEl.innerText.trim()).toBe('+3'); - expect(avatarCounterEl.getAttribute('data-original-title')).toBe('3 more assignees'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js b/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js deleted file mode 100644 index 8fca2637326..00000000000 --- a/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js +++ /dev/null @@ -1,234 +0,0 @@ -import Vue from 'vue'; - -import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { mockMilestone } from 'spec/boards/mock_data'; - -const createComponent = (milestone = mockMilestone) => { - const Component = Vue.extend(IssueMilestone); - - return mountComponent(Component, { - milestone, - }); -}; - -describe('IssueMilestoneComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('isMilestoneStarted', () => { - it('should return `false` when milestoneStart prop is not defined', done => { - const vmStartUndefined = createComponent( - Object.assign({}, mockMilestone, { - start_date: '', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmStartUndefined.isMilestoneStarted).toBe(false); - }) - .then(done) - .catch(done.fail); - - vmStartUndefined.$destroy(); - }); - - it('should return `true` when milestone start date is past current date', done => { - const vmStarted = createComponent( - Object.assign({}, mockMilestone, { - start_date: '1990-07-22', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmStarted.isMilestoneStarted).toBe(true); - }) - .then(done) - .catch(done.fail); - - vmStarted.$destroy(); - }); - }); - - describe('isMilestonePastDue', () => { - it('should return `false` when milestoneDue prop is not defined', done => { - const vmDueUndefined = createComponent( - Object.assign({}, mockMilestone, { - due_date: '', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmDueUndefined.isMilestonePastDue).toBe(false); - }) - .then(done) - .catch(done.fail); - - vmDueUndefined.$destroy(); - }); - - it('should return `true` when milestone due is past current date', done => { - const vmPastDue = createComponent( - Object.assign({}, mockMilestone, { - due_date: '1990-07-22', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmPastDue.isMilestonePastDue).toBe(true); - }) - .then(done) - .catch(done.fail); - - vmPastDue.$destroy(); - }); - }); - - describe('milestoneDatesAbsolute', () => { - it('returns string containing absolute milestone due date', () => { - expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)'); - }); - - it('returns string containing absolute milestone start date when due date is not present', done => { - const vmDueUndefined = createComponent( - Object.assign({}, mockMilestone, { - due_date: '', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmDueUndefined.milestoneDatesAbsolute).toBe('(January 1, 2018)'); - }) - .then(done) - .catch(done.fail); - - vmDueUndefined.$destroy(); - }); - - it('returns empty string when both milestone start and due dates are not present', done => { - const vmDatesUndefined = createComponent( - Object.assign({}, mockMilestone, { - start_date: '', - due_date: '', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmDatesUndefined.milestoneDatesAbsolute).toBe(''); - }) - .then(done) - .catch(done.fail); - - vmDatesUndefined.$destroy(); - }); - }); - - describe('milestoneDatesHuman', () => { - it('returns string containing milestone due date when date is yet to be due', done => { - const vmFuture = createComponent( - Object.assign({}, mockMilestone, { - due_date: `${new Date().getFullYear() + 10}-01-01`, - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmFuture.milestoneDatesHuman).toContain('years remaining'); - }) - .then(done) - .catch(done.fail); - - vmFuture.$destroy(); - }); - - it('returns string containing milestone start date when date has already started and due date is not present', done => { - const vmStarted = createComponent( - Object.assign({}, mockMilestone, { - start_date: '1990-07-22', - due_date: '', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmStarted.milestoneDatesHuman).toContain('Started'); - }) - .then(done) - .catch(done.fail); - - vmStarted.$destroy(); - }); - - it('returns string containing milestone start date when date is yet to start and due date is not present', done => { - const vmStarts = createComponent( - Object.assign({}, mockMilestone, { - start_date: `${new Date().getFullYear() + 10}-01-01`, - due_date: '', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmStarts.milestoneDatesHuman).toContain('Starts'); - }) - .then(done) - .catch(done.fail); - - vmStarts.$destroy(); - }); - - it('returns empty string when milestone start and due dates are not present', done => { - const vmDatesUndefined = createComponent( - Object.assign({}, mockMilestone, { - start_date: '', - due_date: '', - }), - ); - - Vue.nextTick() - .then(() => { - expect(vmDatesUndefined.milestoneDatesHuman).toBe(''); - }) - .then(done) - .catch(done.fail); - - vmDatesUndefined.$destroy(); - }); - }); - }); - - describe('template', () => { - it('renders component root element with class `issue-milestone-details`', () => { - expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true); - }); - - it('renders milestone icon', () => { - expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock'); - }); - - it('renders milestone title', () => { - expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title); - }); - - it('renders milestone tooltip', () => { - expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain( - mockMilestone.title, - ); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js deleted file mode 100644 index aa7d6ea2e34..00000000000 --- a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js +++ /dev/null @@ -1,55 +0,0 @@ -import Vue from 'vue'; -import issueWarning from '~/vue_shared/components/issue/issue_warning.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const IssueWarning = Vue.extend(issueWarning); - -function formatWarning(string) { - // Replace newlines with a space then replace multiple spaces with one space - return string - .trim() - .replace(/\n/g, ' ') - .replace(/\s\s+/g, ' '); -} - -describe('Issue Warning Component', () => { - describe('isLocked', () => { - it('should render locked issue warning information', () => { - const vm = mountComponent(IssueWarning, { - isLocked: true, - }); - - expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/lock$/); - expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - 'This issue is locked. Only project members can comment.', - ); - }); - }); - - describe('isConfidential', () => { - it('should render confidential issue warning information', () => { - const vm = mountComponent(IssueWarning, { - isConfidential: true, - }); - - expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/eye-slash$/); - expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - 'This is a confidential issue. Your comment will not be visible to the public.', - ); - }); - }); - - describe('isLocked and isConfidential', () => { - it('should render locked and confidential issue warning information', () => { - const vm = mountComponent(IssueWarning, { - isLocked: true, - isConfidential: true, - }); - - expect(vm.$el.querySelector('.icon')).toBeFalsy(); - expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - "This issue is confidential and locked. People without permission will never get a notification and won't be able to comment.", - ); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js b/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js deleted file mode 100644 index 42198e92eea..00000000000 --- a/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js +++ /dev/null @@ -1,194 +0,0 @@ -import Vue from 'vue'; -import { mount, createLocalVue } from '@vue/test-utils'; -import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; -import { defaultMilestone, defaultAssignees } from './related_issuable_mock_data'; - -describe('RelatedIssuableItem', () => { - let wrapper; - const props = { - idKey: 1, - displayReference: 'gitlab-org/gitlab-test#1', - pathIdSeparator: '#', - path: `${gl.TEST_HOST}/path`, - title: 'title', - confidential: true, - dueDate: '1990-12-31', - weight: 10, - createdAt: '2018-12-01T00:00:00.00Z', - milestone: defaultMilestone, - assignees: defaultAssignees, - eventNamespace: 'relatedIssue', - }; - const slots = { - dueDate: '
      ', - weight: '
      ', - }; - - beforeEach(() => { - const localVue = createLocalVue(); - - wrapper = mount(localVue.extend(RelatedIssuableItem), { - localVue, - slots, - sync: false, - propsData: props, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('contains issuable-info-container class when canReorder is false', () => { - expect(wrapper.props('canReorder')).toBe(false); - expect(wrapper.find('.issuable-info-container').exists()).toBe(true); - }); - - it('does not render token state', () => { - expect(wrapper.find('.text-secondary svg').exists()).toBe(false); - }); - - it('does not render remove button', () => { - expect(wrapper.find({ ref: 'removeButton' }).exists()).toBe(false); - }); - - describe('token title', () => { - it('links to computedPath', () => { - expect(wrapper.find('.item-title a').attributes('href')).toEqual(wrapper.props('path')); - }); - - it('renders confidential icon', () => { - expect(wrapper.find('.confidential-icon').exists()).toBe(true); - }); - - it('renders title', () => { - expect(wrapper.find('.item-title a').text()).toEqual(props.title); - }); - }); - - describe('token state', () => { - let tokenState; - - beforeEach(done => { - wrapper.setProps({ state: 'opened' }); - - Vue.nextTick(() => { - tokenState = wrapper.find('.issue-token-state-icon-open'); - - done(); - }); - }); - - it('renders if hasState', () => { - expect(tokenState.exists()).toBe(true); - }); - - it('renders state title', () => { - const stateTitle = tokenState.attributes('data-original-title'); - - expect(stateTitle).toContain('Opened'); - expect(stateTitle).toContain( - 'Dec 1, 2018 12:00am GMT+0000', - ); - }); - - it('renders aria label', () => { - expect(tokenState.attributes('aria-label')).toEqual('opened'); - }); - - it('renders open icon when open state', () => { - expect(tokenState.classes('issue-token-state-icon-open')).toBe(true); - }); - - it('renders close icon when close state', done => { - wrapper.setProps({ - state: 'closed', - closedAt: '2018-12-01T00:00:00.00Z', - }); - - Vue.nextTick(() => { - expect(tokenState.classes('issue-token-state-icon-closed')).toBe(true); - - done(); - }); - }); - }); - - describe('token metadata', () => { - let tokenMetadata; - - beforeEach(done => { - Vue.nextTick(() => { - tokenMetadata = wrapper.find('.item-meta'); - - done(); - }); - }); - - it('renders item path and ID', () => { - const pathAndID = tokenMetadata.find('.item-path-id').text(); - - expect(pathAndID).toContain('gitlab-org/gitlab-test'); - expect(pathAndID).toContain('#1'); - }); - - it('renders milestone icon and name', () => { - const milestoneIcon = tokenMetadata.find('.item-milestone svg use'); - const milestoneTitle = tokenMetadata.find('.item-milestone .milestone-title'); - - expect(milestoneIcon.attributes('href')).toContain('clock'); - expect(milestoneTitle.text()).toContain('Milestone title'); - }); - - it('renders due date component', () => { - expect(tokenMetadata.find('.js-due-date-slot').exists()).toBe(true); - }); - - it('renders weight component', () => { - expect(tokenMetadata.find('.js-weight-slot').exists()).toBe(true); - }); - }); - - describe('token assignees', () => { - it('renders assignees avatars', () => { - expect(wrapper.findAll('.item-assignees .user-avatar-link').length).toBe(2); - expect(wrapper.find('.item-assignees .avatar-counter').text()).toContain('+2'); - }); - }); - - describe('remove button', () => { - let removeBtn; - - beforeEach(done => { - wrapper.setProps({ canRemove: true }); - Vue.nextTick(() => { - removeBtn = wrapper.find({ ref: 'removeButton' }); - - done(); - }); - }); - - it('renders if canRemove', () => { - expect(removeBtn.exists()).toBe(true); - }); - - it('renders disabled button when removeDisabled', done => { - wrapper.vm.removeDisabled = true; - - Vue.nextTick(() => { - expect(removeBtn.attributes('disabled')).toEqual('disabled'); - - done(); - }); - }); - - it('triggers onRemoveRequest when clicked', () => { - removeBtn.trigger('click'); - - const { relatedIssueRemoveRequest } = wrapper.emitted(); - - expect(relatedIssueRemoveRequest.length).toBe(1); - expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]); - }); - }); -}); -- cgit v1.2.1 From 1dc5b6ab378caa4c61ad405080e276760a49b336 Mon Sep 17 00:00:00 2001 From: Jacques Erasmus Date: Thu, 25 Apr 2019 14:50:57 +0000 Subject: Implement toast component Implemented the toast component for success messages --- .../clusters/components/application_row_spec.js | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js index 038d2be9e98..d26fad54ebe 100644 --- a/spec/frontend/clusters/components/application_row_spec.js +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -1,6 +1,11 @@ import Vue from 'vue'; import eventHub from '~/clusters/event_hub'; -import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '~/clusters/constants'; +import { + APPLICATION_STATUS, + REQUEST_SUBMITTED, + REQUEST_FAILURE, + UPGRADE_REQUESTED, +} from '~/clusters/constants'; import applicationRow from '~/clusters/components/application_row.vue'; import mountComponent from 'helpers/vue_mount_component_helper'; import { DEFAULT_APPLICATION_STATE } from '../services/mock_data'; @@ -314,6 +319,22 @@ describe('Application Row', () => { 'Update failed. Please check the logs and try again.', ); }); + + it('displays a success toast message if application upgrade was successful', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + title: 'GitLab Runner', + requestStatus: UPGRADE_REQUESTED, + status: APPLICATION_STATUS.UPDATE_ERRORED, + }); + + vm.$toast = { show: jest.fn() }; + vm.status = APPLICATION_STATUS.UPDATED; + + vm.$nextTick(() => { + expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.'); + }); + }); }); describe('Version', () => { -- cgit v1.2.1 From 8f59ea33e7d3dceb63bc5953a5b27d5968536287 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 24 Apr 2019 15:17:46 -0300 Subject: Extract EE specific tests for protected branches/tags We have the spec files containing EE specific code for spec/features/protected_branches_spec.rb and spec/features/protected_tags_spec.rb. This commit deletes / extracts the CE part of it. --- spec/features/protected_branches_spec.rb | 26 ++++++-------------------- spec/features/protected_tags_spec.rb | 13 ++++++------- spec/support/protected_branch_helpers.rb | 30 ++++++++++++++++++++++++++++++ spec/support/protected_tag_helpers.rb | 18 ++++++++++++++++++ 4 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 spec/support/protected_branch_helpers.rb create mode 100644 spec/support/protected_tag_helpers.rb (limited to 'spec') diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 0aff916ec83..0dbff5a2701 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'Protected Branches', :js do + include ProtectedBranchHelpers + let(:user) { create(:user) } let(:admin) { create(:admin) } let(:project) { create(:project, :repository) } @@ -150,27 +152,11 @@ describe 'Protected Branches', :js do end describe "access control" do - include_examples "protected branches > access control > CE" - end - end - - def set_protected_branch_name(branch_name) - find(".js-protected-branch-select").click - find(".dropdown-input-field").set(branch_name) - click_on("Create wildcard #{branch_name}") - end - - def set_defaults - find(".js-allowed-to-merge").click - within('.qa-allowed-to-merge-dropdown') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click - end + before do + stub_licensed_features(protected_refs_for_users: false) + end - find(".js-allowed-to-push").click - within('.qa-allowed-to-push-dropdown') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click + include_examples "protected branches > access control > CE" end end end diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index c8e92cd1c07..652542b1719 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'Protected Tags', :js do + include ProtectedTagHelpers + let(:user) { create(:user, :admin) } let(:project) { create(:project, :repository) } @@ -8,13 +10,6 @@ describe 'Protected Tags', :js do sign_in(user) end - def set_protected_tag_name(tag_name) - find(".js-protected-tag-select").click - find(".dropdown-input-field").set(tag_name) - click_on("Create wildcard #{tag_name}") - find('.protected-tags-dropdown .dropdown-menu', visible: false) - end - describe "explicit protected tags" do it "allows creating explicit protected tags" do visit project_protected_tags_path(project) @@ -92,6 +87,10 @@ describe 'Protected Tags', :js do end describe "access control" do + before do + stub_licensed_features(protected_refs_for_users: false) + end + include_examples "protected tags > access control > CE" end end diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb new file mode 100644 index 00000000000..ede16d1c1e2 --- /dev/null +++ b/spec/support/protected_branch_helpers.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ProtectedBranchHelpers + def set_allowed_to(operation, option = 'Maintainers', form: '.js-new-protected-branch') + within form do + select_elem = find(".js-allowed-to-#{operation}") + select_elem.click + + wait_for_requests + + within('.dropdown-content') do + Array(option).each { |opt| click_on(opt) } + end + + # Enhanced select is used in EE, therefore an extra click is needed. + select_elem.click if select_elem['aria-expanded'] == 'true' + end + end + + def set_protected_branch_name(branch_name) + find('.js-protected-branch-select').click + find('.dropdown-input-field').set(branch_name) + click_on("Create wildcard #{branch_name}") + end + + def set_defaults + set_allowed_to('merge') + set_allowed_to('push') + end +end diff --git a/spec/support/protected_tag_helpers.rb b/spec/support/protected_tag_helpers.rb new file mode 100644 index 00000000000..fe9be856286 --- /dev/null +++ b/spec/support/protected_tag_helpers.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative 'protected_branch_helpers' + +module ProtectedTagHelpers + include ::ProtectedBranchHelpers + + def set_allowed_to(operation, option = 'Maintainers', form: '.new-protected-tag') + super + end + + def set_protected_tag_name(tag_name) + find('.js-protected-tag-select').click + find('.dropdown-input-field').set(tag_name) + click_on("Create wildcard #{tag_name}") + find('.protected-tags-dropdown .dropdown-menu', visible: false) + end +end -- cgit v1.2.1 From 642c7252d4e6d4b7ae1d70e9074b56c30e719fc3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 16 Apr 2019 14:54:19 +0100 Subject: Move import projects karma specs to jest Improvements to the stability/validity of the specs. --- .../components/import_projects_table_spec.js | 185 ++++++++++++++ .../components/imported_project_table_row_spec.js | 58 +++++ .../components/provider_repo_table_row_spec.js | 104 ++++++++ .../frontend/import_projects/store/actions_spec.js | 284 +++++++++++++++++++++ .../components/import_projects_table_spec.js | 188 -------------- .../components/imported_project_table_row_spec.js | 51 ---- .../components/provider_repo_table_row_spec.js | 96 ------- .../import_projects/store/actions_spec.js | 284 --------------------- 8 files changed, 631 insertions(+), 619 deletions(-) create mode 100644 spec/frontend/import_projects/components/import_projects_table_spec.js create mode 100644 spec/frontend/import_projects/components/imported_project_table_row_spec.js create mode 100644 spec/frontend/import_projects/components/provider_repo_table_row_spec.js create mode 100644 spec/frontend/import_projects/store/actions_spec.js delete mode 100644 spec/javascripts/import_projects/components/import_projects_table_spec.js delete mode 100644 spec/javascripts/import_projects/components/imported_project_table_row_spec.js delete mode 100644 spec/javascripts/import_projects/components/provider_repo_table_row_spec.js delete mode 100644 spec/javascripts/import_projects/store/actions_spec.js (limited to 'spec') diff --git a/spec/frontend/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_projects/components/import_projects_table_spec.js new file mode 100644 index 00000000000..17a998d0174 --- /dev/null +++ b/spec/frontend/import_projects/components/import_projects_table_spec.js @@ -0,0 +1,185 @@ +import Vuex from 'vuex'; +import { createLocalVue, mount } from '@vue/test-utils'; +import { state, actions, getters, mutations } from '~/import_projects/store'; +import importProjectsTable from '~/import_projects/components/import_projects_table.vue'; +import STATUS_MAP from '~/import_projects/constants'; + +describe('ImportProjectsTable', () => { + let vm; + const providerTitle = 'THE PROVIDER'; + const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' }; + const importedProject = { + id: 1, + fullPath: 'fullPath', + importStatus: 'started', + providerLink: 'providerLink', + importSource: 'importSource', + }; + + function initStore() { + const stubbedActions = Object.assign({}, actions, { + fetchJobs: jest.fn(), + fetchRepos: jest.fn(actions.requestRepos), + fetchImport: jest.fn(actions.requestImport), + }); + + const store = new Vuex.Store({ + state: state(), + actions: stubbedActions, + mutations, + getters, + }); + + return store; + } + + function mountComponent() { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = initStore(); + + const component = mount(importProjectsTable, { + localVue, + store, + propsData: { + providerTitle, + }, + sync: false, + }); + + return component.vm; + } + + beforeEach(() => { + vm = mountComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders a loading icon whilst repos are loading', () => + vm.$nextTick().then(() => { + expect(vm.$el.querySelector('.js-loading-button-icon')).not.toBeNull(); + })); + + it('renders a table with imported projects and provider repos', () => { + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [importedProject], + providerRepos: [providerRepo], + namespaces: [{ path: 'path' }], + }); + + return vm.$nextTick().then(() => { + expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); + expect(vm.$el.querySelector('.table')).not.toBeNull(); + expect(vm.$el.querySelector('.import-jobs-from-col').innerText).toMatch( + `From ${providerTitle}`, + ); + + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); + }); + }); + + it('renders an empty state if there are no imported projects or provider repos', () => { + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [], + providerRepos: [], + namespaces: [], + }); + + return vm.$nextTick().then(() => { + expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); + expect(vm.$el.querySelector('.table')).toBeNull(); + expect(vm.$el.innerText).toMatch(`No ${providerTitle} repositories available to import`); + }); + }); + + it('shows loading spinner when bulk import button is clicked', () => { + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [], + providerRepos: [providerRepo], + namespaces: [{ path: 'path' }], + }); + + return vm + .$nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-imported-project')).toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); + + vm.$el.querySelector('.js-import-all').click(); + }) + .then(() => vm.$nextTick()) + .then(() => { + expect(vm.$el.querySelector('.js-import-all .js-loading-button-icon')).not.toBeNull(); + }); + }); + + it('imports provider repos if bulk import button is clicked', () => { + mountComponent(); + + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [], + providerRepos: [providerRepo], + namespaces: [{ path: 'path' }], + }); + + return vm + .$nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-imported-project')).toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); + + vm.$store.dispatch('receiveImportSuccess', { importedProject, repoId: providerRepo.id }); + }) + .then(() => vm.$nextTick()) + .then(() => { + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector('.js-provider-repo')).toBeNull(); + }); + }); + + it('polls to update the status of imported projects', () => { + const updatedProjects = [ + { + id: importedProject.id, + importStatus: 'finished', + }, + ]; + + vm.$store.dispatch('receiveReposSuccess', { + importedProjects: [importedProject], + providerRepos: [], + namespaces: [{ path: 'path' }], + }); + + return vm + .$nextTick() + .then(() => { + const statusObject = STATUS_MAP[importedProject.importStatus]; + + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + + vm.$store.dispatch('receiveJobsSuccess', updatedProjects); + }) + .then(() => vm.$nextTick()) + .then(() => { + const statusObject = STATUS_MAP[updatedProjects[0].importStatus]; + + expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + }); + }); +}); diff --git a/spec/frontend/import_projects/components/imported_project_table_row_spec.js b/spec/frontend/import_projects/components/imported_project_table_row_spec.js new file mode 100644 index 00000000000..f95acc1edd7 --- /dev/null +++ b/spec/frontend/import_projects/components/imported_project_table_row_spec.js @@ -0,0 +1,58 @@ +import Vuex from 'vuex'; +import createStore from '~/import_projects/store'; +import { createLocalVue, mount } from '@vue/test-utils'; +import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue'; +import STATUS_MAP from '~/import_projects/constants'; + +describe('ImportedProjectTableRow', () => { + let vm; + const project = { + id: 1, + fullPath: 'fullPath', + importStatus: 'finished', + providerLink: 'providerLink', + importSource: 'importSource', + }; + + function mountComponent() { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const component = mount(importedProjectTableRow, { + localVue, + store: createStore(), + propsData: { + project: { + ...project, + }, + }, + sync: false, + }); + + return component.vm; + } + + beforeEach(() => { + vm = mountComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders an imported project table row', () => { + const providerLink = vm.$el.querySelector('.js-provider-link'); + const statusObject = STATUS_MAP[project.importStatus]; + + expect(vm.$el.classList.contains('js-imported-project')).toBe(true); + expect(providerLink.href).toMatch(project.providerLink); + expect(providerLink.textContent).toMatch(project.importSource); + expect(vm.$el.querySelector('.js-full-path').textContent).toMatch(project.fullPath); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + expect(vm.$el.querySelector('.js-go-to-project').href).toMatch(project.fullPath); + }); +}); diff --git a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js new file mode 100644 index 00000000000..02c786d8d0b --- /dev/null +++ b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js @@ -0,0 +1,104 @@ +import Vuex from 'vuex'; +import { createLocalVue, mount } from '@vue/test-utils'; +import { state, actions, getters, mutations } from '~/import_projects/store'; +import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue'; +import STATUS_MAP, { STATUSES } from '~/import_projects/constants'; + +describe('ProviderRepoTableRow', () => { + let vm; + const fetchImport = jest.fn((context, data) => actions.requestImport(context, data)); + const importPath = '/import-path'; + const defaultTargetNamespace = 'user'; + const ciCdOnly = true; + const repo = { + id: 10, + sanitizedName: 'sanitizedName', + fullName: 'fullName', + providerLink: 'providerLink', + }; + + function initStore() { + const stubbedActions = Object.assign({}, actions, { + fetchImport, + }); + + const store = new Vuex.Store({ + state: state(), + actions: stubbedActions, + mutations, + getters, + }); + + return store; + } + + function mountComponent() { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = initStore(); + store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly }); + + const component = mount(providerRepoTableRow, { + localVue, + store, + propsData: { + repo, + }, + sync: false, + }); + + return component.vm; + } + + beforeEach(() => { + vm = mountComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders a provider repo table row', () => { + const providerLink = vm.$el.querySelector('.js-provider-link'); + const statusObject = STATUS_MAP[STATUSES.NONE]; + + expect(vm.$el.classList.contains('js-provider-repo')).toBe(true); + expect(providerLink.href).toMatch(repo.providerLink); + expect(providerLink.textContent).toMatch(repo.fullName); + expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( + statusObject.text, + ); + + expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); + expect(vm.$el.querySelector('.js-import-button')).not.toBeNull(); + }); + + it('renders a select2 namespace select', () => { + const dropdownTrigger = vm.$el.querySelector('.js-namespace-select'); + + expect(dropdownTrigger).not.toBeNull(); + expect(dropdownTrigger.classList.contains('select2-container')).toBe(true); + + dropdownTrigger.click(); + + expect(vm.$el.querySelector('.select2-drop')).not.toBeNull(); + }); + + it('imports repo when clicking import button', () => { + vm.$el.querySelector('.js-import-button').click(); + + return vm.$nextTick().then(() => { + const { calls } = fetchImport.mock; + + // Not using .toBeCalledWith because it expects + // an unmatchable and undefined 3rd argument. + expect(calls.length).toBe(1); + expect(calls[0][1]).toEqual({ + repo, + newName: repo.sanitizedName, + targetNamespace: defaultTargetNamespace, + }); + }); + }); +}); diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js new file mode 100644 index 00000000000..6a7b90788dd --- /dev/null +++ b/spec/frontend/import_projects/store/actions_spec.js @@ -0,0 +1,284 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { + SET_INITIAL_DATA, + REQUEST_REPOS, + RECEIVE_REPOS_SUCCESS, + RECEIVE_REPOS_ERROR, + REQUEST_IMPORT, + RECEIVE_IMPORT_SUCCESS, + RECEIVE_IMPORT_ERROR, + RECEIVE_JOBS_SUCCESS, +} from '~/import_projects/store/mutation_types'; +import { + setInitialData, + requestRepos, + receiveReposSuccess, + receiveReposError, + fetchRepos, + requestImport, + receiveImportSuccess, + receiveImportError, + fetchImport, + receiveJobsSuccess, + fetchJobs, + clearJobsEtagPoll, + stopJobsPolling, +} from '~/import_projects/store/actions'; +import state from '~/import_projects/store/state'; +import testAction from 'helpers/vuex_action_helper'; +import { TEST_HOST } from 'helpers/test_constants'; + +describe('import_projects store actions', () => { + let localState; + const repoId = 1; + const repos = [{ id: 1 }, { id: 2 }]; + const importPayload = { newName: 'newName', targetNamespace: 'targetNamespace', repo: { id: 1 } }; + + beforeEach(() => { + localState = state(); + }); + + describe('setInitialData', () => { + it(`commits ${SET_INITIAL_DATA} mutation`, done => { + const initialData = { + reposPath: 'reposPath', + provider: 'provider', + jobsPath: 'jobsPath', + importPath: 'impapp/assets/javascripts/vue_shared/components/select2_select.vueortPath', + defaultTargetNamespace: 'defaultTargetNamespace', + ciCdOnly: 'ciCdOnly', + canSelectNamespace: 'canSelectNamespace', + }; + + testAction( + setInitialData, + initialData, + localState, + [{ type: SET_INITIAL_DATA, payload: initialData }], + [], + done, + ); + }); + }); + + describe('requestRepos', () => { + it(`requestRepos commits ${REQUEST_REPOS} mutation`, done => { + testAction( + requestRepos, + null, + localState, + [{ type: REQUEST_REPOS, payload: null }], + [], + done, + ); + }); + }); + + describe('receiveReposSuccess', () => { + it(`commits ${RECEIVE_REPOS_SUCCESS} mutation`, done => { + testAction( + receiveReposSuccess, + repos, + localState, + [{ type: RECEIVE_REPOS_SUCCESS, payload: repos }], + [], + done, + ); + }); + }); + + describe('receiveReposError', () => { + it(`commits ${RECEIVE_REPOS_ERROR} mutation`, done => { + testAction(receiveReposError, repos, localState, [{ type: RECEIVE_REPOS_ERROR }], [], done); + }); + }); + + describe('fetchRepos', () => { + let mock; + + beforeEach(() => { + localState.reposPath = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => mock.restore()); + + it('dispatches requestRepos and receiveReposSuccess actions on a successful request', done => { + const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] }; + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload); + + testAction( + fetchRepos, + null, + localState, + [], + [ + { type: 'requestRepos' }, + { + type: 'receiveReposSuccess', + payload: convertObjectPropsToCamelCase(payload, { deep: true }), + }, + { + type: 'fetchJobs', + }, + ], + done, + ); + }); + + it('dispatches requestRepos and receiveReposSuccess actions on an unsuccessful request', done => { + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + + testAction( + fetchRepos, + null, + localState, + [], + [{ type: 'requestRepos' }, { type: 'receiveReposError' }], + done, + ); + }); + }); + + describe('requestImport', () => { + it(`commits ${REQUEST_IMPORT} mutation`, done => { + testAction( + requestImport, + repoId, + localState, + [{ type: REQUEST_IMPORT, payload: repoId }], + [], + done, + ); + }); + }); + + describe('receiveImportSuccess', () => { + it(`commits ${RECEIVE_IMPORT_SUCCESS} mutation`, done => { + const payload = { importedProject: { name: 'imported/project' }, repoId: 2 }; + + testAction( + receiveImportSuccess, + payload, + localState, + [{ type: RECEIVE_IMPORT_SUCCESS, payload }], + [], + done, + ); + }); + }); + + describe('receiveImportError', () => { + it(`commits ${RECEIVE_IMPORT_ERROR} mutation`, done => { + testAction( + receiveImportError, + repoId, + localState, + [{ type: RECEIVE_IMPORT_ERROR, payload: repoId }], + [], + done, + ); + }); + }); + + describe('fetchImport', () => { + let mock; + + beforeEach(() => { + localState.importPath = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => mock.restore()); + + it('dispatches requestImport and receiveImportSuccess actions on a successful request', done => { + const importedProject = { name: 'imported/project' }; + const importRepoId = importPayload.repo.id; + mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject); + + testAction( + fetchImport, + importPayload, + localState, + [], + [ + { type: 'requestImport', payload: importRepoId }, + { + type: 'receiveImportSuccess', + payload: { + importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }), + repoId: importRepoId, + }, + }, + ], + done, + ); + }); + + it('dispatches requestImport and receiveImportSuccess actions on an unsuccessful request', done => { + mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500); + + testAction( + fetchImport, + importPayload, + localState, + [], + [ + { type: 'requestImport', payload: importPayload.repo.id }, + { type: 'receiveImportError', payload: { repoId: importPayload.repo.id } }, + ], + done, + ); + }); + }); + + describe('receiveJobsSuccess', () => { + it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, done => { + testAction( + receiveJobsSuccess, + repos, + localState, + [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }], + [], + done, + ); + }); + }); + + describe('fetchJobs', () => { + let mock; + + beforeEach(() => { + localState.jobsPath = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + stopJobsPolling(); + clearJobsEtagPoll(); + }); + + afterEach(() => mock.restore()); + + it('dispatches requestJobs and receiveJobsSuccess actions on a successful request', done => { + const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }]; + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects); + + testAction( + fetchJobs, + null, + localState, + [], + [ + { + type: 'receiveJobsSuccess', + payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }), + }, + ], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/import_projects/components/import_projects_table_spec.js b/spec/javascripts/import_projects/components/import_projects_table_spec.js deleted file mode 100644 index ab8642bf0dd..00000000000 --- a/spec/javascripts/import_projects/components/import_projects_table_spec.js +++ /dev/null @@ -1,188 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import createStore from '~/import_projects/store'; -import importProjectsTable from '~/import_projects/components/import_projects_table.vue'; -import STATUS_MAP from '~/import_projects/constants'; -import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; - -describe('ImportProjectsTable', () => { - let vm; - let mock; - let store; - const reposPath = '/repos-path'; - const jobsPath = '/jobs-path'; - const providerTitle = 'THE PROVIDER'; - const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' }; - const importedProject = { - id: 1, - fullPath: 'fullPath', - importStatus: 'started', - providerLink: 'providerLink', - importSource: 'importSource', - }; - - function createComponent() { - const ImportProjectsTable = Vue.extend(importProjectsTable); - - const component = new ImportProjectsTable({ - store, - propsData: { - providerTitle, - }, - }).$mount(); - - store.dispatch('stopJobsPolling'); - - return component; - } - - beforeEach(() => { - store = createStore(); - store.dispatch('setInitialData', { reposPath }); - mock = new MockAdapter(axios); - }); - - afterEach(() => { - vm.$destroy(); - mock.restore(); - }); - - it('renders a loading icon whilst repos are loading', done => { - mock.restore(); // Stop the mock adapter from responding to the request, keeping the spinner up - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-loading-button-icon')).not.toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('renders a table with imported projects and provider repos', done => { - const response = { - importedProjects: [importedProject], - providerRepos: [providerRepo], - namespaces: [{ path: 'path' }], - }; - mock.onGet(reposPath).reply(200, response); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); - expect(vm.$el.querySelector('.table')).not.toBeNull(); - expect(vm.$el.querySelector('.import-jobs-from-col').innerText).toMatch( - `From ${providerTitle}`, - ); - - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('renders an empty state if there are no imported projects or provider repos', done => { - const response = { - importedProjects: [], - providerRepos: [], - namespaces: [], - }; - mock.onGet(reposPath).reply(200, response); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull(); - expect(vm.$el.querySelector('.table')).toBeNull(); - expect(vm.$el.innerText).toMatch(`No ${providerTitle} repositories available to import`); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('imports provider repos if bulk import button is clicked', done => { - const importPath = '/import-path'; - const response = { - importedProjects: [], - providerRepos: [providerRepo], - namespaces: [{ path: 'path' }], - }; - - mock.onGet(reposPath).replyOnce(200, response); - mock.onPost(importPath).replyOnce(200, importedProject); - - store.dispatch('setInitialData', { importPath }); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - expect(vm.$el.querySelector('.js-imported-project')).toBeNull(); - expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull(); - - vm.$el.querySelector('.js-import-all').click(); - }) - .then(() => setTimeoutPromise()) - .then(() => { - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector('.js-provider-repo')).toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); - - it('polls to update the status of imported projects', done => { - const importPath = '/import-path'; - const response = { - importedProjects: [importedProject], - providerRepos: [], - namespaces: [{ path: 'path' }], - }; - const updatedProjects = [ - { - id: importedProject.id, - importStatus: 'finished', - }, - ]; - - mock.onGet(reposPath).replyOnce(200, response); - - store.dispatch('setInitialData', { importPath, jobsPath }); - - vm = createComponent(); - - setTimeoutPromise() - .then(() => { - const statusObject = STATUS_MAP[importedProject.importStatus]; - - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); - - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); - - mock.onGet(jobsPath).replyOnce(200, updatedProjects); - return store.dispatch('restartJobsPolling'); - }) - .then(() => setTimeoutPromise()) - .then(() => { - const statusObject = STATUS_MAP[updatedProjects[0].importStatus]; - - expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull(); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); - - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); - }) - .then(() => done()) - .catch(() => done.fail()); - }); -}); diff --git a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js deleted file mode 100644 index 7dac7e9ccc1..00000000000 --- a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js +++ /dev/null @@ -1,51 +0,0 @@ -import Vue from 'vue'; -import createStore from '~/import_projects/store'; -import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue'; -import STATUS_MAP from '~/import_projects/constants'; - -describe('ImportedProjectTableRow', () => { - let vm; - const project = { - id: 1, - fullPath: 'fullPath', - importStatus: 'finished', - providerLink: 'providerLink', - importSource: 'importSource', - }; - - function createComponent() { - const ImportedProjectTableRow = Vue.extend(importedProjectTableRow); - - const store = createStore(); - return new ImportedProjectTableRow({ - store, - propsData: { - project: { - ...project, - }, - }, - }).$mount(); - } - - afterEach(() => { - vm.$destroy(); - }); - - it('renders an imported project table row', () => { - vm = createComponent(); - - const providerLink = vm.$el.querySelector('.js-provider-link'); - const statusObject = STATUS_MAP[project.importStatus]; - - expect(vm.$el.classList.contains('js-imported-project')).toBe(true); - expect(providerLink.href).toMatch(project.providerLink); - expect(providerLink.textContent).toMatch(project.importSource); - expect(vm.$el.querySelector('.js-full-path').textContent).toMatch(project.fullPath); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); - - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); - expect(vm.$el.querySelector('.js-go-to-project').href).toMatch(project.fullPath); - }); -}); diff --git a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js deleted file mode 100644 index 4d2bacd2ad0..00000000000 --- a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js +++ /dev/null @@ -1,96 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import createStore from '~/import_projects/store'; -import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue'; -import STATUS_MAP, { STATUSES } from '~/import_projects/constants'; -import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; - -describe('ProviderRepoTableRow', () => { - let store; - let vm; - const repo = { - id: 10, - sanitizedName: 'sanitizedName', - fullName: 'fullName', - providerLink: 'providerLink', - }; - - function createComponent() { - const ProviderRepoTableRow = Vue.extend(providerRepoTableRow); - - return new ProviderRepoTableRow({ - store, - propsData: { - repo: { - ...repo, - }, - }, - }).$mount(); - } - - beforeEach(() => { - store = createStore(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders a provider repo table row', () => { - vm = createComponent(); - - const providerLink = vm.$el.querySelector('.js-provider-link'); - const statusObject = STATUS_MAP[STATUSES.NONE]; - - expect(vm.$el.classList.contains('js-provider-repo')).toBe(true); - expect(providerLink.href).toMatch(repo.providerLink); - expect(providerLink.textContent).toMatch(repo.fullName); - expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch( - statusObject.text, - ); - - expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull(); - expect(vm.$el.querySelector('.js-import-button')).not.toBeNull(); - }); - - it('renders a select2 namespace select', () => { - vm = createComponent(); - - const dropdownTrigger = vm.$el.querySelector('.js-namespace-select'); - - expect(dropdownTrigger).not.toBeNull(); - expect(dropdownTrigger.classList.contains('select2-container')).toBe(true); - - dropdownTrigger.click(); - - expect(vm.$el.querySelector('.select2-drop')).not.toBeNull(); - }); - - it('imports repo when clicking import button', done => { - const importPath = '/import-path'; - const defaultTargetNamespace = 'user'; - const ciCdOnly = true; - const mock = new MockAdapter(axios); - - store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly }); - mock.onPost(importPath).replyOnce(200); - spyOn(store, 'dispatch').and.returnValue(new Promise(() => {})); - - vm = createComponent(); - - vm.$el.querySelector('.js-import-button').click(); - - setTimeoutPromise() - .then(() => { - expect(store.dispatch).toHaveBeenCalledWith('fetchImport', { - repo, - newName: repo.sanitizedName, - targetNamespace: defaultTargetNamespace, - }); - }) - .then(() => mock.restore()) - .then(done) - .catch(done.fail); - }); -}); diff --git a/spec/javascripts/import_projects/store/actions_spec.js b/spec/javascripts/import_projects/store/actions_spec.js deleted file mode 100644 index 77850ee3283..00000000000 --- a/spec/javascripts/import_projects/store/actions_spec.js +++ /dev/null @@ -1,284 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import { - SET_INITIAL_DATA, - REQUEST_REPOS, - RECEIVE_REPOS_SUCCESS, - RECEIVE_REPOS_ERROR, - REQUEST_IMPORT, - RECEIVE_IMPORT_SUCCESS, - RECEIVE_IMPORT_ERROR, - RECEIVE_JOBS_SUCCESS, -} from '~/import_projects/store/mutation_types'; -import { - setInitialData, - requestRepos, - receiveReposSuccess, - receiveReposError, - fetchRepos, - requestImport, - receiveImportSuccess, - receiveImportError, - fetchImport, - receiveJobsSuccess, - fetchJobs, - clearJobsEtagPoll, - stopJobsPolling, -} from '~/import_projects/store/actions'; -import state from '~/import_projects/store/state'; -import testAction from 'spec/helpers/vuex_action_helper'; -import { TEST_HOST } from 'spec/test_constants'; - -describe('import_projects store actions', () => { - let localState; - const repoId = 1; - const repos = [{ id: 1 }, { id: 2 }]; - const importPayload = { newName: 'newName', targetNamespace: 'targetNamespace', repo: { id: 1 } }; - - beforeEach(() => { - localState = state(); - }); - - describe('setInitialData', () => { - it(`commits ${SET_INITIAL_DATA} mutation`, done => { - const initialData = { - reposPath: 'reposPath', - provider: 'provider', - jobsPath: 'jobsPath', - importPath: 'impapp/assets/javascripts/vue_shared/components/select2_select.vueortPath', - defaultTargetNamespace: 'defaultTargetNamespace', - ciCdOnly: 'ciCdOnly', - canSelectNamespace: 'canSelectNamespace', - }; - - testAction( - setInitialData, - initialData, - localState, - [{ type: SET_INITIAL_DATA, payload: initialData }], - [], - done, - ); - }); - }); - - describe('requestRepos', () => { - it(`requestRepos commits ${REQUEST_REPOS} mutation`, done => { - testAction( - requestRepos, - null, - localState, - [{ type: REQUEST_REPOS, payload: null }], - [], - done, - ); - }); - }); - - describe('receiveReposSuccess', () => { - it(`commits ${RECEIVE_REPOS_SUCCESS} mutation`, done => { - testAction( - receiveReposSuccess, - repos, - localState, - [{ type: RECEIVE_REPOS_SUCCESS, payload: repos }], - [], - done, - ); - }); - }); - - describe('receiveReposError', () => { - it(`commits ${RECEIVE_REPOS_ERROR} mutation`, done => { - testAction(receiveReposError, repos, localState, [{ type: RECEIVE_REPOS_ERROR }], [], done); - }); - }); - - describe('fetchRepos', () => { - let mock; - - beforeEach(() => { - localState.reposPath = `${TEST_HOST}/endpoint.json`; - mock = new MockAdapter(axios); - }); - - afterEach(() => mock.restore()); - - it('dispatches requestRepos and receiveReposSuccess actions on a successful request', done => { - const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] }; - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload); - - testAction( - fetchRepos, - null, - localState, - [], - [ - { type: 'requestRepos' }, - { - type: 'receiveReposSuccess', - payload: convertObjectPropsToCamelCase(payload, { deep: true }), - }, - { - type: 'fetchJobs', - }, - ], - done, - ); - }); - - it('dispatches requestRepos and receiveReposSuccess actions on an unsuccessful request', done => { - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); - - testAction( - fetchRepos, - null, - localState, - [], - [{ type: 'requestRepos' }, { type: 'receiveReposError' }], - done, - ); - }); - }); - - describe('requestImport', () => { - it(`commits ${REQUEST_IMPORT} mutation`, done => { - testAction( - requestImport, - repoId, - localState, - [{ type: REQUEST_IMPORT, payload: repoId }], - [], - done, - ); - }); - }); - - describe('receiveImportSuccess', () => { - it(`commits ${RECEIVE_IMPORT_SUCCESS} mutation`, done => { - const payload = { importedProject: { name: 'imported/project' }, repoId: 2 }; - - testAction( - receiveImportSuccess, - payload, - localState, - [{ type: RECEIVE_IMPORT_SUCCESS, payload }], - [], - done, - ); - }); - }); - - describe('receiveImportError', () => { - it(`commits ${RECEIVE_IMPORT_ERROR} mutation`, done => { - testAction( - receiveImportError, - repoId, - localState, - [{ type: RECEIVE_IMPORT_ERROR, payload: repoId }], - [], - done, - ); - }); - }); - - describe('fetchImport', () => { - let mock; - - beforeEach(() => { - localState.importPath = `${TEST_HOST}/endpoint.json`; - mock = new MockAdapter(axios); - }); - - afterEach(() => mock.restore()); - - it('dispatches requestImport and receiveImportSuccess actions on a successful request', done => { - const importedProject = { name: 'imported/project' }; - const importRepoId = importPayload.repo.id; - mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject); - - testAction( - fetchImport, - importPayload, - localState, - [], - [ - { type: 'requestImport', payload: importRepoId }, - { - type: 'receiveImportSuccess', - payload: { - importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }), - repoId: importRepoId, - }, - }, - ], - done, - ); - }); - - it('dispatches requestImport and receiveImportSuccess actions on an unsuccessful request', done => { - mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500); - - testAction( - fetchImport, - importPayload, - localState, - [], - [ - { type: 'requestImport', payload: importPayload.repo.id }, - { type: 'receiveImportError', payload: { repoId: importPayload.repo.id } }, - ], - done, - ); - }); - }); - - describe('receiveJobsSuccess', () => { - it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, done => { - testAction( - receiveJobsSuccess, - repos, - localState, - [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }], - [], - done, - ); - }); - }); - - describe('fetchJobs', () => { - let mock; - - beforeEach(() => { - localState.jobsPath = `${TEST_HOST}/endpoint.json`; - mock = new MockAdapter(axios); - }); - - afterEach(() => { - stopJobsPolling(); - clearJobsEtagPoll(); - }); - - afterEach(() => mock.restore()); - - it('dispatches requestJobs and receiveJobsSuccess actions on a successful request', done => { - const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }]; - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects); - - testAction( - fetchJobs, - null, - localState, - [], - [ - { - type: 'receiveJobsSuccess', - payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }), - }, - ], - done, - ); - }); - }); -}); -- cgit v1.2.1 From d60d50d6baa285d869ab1318c70a3baf0bab2701 Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Wed, 24 Apr 2019 11:12:20 -0600 Subject: Use wiki partial in search results --- spec/controllers/search_controller_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 9c60f0fcd4d..4634d1d4bb3 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -11,6 +11,30 @@ describe SearchController do sign_in(user) end + context 'uses the right partials depending on scope' do + using RSpec::Parameterized::TableSyntax + render_views + + set(:project) { create(:project, :public, :repository, :wiki_repo) } + + subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) } + + where(:partial, :scope) do + '_blob' | :blobs + '_wiki_blob' | :wiki_blobs + '_commit' | :commits + end + + with_them do + it do + project_wiki = create(:project_wiki, project: project, user: user) + create(:wiki_page, wiki: project_wiki, attrs: { title: 'merge', content: 'merge' }) + + expect(subject).to render_template("search/results/#{partial}") + end + end + end + it 'finds issue comments' do project = create(:project, :public) note = create(:note_on_issue, project: project) -- cgit v1.2.1 From 690382dda6d7404d702276413f907d5744498fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Alc=C3=A1ntara?= Date: Fri, 26 Apr 2019 08:15:44 +0000 Subject: Use a FSM to determine application next state - Separate cluster application UI state from server-side app status - Use a state machine to determine cluster application next state - Instead of using two variables to keep track of when an app is installing or updating, just use the app status property and control server-side and user events using the FSM service. --- spec/frontend/clusters/clusters_bundle_spec.js | 47 ++++---- .../clusters/components/application_row_spec.js | 68 ++++------- .../services/application_state_machine_spec.js | 134 +++++++++++++++++++++ spec/frontend/clusters/services/mock_data.js | 1 - .../clusters/stores/clusters_store_spec.js | 43 +++---- 5 files changed, 200 insertions(+), 93 deletions(-) create mode 100644 spec/frontend/clusters/services/application_state_machine_spec.js (limited to 'spec') diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index 33a35069004..5103cb4f69f 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -1,16 +1,13 @@ import Clusters from '~/clusters/clusters_bundle'; -import { - REQUEST_SUBMITTED, - REQUEST_FAILURE, - APPLICATION_STATUS, - INGRESS_DOMAIN_SUFFIX, -} from '~/clusters/constants'; +import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX } from '~/clusters/constants'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import { loadHTMLFixture } from 'helpers/fixtures'; import { setTestTimeout } from 'helpers/timeout'; import $ from 'jquery'; +const { INSTALLING, INSTALLABLE, INSTALLED, NOT_INSTALLABLE } = APPLICATION_STATUS; + describe('Clusters', () => { setTestTimeout(1000); @@ -93,7 +90,7 @@ describe('Clusters', () => { it('does not show alert when things transition from initial null state to something', () => { cluster.checkForNewInstalls(INITIAL_APP_MAP, { ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' }, + helm: { status: INSTALLABLE, title: 'Helm Tiller' }, }); const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); @@ -105,11 +102,11 @@ describe('Clusters', () => { cluster.checkForNewInstalls( { ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, + helm: { status: INSTALLING, title: 'Helm Tiller' }, }, { ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, + helm: { status: INSTALLED, title: 'Helm Tiller' }, }, ); @@ -125,13 +122,13 @@ describe('Clusters', () => { cluster.checkForNewInstalls( { ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, - ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' }, + helm: { status: INSTALLING, title: 'Helm Tiller' }, + ingress: { status: INSTALLABLE, title: 'Ingress' }, }, { ...INITIAL_APP_MAP, - helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, - ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' }, + helm: { status: INSTALLED, title: 'Helm Tiller' }, + ingress: { status: INSTALLED, title: 'Ingress' }, }, ); @@ -218,11 +215,11 @@ describe('Clusters', () => { it('tries to install helm', () => { jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); + cluster.store.state.applications.helm.status = INSTALLABLE; cluster.installApplication({ id: 'helm' }); - expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING); expect(cluster.store.state.applications.helm.requestReason).toEqual(null); expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined); }); @@ -230,11 +227,11 @@ describe('Clusters', () => { it('tries to install ingress', () => { jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null); + cluster.store.state.applications.ingress.status = INSTALLABLE; cluster.installApplication({ id: 'ingress' }); - expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.ingress.status).toEqual(INSTALLING); expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); }); @@ -242,11 +239,11 @@ describe('Clusters', () => { it('tries to install runner', () => { jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - expect(cluster.store.state.applications.runner.requestStatus).toEqual(null); + cluster.store.state.applications.runner.status = INSTALLABLE; cluster.installApplication({ id: 'runner' }); - expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.runner.status).toEqual(INSTALLING); expect(cluster.store.state.applications.runner.requestReason).toEqual(null); expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); }); @@ -254,13 +251,12 @@ describe('Clusters', () => { it('tries to install jupyter', () => { jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null); cluster.installApplication({ id: 'jupyter', params: { hostname: cluster.store.state.applications.jupyter.hostname }, }); - expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUBMITTED); + cluster.store.state.applications.jupyter.status = INSTALLABLE; expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { hostname: cluster.store.state.applications.jupyter.hostname, @@ -272,16 +268,18 @@ describe('Clusters', () => { .spyOn(cluster.service, 'installApplication') .mockRejectedValueOnce(new Error('STUBBED ERROR')); - expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); + cluster.store.state.applications.helm.status = INSTALLABLE; const promise = cluster.installApplication({ id: 'helm' }); - expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING); expect(cluster.store.state.applications.helm.requestReason).toEqual(null); expect(cluster.service.installApplication).toHaveBeenCalled(); return promise.then(() => { - expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE); + expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE); + expect(cluster.store.state.applications.helm.installFailed).toBe(true); + expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); }); }); @@ -315,7 +313,6 @@ describe('Clusters', () => { }); describe('toggleIngressDomainHelpText', () => { - const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS; let ingressPreviousState; let ingressNewState; diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js index d26fad54ebe..17273b7d5b1 100644 --- a/spec/frontend/clusters/components/application_row_spec.js +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -1,11 +1,6 @@ import Vue from 'vue'; import eventHub from '~/clusters/event_hub'; -import { - APPLICATION_STATUS, - REQUEST_SUBMITTED, - REQUEST_FAILURE, - UPGRADE_REQUESTED, -} from '~/clusters/constants'; +import { APPLICATION_STATUS } from '~/clusters/constants'; import applicationRow from '~/clusters/components/application_row.vue'; import mountComponent from 'helpers/vue_mount_component_helper'; import { DEFAULT_APPLICATION_STATE } from '../services/mock_data'; @@ -85,17 +80,6 @@ describe('Application Row', () => { expect(vm.installButtonDisabled).toEqual(false); }); - it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.SCHEDULED, - }); - - expect(vm.installButtonLabel).toEqual('Installing'); - expect(vm.installButtonLoading).toEqual(true); - expect(vm.installButtonDisabled).toEqual(true); - }); - it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, @@ -107,18 +91,6 @@ describe('Application Row', () => { expect(vm.installButtonDisabled).toEqual(true); }); - it('has loading "Installing" when REQUEST_SUBMITTED', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - requestStatus: REQUEST_SUBMITTED, - }); - - expect(vm.installButtonLabel).toEqual('Installing'); - expect(vm.installButtonLoading).toEqual(true); - expect(vm.installButtonDisabled).toEqual(true); - }); - it('has disabled "Installed" when application is installed and not uninstallable', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, @@ -144,10 +116,11 @@ describe('Application Row', () => { expect(installBtn).toBe(null); }); - it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => { + it('has enabled "Install" when install fails', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.ERROR, + status: APPLICATION_STATUS.INSTALLABLE, + installFailed: true, }); expect(vm.installButtonLabel).toEqual('Install'); @@ -159,7 +132,6 @@ describe('Application Row', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.INSTALLABLE, - requestStatus: REQUEST_FAILURE, }); expect(vm.installButtonLabel).toEqual('Install'); @@ -251,15 +223,15 @@ describe('Application Row', () => { expect(upgradeBtn.innerHTML).toContain('Upgrade'); }); - it('has enabled "Retry update" when APPLICATION_STATUS.UPDATE_ERRORED', () => { + it('has enabled "Retry update" when update process fails', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATE_ERRORED, + status: APPLICATION_STATUS.INSTALLED, + updateFailed: true, }); const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); expect(upgradeBtn).not.toBe(null); - expect(vm.upgradeFailed).toBe(true); expect(upgradeBtn.innerHTML).toContain('Retry update'); }); @@ -279,7 +251,8 @@ describe('Application Row', () => { jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATE_ERRORED, + status: APPLICATION_STATUS.INSTALLED, + upgradeAvailable: true, }); const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); @@ -308,7 +281,8 @@ describe('Application Row', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, title: 'GitLab Runner', - status: APPLICATION_STATUS.UPDATE_ERRORED, + status: APPLICATION_STATUS.INSTALLED, + updateFailed: true, }); const failureMessage = vm.$el.querySelector( '.js-cluster-application-upgrade-failure-message', @@ -324,12 +298,11 @@ describe('Application Row', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, title: 'GitLab Runner', - requestStatus: UPGRADE_REQUESTED, - status: APPLICATION_STATUS.UPDATE_ERRORED, + updateSuccessful: false, }); vm.$toast = { show: jest.fn() }; - vm.status = APPLICATION_STATUS.UPDATED; + vm.updateSuccessful = true; vm.$nextTick(() => { expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.'); @@ -342,7 +315,8 @@ describe('Application Row', () => { const version = '0.1.45'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATED, + status: APPLICATION_STATUS.INSTALLED, + updateSuccessful: true, version, }); const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); @@ -358,7 +332,8 @@ describe('Application Row', () => { const chartRepo = 'https://gitlab.com/charts/gitlab-runner'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATED, + status: APPLICATION_STATUS.INSTALLED, + updateSuccessful: true, chartRepo, version, }); @@ -372,7 +347,8 @@ describe('Application Row', () => { const version = '0.1.45'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.UPDATE_ERRORED, + status: APPLICATION_STATUS.INSTALLED, + updateFailed: true, version, }); const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); @@ -388,7 +364,6 @@ describe('Application Row', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: null, - requestStatus: null, }); const generalErrorMessage = vm.$el.querySelector( '.js-cluster-application-general-error-message', @@ -397,12 +372,13 @@ describe('Application Row', () => { expect(generalErrorMessage).toBeNull(); }); - it('shows status reason when APPLICATION_STATUS.ERROR', () => { + it('shows status reason when install fails', () => { const statusReason = 'We broke it 0.0'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.ERROR, statusReason, + installFailed: true, }); const generalErrorMessage = vm.$el.querySelector( '.js-cluster-application-general-error-message', @@ -423,7 +399,7 @@ describe('Application Row', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.INSTALLABLE, - requestStatus: REQUEST_FAILURE, + installFailed: true, requestReason, }); const generalErrorMessage = vm.$el.querySelector( diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js new file mode 100644 index 00000000000..e74b7910572 --- /dev/null +++ b/spec/frontend/clusters/services/application_state_machine_spec.js @@ -0,0 +1,134 @@ +import transitionApplicationState from '~/clusters/services/application_state_machine'; +import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT } from '~/clusters/constants'; + +const { + NO_STATUS, + SCHEDULED, + NOT_INSTALLABLE, + INSTALLABLE, + INSTALLING, + INSTALLED, + ERROR, + UPDATING, + UPDATED, + UPDATE_ERRORED, +} = APPLICATION_STATUS; + +const NO_EFFECTS = 'no effects'; + +describe('applicationStateMachine', () => { + const noEffectsToEmptyObject = effects => (typeof effects === 'string' ? {} : effects); + + describe(`current state is ${NO_STATUS}`, () => { + it.each` + expectedState | event | effects + ${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS} + ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS} + ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS} + ${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS} + ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} + ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }} + ${UPDATING} | ${UPDATING} | ${NO_EFFECTS} + ${INSTALLED} | ${UPDATED} | ${NO_EFFECTS} + ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} + `(`transitions to $expectedState on $event event and applies $effects`, data => { + const { expectedState, event, effects } = data; + const currentAppState = { + status: NO_STATUS, + }; + + expect(transitionApplicationState(currentAppState, event)).toEqual({ + status: expectedState, + ...noEffectsToEmptyObject(effects), + }); + }); + }); + + describe(`current state is ${NOT_INSTALLABLE}`, () => { + it.each` + expectedState | event | effects + ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS} + `(`transitions to $expectedState on $event event and applies $effects`, data => { + const { expectedState, event, effects } = data; + const currentAppState = { + status: NOT_INSTALLABLE, + }; + + expect(transitionApplicationState(currentAppState, event)).toEqual({ + status: expectedState, + ...noEffectsToEmptyObject(effects), + }); + }); + }); + + describe(`current state is ${INSTALLABLE}`, () => { + it.each` + expectedState | event | effects + ${INSTALLING} | ${INSTALL_EVENT} | ${{ installFailed: false }} + ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} + `(`transitions to $expectedState on $event event and applies $effects`, data => { + const { expectedState, event, effects } = data; + const currentAppState = { + status: INSTALLABLE, + }; + + expect(transitionApplicationState(currentAppState, event)).toEqual({ + status: expectedState, + ...noEffectsToEmptyObject(effects), + }); + }); + }); + + describe(`current state is ${INSTALLING}`, () => { + it.each` + expectedState | event | effects + ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} + ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }} + `(`transitions to $expectedState on $event event and applies $effects`, data => { + const { expectedState, event, effects } = data; + const currentAppState = { + status: INSTALLING, + }; + + expect(transitionApplicationState(currentAppState, event)).toEqual({ + status: expectedState, + ...noEffectsToEmptyObject(effects), + }); + }); + }); + + describe(`current state is ${INSTALLED}`, () => { + it.each` + expectedState | event | effects + ${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }} + `(`transitions to $expectedState on $event event and applies $effects`, data => { + const { expectedState, event, effects } = data; + const currentAppState = { + status: INSTALLED, + }; + + expect(transitionApplicationState(currentAppState, event)).toEqual({ + status: expectedState, + ...effects, + }); + }); + }); + + describe(`current state is ${UPDATING}`, () => { + it.each` + expectedState | event | effects + ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true, updateAcknowledged: false }} + ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} + `(`transitions to $expectedState on $event event and applies $effects`, data => { + const { expectedState, event, effects } = data; + const currentAppState = { + status: UPDATING, + }; + + expect(transitionApplicationState(currentAppState, event)).toEqual({ + status: expectedState, + ...effects, + }); + }); + }); +}); diff --git a/spec/frontend/clusters/services/mock_data.js b/spec/frontend/clusters/services/mock_data.js index b4d1bb710e0..1e896af1c7d 100644 --- a/spec/frontend/clusters/services/mock_data.js +++ b/spec/frontend/clusters/services/mock_data.js @@ -113,7 +113,6 @@ const DEFAULT_APPLICATION_STATE = { description: 'Some description about this interesting application!', status: null, statusReason: null, - requestStatus: null, requestReason: null, }; diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index c0e8b737ea2..a20e0439555 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -32,15 +32,6 @@ describe('Clusters Store', () => { }); describe('updateAppProperty', () => { - it('should store new request status', () => { - expect(store.state.applications.helm.requestStatus).toEqual(null); - - const newStatus = APPLICATION_STATUS.INSTALLING; - store.updateAppProperty('helm', 'requestStatus', newStatus); - - expect(store.state.applications.helm.requestStatus).toEqual(newStatus); - }); - it('should store new request reason', () => { expect(store.state.applications.helm.requestReason).toEqual(null); @@ -68,80 +59,90 @@ describe('Clusters Store', () => { title: 'Helm Tiller', status: mockResponseData.applications[0].status, statusReason: mockResponseData.applications[0].status_reason, - requestStatus: null, requestReason: null, installed: false, + installFailed: false, + uninstallable: false, }, ingress: { title: 'Ingress', - status: mockResponseData.applications[1].status, + status: APPLICATION_STATUS.INSTALLABLE, statusReason: mockResponseData.applications[1].status_reason, - requestStatus: null, requestReason: null, externalIp: null, externalHostname: null, installed: false, + installFailed: true, + uninstallable: false, }, runner: { title: 'GitLab Runner', status: mockResponseData.applications[2].status, statusReason: mockResponseData.applications[2].status_reason, - requestStatus: null, requestReason: null, version: mockResponseData.applications[2].version, upgradeAvailable: mockResponseData.applications[2].update_available, chartRepo: 'https://gitlab.com/charts/gitlab-runner', installed: false, + installFailed: false, + updateAcknowledged: true, + updateFailed: false, + updateSuccessful: false, + uninstallable: false, }, prometheus: { title: 'Prometheus', - status: mockResponseData.applications[3].status, + status: APPLICATION_STATUS.INSTALLABLE, statusReason: mockResponseData.applications[3].status_reason, - requestStatus: null, requestReason: null, installed: false, + installFailed: true, + uninstallable: false, }, jupyter: { title: 'JupyterHub', status: mockResponseData.applications[4].status, statusReason: mockResponseData.applications[4].status_reason, - requestStatus: null, requestReason: null, hostname: '', installed: false, + installFailed: false, + uninstallable: false, }, knative: { title: 'Knative', status: mockResponseData.applications[5].status, statusReason: mockResponseData.applications[5].status_reason, - requestStatus: null, requestReason: null, hostname: null, isEditingHostName: false, externalIp: null, externalHostname: null, installed: false, + installFailed: false, + uninstallable: false, }, cert_manager: { title: 'Cert-Manager', - status: mockResponseData.applications[6].status, + status: APPLICATION_STATUS.INSTALLABLE, + installFailed: true, statusReason: mockResponseData.applications[6].status_reason, - requestStatus: null, requestReason: null, email: mockResponseData.applications[6].email, installed: false, + uninstallable: false, }, }, }); }); - describe.each(APPLICATION_INSTALLED_STATUSES)('given the current app status is %s', () => { + describe.each(APPLICATION_INSTALLED_STATUSES)('given the current app status is %s', status => { it('marks application as installed', () => { const mockResponseData = CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data; const runnerAppIndex = 2; - mockResponseData.applications[runnerAppIndex].status = APPLICATION_STATUS.INSTALLED; + mockResponseData.applications[runnerAppIndex].status = status; store.updateStateFromServer(mockResponseData); -- cgit v1.2.1 From 94547f81c1d091775dceb659e191ba6bd6e14d9d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 26 Apr 2019 09:49:21 +0000 Subject: Revert "Merge branch '60855-mr-popover-is-not-attached-in-system-notes' into 'master'" This reverts merge request !27589 --- spec/frontend/vue_shared/components/notes/system_note_spec.js | 6 ------ 1 file changed, 6 deletions(-) (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js index 5b4ca20940a..adcb1c858aa 100644 --- a/spec/frontend/vue_shared/components/notes/system_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js @@ -5,10 +5,8 @@ import createStore from '~/notes/stores'; describe('system note component', () => { let vm; let props; - let initMRPopoversSpy; beforeEach(() => { - initMRPopoversSpy = spyOnDependency(issueSystemNote, 'initMRPopovers'); props = { note: { id: '1424', @@ -58,8 +56,4 @@ describe('system note component', () => { it('removes wrapping paragraph from note HTML', () => { expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('closed'); }); - - it('should initMRPopovers onMount', () => { - expect(initMRPopoversSpy).toHaveBeenCalled(); - }); }); -- cgit v1.2.1 From 4848c32ea00a9a5be587aec0f24214e56724c683 Mon Sep 17 00:00:00 2001 From: Martin Hobert Date: Fri, 26 Apr 2019 14:54:20 +0200 Subject: refactor(sidebar): Refactored Karma spec files to Jest fix #58830 Added changelog Updated to use jest functions Added mock implementation --- .../sidebar/collapsed_calendar_icon_spec.js | 36 ++++++ .../sidebar/collapsed_grouped_date_picker_spec.js | 86 +++++++++++++ .../components/sidebar/date_picker_spec.js | 121 ++++++++++++++++++ .../components/sidebar/labels_select/base_spec.js | 134 ++++++++++++++++++++ .../sidebar/labels_select/dropdown_button_spec.js | 97 ++++++++++++++ .../labels_select/dropdown_create_label_spec.js | 107 ++++++++++++++++ .../sidebar/labels_select/dropdown_footer_spec.js | 75 +++++++++++ .../sidebar/labels_select/dropdown_header_spec.js | 40 ++++++ .../labels_select/dropdown_search_input_spec.js | 42 +++++++ .../sidebar/labels_select/dropdown_title_spec.js | 43 +++++++ .../labels_select/dropdown_value_collapsed_spec.js | 97 ++++++++++++++ .../sidebar/labels_select/dropdown_value_spec.js | 139 +++++++++++++++++++++ .../components/sidebar/toggle_sidebar_spec.js | 32 +++++ .../sidebar/collapsed_calendar_icon_spec.js | 36 ------ .../sidebar/collapsed_grouped_date_picker_spec.js | 86 ------------- .../components/sidebar/date_picker_spec.js | 121 ------------------ .../components/sidebar/labels_select/base_spec.js | 130 ------------------- .../sidebar/labels_select/dropdown_button_spec.js | 95 -------------- .../labels_select/dropdown_create_label_spec.js | 108 ---------------- .../sidebar/labels_select/dropdown_footer_spec.js | 76 ----------- .../sidebar/labels_select/dropdown_header_spec.js | 40 ------ .../labels_select/dropdown_search_input_spec.js | 42 ------- .../sidebar/labels_select/dropdown_title_spec.js | 43 ------- .../labels_select/dropdown_value_collapsed_spec.js | 98 --------------- .../sidebar/labels_select/dropdown_value_spec.js | 137 -------------------- .../components/sidebar/toggle_sidebar_spec.js | 32 ----- 26 files changed, 1049 insertions(+), 1044 deletions(-) create mode 100644 spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/date_picker_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js delete mode 100644 spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js new file mode 100644 index 00000000000..691ebe43d6b --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js @@ -0,0 +1,36 @@ +import Vue from 'vue'; +import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; + +describe('collapsedCalendarIcon', () => { + let vm; + beforeEach(() => { + const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon); + vm = mountComponent(CollapsedCalendarIcon, { + containerClass: 'test-class', + text: 'text', + showIcon: false, + }); + }); + + it('should add class to container', () => { + expect(vm.$el.classList.contains('test-class')).toEqual(true); + }); + + it('should hide calendar icon if showIcon', () => { + expect(vm.$el.querySelector('.fa-calendar')).toBeNull(); + }); + + it('should render text', () => { + expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text'); + }); + + it('should emit click event when container is clicked', () => { + const click = jest.fn(); + vm.$on('click', click); + + vm.$el.click(); + + expect(click).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js new file mode 100644 index 00000000000..062ebfa01c9 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js @@ -0,0 +1,86 @@ +import Vue from 'vue'; +import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; + +describe('collapsedGroupedDatePicker', () => { + let vm; + beforeEach(() => { + const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker); + vm = mountComponent(CollapsedGroupedDatePicker, { + showToggleSidebar: true, + }); + }); + + describe('toggleCollapse events', () => { + beforeEach(done => { + jest.spyOn(vm, 'toggleSidebar').mockImplementation(() => {}); + vm.minDate = new Date('07/17/2016'); + Vue.nextTick(done); + }); + + it('should emit when collapsed-calendar-icon is clicked', () => { + vm.$el.querySelector('.sidebar-collapsed-icon').click(); + + expect(vm.toggleSidebar).toHaveBeenCalled(); + }); + }); + + describe('minDate and maxDate', () => { + beforeEach(done => { + vm.minDate = new Date('07/17/2016'); + vm.maxDate = new Date('07/17/2017'); + Vue.nextTick(done); + }); + + it('should render both collapsed-calendar-icon', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + + expect(icons.length).toEqual(2); + expect(icons[0].innerText.trim()).toEqual('Jul 17 2016'); + expect(icons[1].innerText.trim()).toEqual('Jul 17 2017'); + }); + }); + + describe('minDate', () => { + beforeEach(done => { + vm.minDate = new Date('07/17/2016'); + Vue.nextTick(done); + }); + + it('should render minDate in collapsed-calendar-icon', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + + expect(icons.length).toEqual(1); + expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016'); + }); + }); + + describe('maxDate', () => { + beforeEach(done => { + vm.maxDate = new Date('07/17/2017'); + Vue.nextTick(done); + }); + + it('should render maxDate in collapsed-calendar-icon', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + + expect(icons.length).toEqual(1); + expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017'); + }); + }); + + describe('no dates', () => { + it('should render None', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + + expect(icons.length).toEqual(1); + expect(icons[0].innerText.trim()).toEqual('None'); + }); + + it('should have tooltip as `Start and due date`', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + + expect(icons[0].dataset.originalTitle).toBe('Start and due date'); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js new file mode 100644 index 00000000000..5e2bca6efc9 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js @@ -0,0 +1,121 @@ +import Vue from 'vue'; +import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; + +describe('sidebarDatePicker', () => { + let vm; + beforeEach(() => { + const SidebarDatePicker = Vue.extend(sidebarDatePicker); + vm = mountComponent(SidebarDatePicker, { + label: 'label', + isLoading: true, + }); + }); + + it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => { + const toggleCollapse = jest.fn(); + vm.$on('toggleCollapse', toggleCollapse); + + vm.$el.querySelector('.issuable-sidebar-header .gutter-toggle').click(); + + expect(toggleCollapse).toHaveBeenCalled(); + }); + + it('should render collapsed-calendar-icon', () => { + expect(vm.$el.querySelector('.sidebar-collapsed-icon')).toBeDefined(); + }); + + it('should render label', () => { + expect(vm.$el.querySelector('.title').innerText.trim()).toEqual('label'); + }); + + it('should render loading-icon when isLoading', () => { + expect(vm.$el.querySelector('.fa-spin')).toBeDefined(); + }); + + it('should render value when not editing', () => { + expect(vm.$el.querySelector('.value-content')).toBeDefined(); + }); + + it('should render None if there is no selectedDate', () => { + expect(vm.$el.querySelector('.value-content span').innerText.trim()).toEqual('None'); + }); + + it('should render date-picker when editing', done => { + vm.editing = true; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.pika-label')).toBeDefined(); + done(); + }); + }); + + describe('editable', () => { + beforeEach(done => { + vm.editable = true; + Vue.nextTick(done); + }); + + it('should render edit button', () => { + expect(vm.$el.querySelector('.title .btn-blank').innerText.trim()).toEqual('Edit'); + }); + + it('should enable editing when edit button is clicked', done => { + vm.isLoading = false; + Vue.nextTick(() => { + vm.$el.querySelector('.title .btn-blank').click(); + + expect(vm.editing).toEqual(true); + done(); + }); + }); + }); + + it('should render date if selectedDate', done => { + vm.selectedDate = new Date('07/07/2017'); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.value-content strong').innerText.trim()).toEqual('Jul 7, 2017'); + done(); + }); + }); + + describe('selectedDate and editable', () => { + beforeEach(done => { + vm.selectedDate = new Date('07/07/2017'); + vm.editable = true; + Vue.nextTick(done); + }); + + it('should render remove button if selectedDate and editable', () => { + expect(vm.$el.querySelector('.value-content .btn-blank').innerText.trim()).toEqual('remove'); + }); + + it('should emit saveDate when remove button is clicked', () => { + const saveDate = jest.fn(); + vm.$on('saveDate', saveDate); + + vm.$el.querySelector('.value-content .btn-blank').click(); + + expect(saveDate).toHaveBeenCalled(); + }); + }); + + describe('showToggleSidebar', () => { + beforeEach(done => { + vm.showToggleSidebar = true; + Vue.nextTick(done); + }); + + it('should render toggle-sidebar when showToggleSidebar', () => { + expect(vm.$el.querySelector('.title .gutter-toggle')).toBeDefined(); + }); + + it('should emit toggleCollapse when toggle sidebar is clicked', () => { + const toggleCollapse = jest.fn(); + vm.$on('toggleCollapse', toggleCollapse); + + vm.$el.querySelector('.title .gutter-toggle').click(); + + expect(toggleCollapse).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js new file mode 100644 index 00000000000..6aee616c324 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js @@ -0,0 +1,134 @@ +import Vue from 'vue'; + +import LabelsSelect from '~/labels_select'; +import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue'; + +import { mount } from '@vue/test-utils'; +import { + mockConfig, + mockLabels, +} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; + +const createComponent = (config = mockConfig) => { + const Component = Vue.extend(baseComponent); + + return mount(Component, { + propsData: config, + sync: false, + }); +}; + +describe('BaseComponent', () => { + let wrapper; + let vm; + + beforeEach(done => { + wrapper = createComponent(); + + ({ vm } = wrapper); + + Vue.nextTick(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('computed', () => { + describe('hiddenInputName', () => { + it('returns correct string when showCreate prop is `true`', () => { + expect(vm.hiddenInputName).toBe('issue[label_names][]'); + }); + + it('returns correct string when showCreate prop is `false`', () => { + wrapper.setProps({ showCreate: false }); + + expect(vm.hiddenInputName).toBe('label_id[]'); + }); + }); + + describe('createLabelTitle', () => { + it('returns `Create project label` when `isProject` prop is true', () => { + expect(vm.createLabelTitle).toBe('Create project label'); + }); + + it('return `Create group label` when `isProject` prop is false', () => { + wrapper.setProps({ isProject: false }); + + expect(vm.createLabelTitle).toBe('Create group label'); + }); + }); + + describe('manageLabelsTitle', () => { + it('returns `Manage project labels` when `isProject` prop is true', () => { + expect(vm.manageLabelsTitle).toBe('Manage project labels'); + }); + + it('return `Manage group labels` when `isProject` prop is false', () => { + wrapper.setProps({ isProject: false }); + + expect(vm.manageLabelsTitle).toBe('Manage group labels'); + }); + }); + }); + + describe('methods', () => { + describe('handleClick', () => { + it('emits onLabelClick event with label and list of labels as params', () => { + jest.spyOn(vm, '$emit').mockImplementation(() => {}); + vm.handleClick(mockLabels[0]); + + expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]); + }); + }); + + describe('handleCollapsedValueClick', () => { + it('emits toggleCollapse event on component', () => { + jest.spyOn(vm, '$emit').mockImplementation(() => {}); + vm.handleCollapsedValueClick(); + + expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse'); + }); + }); + + describe('handleDropdownHidden', () => { + it('emits onDropdownClose event on component', () => { + jest.spyOn(vm, '$emit').mockImplementation(() => {}); + vm.handleDropdownHidden(); + + expect(vm.$emit).toHaveBeenCalledWith('onDropdownClose'); + }); + }); + }); + + describe('mounted', () => { + it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => { + expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true); + }); + }); + + describe('template', () => { + it('renders component container element with classes `block labels`', () => { + expect(vm.$el.classList.contains('block')).toBe(true); + expect(vm.$el.classList.contains('labels')).toBe(true); + }); + + it('renders `.selectbox` element', () => { + expect(vm.$el.querySelector('.selectbox')).not.toBeNull(); + expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;'); + }); + + it('renders `.dropdown` element', () => { + expect(vm.$el.querySelector('.dropdown')).not.toBeNull(); + }); + + it('renders `.dropdown-menu` element', () => { + const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu'); + + expect(dropdownMenuEl).not.toBeNull(); + expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull(); + expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull(); + expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js new file mode 100644 index 00000000000..bb33dc6ea0f --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js @@ -0,0 +1,97 @@ +import Vue from 'vue'; + +import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; +import { + mockConfig, + mockLabels, +} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; + +const componentConfig = Object.assign({}, mockConfig, { + fieldName: 'label_id[]', + labels: mockLabels, + showExtraOptions: false, +}); + +const createComponent = (config = componentConfig) => { + const Component = Vue.extend(dropdownButtonComponent); + + return mountComponent(Component, config); +}; + +describe('DropdownButtonComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('dropdownToggleText', () => { + it('returns text as `Label` when `labels` prop is empty array', () => { + const mockEmptyLabels = Object.assign({}, componentConfig, { labels: [] }); + const vmEmptyLabels = createComponent(mockEmptyLabels); + + expect(vmEmptyLabels.dropdownToggleText).toBe('Label'); + vmEmptyLabels.$destroy(); + }); + + it('returns first label name with remaining label count when `labels` prop has more than one item', () => { + const mockMoreLabels = Object.assign({}, componentConfig, { + labels: mockLabels.concat(mockLabels), + }); + const vmMoreLabels = createComponent(mockMoreLabels); + + expect(vmMoreLabels.dropdownToggleText).toBe( + `Foo Label +${mockMoreLabels.labels.length - 1} more`, + ); + vmMoreLabels.$destroy(); + }); + + it('returns first label name when `labels` prop has only one item present', () => { + const singleLabel = Object.assign({}, componentConfig, { + labels: [mockLabels[0]], + }); + const vmSingleLabel = createComponent(singleLabel); + + expect(vmSingleLabel.dropdownToggleText).toBe(mockLabels[0].title); + + vmSingleLabel.$destroy(); + }); + }); + }); + + describe('template', () => { + it('renders component container element of type `button`', () => { + expect(vm.$el.nodeName).toBe('BUTTON'); + }); + + it('renders component container element with required data attributes', () => { + expect(vm.$el.dataset.abilityName).toBe(vm.abilityName); + expect(vm.$el.dataset.fieldName).toBe(vm.fieldName); + expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath); + expect(vm.$el.dataset.labels).toBe(vm.labelsPath); + expect(vm.$el.dataset.namespacePath).toBe(vm.namespace); + expect(vm.$el.dataset.showAny).not.toBeDefined(); + }); + + it('renders dropdown toggle text element', () => { + const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text'); + + expect(dropdownToggleTextEl).not.toBeNull(); + expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label +1 more'); + }); + + it('renders dropdown button icon', () => { + const dropdownIconEl = vm.$el.querySelector('i.fa'); + + expect(dropdownIconEl).not.toBeNull(); + expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js new file mode 100644 index 00000000000..1c25d42682c --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js @@ -0,0 +1,107 @@ +import Vue from 'vue'; + +import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; +import { mockSuggestedColors } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; + +const createComponent = headerTitle => { + const Component = Vue.extend(dropdownCreateLabelComponent); + + return mountComponent(Component, { + headerTitle, + }); +}; + +describe('DropdownCreateLabelComponent', () => { + let vm; + + beforeEach(() => { + gon.suggested_label_colors = mockSuggestedColors; + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('created', () => { + it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => { + expect(vm.suggestedColors.length).toBe(mockSuggestedColors.length); + }); + }); + + describe('template', () => { + it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => { + expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true); + }); + + it('renders `Go back` button on component header', () => { + const backButtonEl = vm.$el.querySelector( + '.dropdown-title button.dropdown-title-button.dropdown-menu-back', + ); + + expect(backButtonEl).not.toBe(null); + expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null); + }); + + it('renders component header element as `Create new label` when `headerTitle` prop is not provided', () => { + const headerEl = vm.$el.querySelector('.dropdown-title'); + + expect(headerEl.innerText.trim()).toContain('Create new label'); + }); + + it('renders component header element with value of `headerTitle` prop', () => { + const headerTitle = 'Create project label'; + const vmWithHeaderTitle = createComponent(headerTitle); + const headerEl = vmWithHeaderTitle.$el.querySelector('.dropdown-title'); + + expect(headerEl.innerText.trim()).toContain(headerTitle); + vmWithHeaderTitle.$destroy(); + }); + + it('renders `Close` button on component header', () => { + const closeButtonEl = vm.$el.querySelector( + '.dropdown-title button.dropdown-title-button.dropdown-menu-close', + ); + + expect(closeButtonEl).not.toBe(null); + expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null); + }); + + it('renders `Name new label` input element', () => { + expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null); + expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null); + }); + + it('renders suggested colors list elements', () => { + const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown'); + + expect(colorsListContainerEl).not.toBe(null); + expect(colorsListContainerEl.querySelectorAll('a').length).toBe(mockSuggestedColors.length); + + const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0]; + + expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0]); + expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);'); + }); + + it('renders color input element', () => { + expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null); + expect( + vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview'), + ).not.toBe(null); + + expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null); + }); + + it('renders component action buttons', () => { + const createBtnEl = vm.$el.querySelector('button.js-new-label-btn'); + const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn'); + + expect(createBtnEl).not.toBe(null); + expect(createBtnEl.innerText.trim()).toBe('Create'); + expect(cancelBtnEl.innerText.trim()).toBe('Cancel'); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js new file mode 100644 index 00000000000..989901a0012 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js @@ -0,0 +1,75 @@ +import Vue from 'vue'; + +import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; +import { mockConfig } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; + +const createComponent = ( + labelsWebUrl = mockConfig.labelsWebUrl, + createLabelTitle, + manageLabelsTitle, +) => { + const Component = Vue.extend(dropdownFooterComponent); + + return mountComponent(Component, { + labelsWebUrl, + createLabelTitle, + manageLabelsTitle, + }); +}; + +describe('DropdownFooterComponent', () => { + const createLabelTitle = 'Create project label'; + const manageLabelsTitle = 'Manage project labels'; + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders link element with `Create new label` when `createLabelTitle` prop is not provided', () => { + const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page'); + + expect(createLabelEl).not.toBeNull(); + expect(createLabelEl.innerText.trim()).toBe('Create new label'); + }); + + it('renders link element with value of `createLabelTitle` prop', () => { + const vmWithCreateLabelTitle = createComponent(mockConfig.labelsWebUrl, createLabelTitle); + const createLabelEl = vmWithCreateLabelTitle.$el.querySelector( + '.dropdown-footer-list .dropdown-toggle-page', + ); + + expect(createLabelEl.innerText.trim()).toBe(createLabelTitle); + vmWithCreateLabelTitle.$destroy(); + }); + + it('renders link element with `Manage labels` when `manageLabelsTitle` prop is not provided', () => { + const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link'); + + expect(manageLabelsEl).not.toBeNull(); + expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl); + expect(manageLabelsEl.innerText.trim()).toBe('Manage labels'); + }); + + it('renders link element with value of `manageLabelsTitle` prop', () => { + const vmWithManageLabelsTitle = createComponent( + mockConfig.labelsWebUrl, + createLabelTitle, + manageLabelsTitle, + ); + const manageLabelsEl = vmWithManageLabelsTitle.$el.querySelector( + '.dropdown-footer-list .dropdown-external-link', + ); + + expect(manageLabelsEl.innerText.trim()).toBe(manageLabelsTitle); + vmWithManageLabelsTitle.$destroy(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js new file mode 100644 index 00000000000..c36a82e1a35 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js @@ -0,0 +1,40 @@ +import Vue from 'vue'; + +import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; + +const createComponent = () => { + const Component = Vue.extend(dropdownHeaderComponent); + + return mountComponent(Component); +}; + +describe('DropdownHeaderComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders header text element', () => { + const headerEl = vm.$el.querySelector('.dropdown-title span'); + + expect(headerEl.innerText.trim()).toBe('Assign labels'); + }); + + it('renders `Close` button element', () => { + const closeBtnEl = vm.$el.querySelector( + '.dropdown-title button.dropdown-title-button.dropdown-menu-close', + ); + + expect(closeBtnEl).not.toBeNull(); + expect(closeBtnEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBeNull(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js new file mode 100644 index 00000000000..2fffb2e495e --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js @@ -0,0 +1,42 @@ +import Vue from 'vue'; + +import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; + +const createComponent = () => { + const Component = Vue.extend(dropdownSearchInputComponent); + + return mountComponent(Component); +}; + +describe('DropdownSearchInputComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders input element with type `search`', () => { + const inputEl = vm.$el.querySelector('input.dropdown-input-field'); + + expect(inputEl).not.toBeNull(); + expect(inputEl.getAttribute('type')).toBe('search'); + }); + + it('renders search icon element', () => { + expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull(); + }); + + it('renders clear search icon element', () => { + expect( + vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'), + ).not.toBeNull(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js new file mode 100644 index 00000000000..1616e657c81 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; + +import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; + +const createComponent = (canEdit = true) => { + const Component = Vue.extend(dropdownTitleComponent); + + return mountComponent(Component, { + canEdit, + }); +}; + +describe('DropdownTitleComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders title text', () => { + expect(vm.$el.classList.contains('title', 'hide-collapsed')).toBe(true); + expect(vm.$el.innerText.trim()).toContain('Labels'); + }); + + it('renders spinner icon element', () => { + expect(vm.$el.querySelector('.fa-spinner.fa-spin.block-loading')).not.toBeNull(); + }); + + it('renders `Edit` button element', () => { + const editBtnEl = vm.$el.querySelector('button.edit-link.js-sidebar-dropdown-toggle'); + + expect(editBtnEl).not.toBeNull(); + expect(editBtnEl.innerText.trim()).toBe('Edit'); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js new file mode 100644 index 00000000000..517f2c01c46 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js @@ -0,0 +1,97 @@ +import Vue from 'vue'; + +import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; +import { mockLabels } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; + +const createComponent = (labels = mockLabels) => { + const Component = Vue.extend(dropdownValueCollapsedComponent); + + return mountComponent(Component, { + labels, + }); +}; + +describe('DropdownValueCollapsedComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('labelsList', () => { + it('returns default text when `labels` prop is empty array', () => { + const vmEmptyLabels = createComponent([]); + + expect(vmEmptyLabels.labelsList).toBe('Labels'); + vmEmptyLabels.$destroy(); + }); + + it('returns labels names separated by coma when `labels` prop has more than one item', () => { + const labels = mockLabels.concat(mockLabels); + const vmMoreLabels = createComponent(labels); + + const expectedText = labels.map(label => label.title).join(', '); + + expect(vmMoreLabels.labelsList).toBe(expectedText); + vmMoreLabels.$destroy(); + }); + + it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => { + const mockMoreLabels = Object.assign([], mockLabels); + for (let i = 0; i < 6; i += 1) { + mockMoreLabels.unshift(mockLabels[0]); + } + + const vmMoreLabels = createComponent(mockMoreLabels); + + const expectedText = `${mockMoreLabels + .slice(0, 5) + .map(label => label.title) + .join(', ')}, and ${mockMoreLabels.length - 5} more`; + + expect(vmMoreLabels.labelsList).toBe(expectedText); + vmMoreLabels.$destroy(); + }); + + it('returns first label name when `labels` prop has only one item present', () => { + const text = mockLabels.map(label => label.title).join(', '); + + expect(vm.labelsList).toBe(text); + }); + }); + }); + + describe('methods', () => { + describe('handleClick', () => { + it('emits onValueClick event on component', () => { + jest.spyOn(vm, '$emit').mockImplementation(() => {}); + vm.handleClick(); + + expect(vm.$emit).toHaveBeenCalledWith('onValueClick'); + }); + }); + }); + + describe('template', () => { + it('renders component container element with tooltip`', () => { + expect(vm.$el.dataset.placement).toBe('left'); + expect(vm.$el.dataset.container).toBe('body'); + expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList); + }); + + it('renders tags icon element', () => { + expect(vm.$el.querySelector('.fa-tags')).not.toBeNull(); + }); + + it('renders labels count', () => { + expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js new file mode 100644 index 00000000000..ec143fec5d9 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js @@ -0,0 +1,139 @@ +import Vue from 'vue'; +import $ from 'jquery'; + +import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue'; + +import mountComponent from 'helpers/vue_mount_component_helper'; +import { + mockConfig, + mockLabels, +} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data'; + +const createComponent = ( + labels = mockLabels, + labelFilterBasePath = mockConfig.labelFilterBasePath, +) => { + const Component = Vue.extend(dropdownValueComponent); + + return mountComponent(Component, { + labels, + labelFilterBasePath, + enableScopedLabels: true, + }); +}; + +describe('DropdownValueComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('isEmpty', () => { + it('returns true if `labels` prop is empty', () => { + const vmEmptyLabels = createComponent([]); + + expect(vmEmptyLabels.isEmpty).toBe(true); + vmEmptyLabels.$destroy(); + }); + + it('returns false if `labels` prop is empty', () => { + expect(vm.isEmpty).toBe(false); + }); + }); + }); + + describe('methods', () => { + describe('labelFilterUrl', () => { + it('returns URL string starting with labelFilterBasePath and encoded label.title', () => { + expect( + vm.labelFilterUrl({ + title: 'Foo bar', + }), + ).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20bar'); + }); + }); + + describe('labelStyle', () => { + it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => { + const label = { + textColor: '#FFFFFF', + color: '#BADA55', + }; + const styleObj = vm.labelStyle(label); + + expect(styleObj.color).toBe(label.textColor); + expect(styleObj.backgroundColor).toBe(label.color); + }); + }); + + describe('scopedLabelsDescription', () => { + it('returns html for tooltip', () => { + const html = vm.scopedLabelsDescription(mockLabels[1]); + const $el = $.parseHTML(html); + + expect($el[0]).toHaveClass('scoped-label-tooltip-title'); + expect($el[2].textContent).toEqual(mockLabels[1].description); + }); + }); + + describe('showScopedLabels', () => { + it('returns true if the label is scoped label', () => { + expect(vm.showScopedLabels(mockLabels[1])).toBe(true); + }); + + it('returns false when label is a regular label', () => { + expect(vm.showScopedLabels(mockLabels[0])).toBe(false); + }); + }); + }); + + describe('template', () => { + it('renders component container element with classes `hide-collapsed value issuable-show-labels`', () => { + expect(vm.$el.classList.contains('hide-collapsed', 'value', 'issuable-show-labels')).toBe( + true, + ); + }); + + it('render slot content inside component when `labels` prop is empty', () => { + const vmEmptyLabels = createComponent([]); + + expect(vmEmptyLabels.$el.querySelector('.text-secondary').innerText.trim()).toBe( + mockConfig.emptyValueText, + ); + vmEmptyLabels.$destroy(); + }); + + it('renders label element with filter URL', () => { + expect(vm.$el.querySelector('a').getAttribute('href')).toBe( + '/gitlab-org/my-project/issues?label_name[]=Foo%20Label', + ); + }); + + it('renders label element and styles based on label details', () => { + const labelEl = vm.$el.querySelector('a span.badge.color-label'); + + expect(labelEl).not.toBeNull(); + expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);'); + expect(labelEl.innerText.trim()).toBe(mockLabels[0].title); + }); + + describe('label is of scoped-label type', () => { + it('renders a scoped-label-wrapper span to incorporate 2 anchors', () => { + expect(vm.$el.querySelector('span.scoped-label-wrapper')).not.toBeNull(); + }); + + it('renders anchor tag containing question icon', () => { + const anchor = vm.$el.querySelector('.scoped-label-wrapper a.scoped-label'); + + expect(anchor).not.toBeNull(); + expect(anchor.querySelector('i.fa-question-circle')).not.toBeNull(); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js new file mode 100644 index 00000000000..5cf25ca6f81 --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; + +describe('toggleSidebar', () => { + let vm; + beforeEach(() => { + const ToggleSidebar = Vue.extend(toggleSidebar); + vm = mountComponent(ToggleSidebar, { + collapsed: true, + }); + }); + + it('should render << when collapsed', () => { + expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-left')).toEqual(true); + }); + + it('should render >> when collapsed', () => { + vm.collapsed = false; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-right')).toEqual(true); + }); + }); + + it('should emit toggle event when button clicked', () => { + const toggle = jest.fn(); + vm.$on('toggle', toggle); + vm.$el.click(); + + expect(toggle).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js deleted file mode 100644 index 6bff1521695..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import Vue from 'vue'; -import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('collapsedCalendarIcon', () => { - let vm; - beforeEach(() => { - const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon); - vm = mountComponent(CollapsedCalendarIcon, { - containerClass: 'test-class', - text: 'text', - showIcon: false, - }); - }); - - it('should add class to container', () => { - expect(vm.$el.classList.contains('test-class')).toEqual(true); - }); - - it('should hide calendar icon if showIcon', () => { - expect(vm.$el.querySelector('.fa-calendar')).toBeNull(); - }); - - it('should render text', () => { - expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text'); - }); - - it('should emit click event when container is clicked', () => { - const click = jasmine.createSpy(); - vm.$on('click', click); - - vm.$el.click(); - - expect(click).toHaveBeenCalled(); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js deleted file mode 100644 index c507a97d37e..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import Vue from 'vue'; -import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('collapsedGroupedDatePicker', () => { - let vm; - beforeEach(() => { - const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker); - vm = mountComponent(CollapsedGroupedDatePicker, { - showToggleSidebar: true, - }); - }); - - describe('toggleCollapse events', () => { - beforeEach(done => { - spyOn(vm, 'toggleSidebar'); - vm.minDate = new Date('07/17/2016'); - Vue.nextTick(done); - }); - - it('should emit when collapsed-calendar-icon is clicked', () => { - vm.$el.querySelector('.sidebar-collapsed-icon').click(); - - expect(vm.toggleSidebar).toHaveBeenCalled(); - }); - }); - - describe('minDate and maxDate', () => { - beforeEach(done => { - vm.minDate = new Date('07/17/2016'); - vm.maxDate = new Date('07/17/2017'); - Vue.nextTick(done); - }); - - it('should render both collapsed-calendar-icon', () => { - const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); - - expect(icons.length).toEqual(2); - expect(icons[0].innerText.trim()).toEqual('Jul 17 2016'); - expect(icons[1].innerText.trim()).toEqual('Jul 17 2017'); - }); - }); - - describe('minDate', () => { - beforeEach(done => { - vm.minDate = new Date('07/17/2016'); - Vue.nextTick(done); - }); - - it('should render minDate in collapsed-calendar-icon', () => { - const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); - - expect(icons.length).toEqual(1); - expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016'); - }); - }); - - describe('maxDate', () => { - beforeEach(done => { - vm.maxDate = new Date('07/17/2017'); - Vue.nextTick(done); - }); - - it('should render maxDate in collapsed-calendar-icon', () => { - const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); - - expect(icons.length).toEqual(1); - expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017'); - }); - }); - - describe('no dates', () => { - it('should render None', () => { - const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); - - expect(icons.length).toEqual(1); - expect(icons[0].innerText.trim()).toEqual('None'); - }); - - it('should have tooltip as `Start and due date`', () => { - const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); - - expect(icons[0].dataset.originalTitle).toBe('Start and due date'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js deleted file mode 100644 index 805ba7b9947..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js +++ /dev/null @@ -1,121 +0,0 @@ -import Vue from 'vue'; -import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('sidebarDatePicker', () => { - let vm; - beforeEach(() => { - const SidebarDatePicker = Vue.extend(sidebarDatePicker); - vm = mountComponent(SidebarDatePicker, { - label: 'label', - isLoading: true, - }); - }); - - it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => { - const toggleCollapse = jasmine.createSpy(); - vm.$on('toggleCollapse', toggleCollapse); - - vm.$el.querySelector('.issuable-sidebar-header .gutter-toggle').click(); - - expect(toggleCollapse).toHaveBeenCalled(); - }); - - it('should render collapsed-calendar-icon', () => { - expect(vm.$el.querySelector('.sidebar-collapsed-icon')).toBeDefined(); - }); - - it('should render label', () => { - expect(vm.$el.querySelector('.title').innerText.trim()).toEqual('label'); - }); - - it('should render loading-icon when isLoading', () => { - expect(vm.$el.querySelector('.fa-spin')).toBeDefined(); - }); - - it('should render value when not editing', () => { - expect(vm.$el.querySelector('.value-content')).toBeDefined(); - }); - - it('should render None if there is no selectedDate', () => { - expect(vm.$el.querySelector('.value-content span').innerText.trim()).toEqual('None'); - }); - - it('should render date-picker when editing', done => { - vm.editing = true; - Vue.nextTick(() => { - expect(vm.$el.querySelector('.pika-label')).toBeDefined(); - done(); - }); - }); - - describe('editable', () => { - beforeEach(done => { - vm.editable = true; - Vue.nextTick(done); - }); - - it('should render edit button', () => { - expect(vm.$el.querySelector('.title .btn-blank').innerText.trim()).toEqual('Edit'); - }); - - it('should enable editing when edit button is clicked', done => { - vm.isLoading = false; - Vue.nextTick(() => { - vm.$el.querySelector('.title .btn-blank').click(); - - expect(vm.editing).toEqual(true); - done(); - }); - }); - }); - - it('should render date if selectedDate', done => { - vm.selectedDate = new Date('07/07/2017'); - Vue.nextTick(() => { - expect(vm.$el.querySelector('.value-content strong').innerText.trim()).toEqual('Jul 7, 2017'); - done(); - }); - }); - - describe('selectedDate and editable', () => { - beforeEach(done => { - vm.selectedDate = new Date('07/07/2017'); - vm.editable = true; - Vue.nextTick(done); - }); - - it('should render remove button if selectedDate and editable', () => { - expect(vm.$el.querySelector('.value-content .btn-blank').innerText.trim()).toEqual('remove'); - }); - - it('should emit saveDate when remove button is clicked', () => { - const saveDate = jasmine.createSpy(); - vm.$on('saveDate', saveDate); - - vm.$el.querySelector('.value-content .btn-blank').click(); - - expect(saveDate).toHaveBeenCalled(); - }); - }); - - describe('showToggleSidebar', () => { - beforeEach(done => { - vm.showToggleSidebar = true; - Vue.nextTick(done); - }); - - it('should render toggle-sidebar when showToggleSidebar', () => { - expect(vm.$el.querySelector('.title .gutter-toggle')).toBeDefined(); - }); - - it('should emit toggleCollapse when toggle sidebar is clicked', () => { - const toggleCollapse = jasmine.createSpy(); - vm.$on('toggleCollapse', toggleCollapse); - - vm.$el.querySelector('.title .gutter-toggle').click(); - - expect(toggleCollapse).toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js deleted file mode 100644 index c44b04009ca..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js +++ /dev/null @@ -1,130 +0,0 @@ -import Vue from 'vue'; - -import LabelsSelect from '~/labels_select'; -import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig, mockLabels } from './mock_data'; - -const createComponent = (config = mockConfig) => { - const Component = Vue.extend(baseComponent); - - return mountComponent(Component, config); -}; - -describe('BaseComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('hiddenInputName', () => { - it('returns correct string when showCreate prop is `true`', () => { - expect(vm.hiddenInputName).toBe('issue[label_names][]'); - }); - - it('returns correct string when showCreate prop is `false`', () => { - const mockConfigNonEditable = Object.assign({}, mockConfig, { showCreate: false }); - const vmNonEditable = createComponent(mockConfigNonEditable); - - expect(vmNonEditable.hiddenInputName).toBe('label_id[]'); - vmNonEditable.$destroy(); - }); - }); - - describe('createLabelTitle', () => { - it('returns `Create project label` when `isProject` prop is true', () => { - expect(vm.createLabelTitle).toBe('Create project label'); - }); - - it('return `Create group label` when `isProject` prop is false', () => { - const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false }); - const vmGroup = createComponent(mockConfigGroup); - - expect(vmGroup.createLabelTitle).toBe('Create group label'); - vmGroup.$destroy(); - }); - }); - - describe('manageLabelsTitle', () => { - it('returns `Manage project labels` when `isProject` prop is true', () => { - expect(vm.manageLabelsTitle).toBe('Manage project labels'); - }); - - it('return `Manage group labels` when `isProject` prop is false', () => { - const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false }); - const vmGroup = createComponent(mockConfigGroup); - - expect(vmGroup.manageLabelsTitle).toBe('Manage group labels'); - vmGroup.$destroy(); - }); - }); - }); - - describe('methods', () => { - describe('handleClick', () => { - it('emits onLabelClick event with label and list of labels as params', () => { - spyOn(vm, '$emit'); - vm.handleClick(mockLabels[0]); - - expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]); - }); - }); - - describe('handleCollapsedValueClick', () => { - it('emits toggleCollapse event on component', () => { - spyOn(vm, '$emit'); - vm.handleCollapsedValueClick(); - - expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse'); - }); - }); - - describe('handleDropdownHidden', () => { - it('emits onDropdownClose event on component', () => { - spyOn(vm, '$emit'); - vm.handleDropdownHidden(); - - expect(vm.$emit).toHaveBeenCalledWith('onDropdownClose'); - }); - }); - }); - - describe('mounted', () => { - it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => { - expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true); - }); - }); - - describe('template', () => { - it('renders component container element with classes `block labels`', () => { - expect(vm.$el.classList.contains('block')).toBe(true); - expect(vm.$el.classList.contains('labels')).toBe(true); - }); - - it('renders `.selectbox` element', () => { - expect(vm.$el.querySelector('.selectbox')).not.toBeNull(); - expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;'); - }); - - it('renders `.dropdown` element', () => { - expect(vm.$el.querySelector('.dropdown')).not.toBeNull(); - }); - - it('renders `.dropdown-menu` element', () => { - const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu'); - - expect(dropdownMenuEl).not.toBeNull(); - expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull(); - expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull(); - expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js deleted file mode 100644 index 0689fc1cf1f..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js +++ /dev/null @@ -1,95 +0,0 @@ -import Vue from 'vue'; - -import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig, mockLabels } from './mock_data'; - -const componentConfig = Object.assign({}, mockConfig, { - fieldName: 'label_id[]', - labels: mockLabels, - showExtraOptions: false, -}); - -const createComponent = (config = componentConfig) => { - const Component = Vue.extend(dropdownButtonComponent); - - return mountComponent(Component, config); -}; - -describe('DropdownButtonComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('dropdownToggleText', () => { - it('returns text as `Label` when `labels` prop is empty array', () => { - const mockEmptyLabels = Object.assign({}, componentConfig, { labels: [] }); - const vmEmptyLabels = createComponent(mockEmptyLabels); - - expect(vmEmptyLabels.dropdownToggleText).toBe('Label'); - vmEmptyLabels.$destroy(); - }); - - it('returns first label name with remaining label count when `labels` prop has more than one item', () => { - const mockMoreLabels = Object.assign({}, componentConfig, { - labels: mockLabels.concat(mockLabels), - }); - const vmMoreLabels = createComponent(mockMoreLabels); - - expect(vmMoreLabels.dropdownToggleText).toBe( - `Foo Label +${mockMoreLabels.labels.length - 1} more`, - ); - vmMoreLabels.$destroy(); - }); - - it('returns first label name when `labels` prop has only one item present', () => { - const singleLabel = Object.assign({}, componentConfig, { - labels: [mockLabels[0]], - }); - const vmSingleLabel = createComponent(singleLabel); - - expect(vmSingleLabel.dropdownToggleText).toBe(mockLabels[0].title); - - vmSingleLabel.$destroy(); - }); - }); - }); - - describe('template', () => { - it('renders component container element of type `button`', () => { - expect(vm.$el.nodeName).toBe('BUTTON'); - }); - - it('renders component container element with required data attributes', () => { - expect(vm.$el.dataset.abilityName).toBe(vm.abilityName); - expect(vm.$el.dataset.fieldName).toBe(vm.fieldName); - expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath); - expect(vm.$el.dataset.labels).toBe(vm.labelsPath); - expect(vm.$el.dataset.namespacePath).toBe(vm.namespace); - expect(vm.$el.dataset.showAny).not.toBeDefined(); - }); - - it('renders dropdown toggle text element', () => { - const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text'); - - expect(dropdownToggleTextEl).not.toBeNull(); - expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label +1 more'); - }); - - it('renders dropdown button icon', () => { - const dropdownIconEl = vm.$el.querySelector('i.fa'); - - expect(dropdownIconEl).not.toBeNull(); - expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js deleted file mode 100644 index b8f32f96332..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js +++ /dev/null @@ -1,108 +0,0 @@ -import Vue from 'vue'; - -import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockSuggestedColors } from './mock_data'; - -const createComponent = headerTitle => { - const Component = Vue.extend(dropdownCreateLabelComponent); - - return mountComponent(Component, { - headerTitle, - }); -}; - -describe('DropdownCreateLabelComponent', () => { - let vm; - - beforeEach(() => { - gon.suggested_label_colors = mockSuggestedColors; - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('created', () => { - it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => { - expect(vm.suggestedColors.length).toBe(mockSuggestedColors.length); - }); - }); - - describe('template', () => { - it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => { - expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true); - }); - - it('renders `Go back` button on component header', () => { - const backButtonEl = vm.$el.querySelector( - '.dropdown-title button.dropdown-title-button.dropdown-menu-back', - ); - - expect(backButtonEl).not.toBe(null); - expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null); - }); - - it('renders component header element as `Create new label` when `headerTitle` prop is not provided', () => { - const headerEl = vm.$el.querySelector('.dropdown-title'); - - expect(headerEl.innerText.trim()).toContain('Create new label'); - }); - - it('renders component header element with value of `headerTitle` prop', () => { - const headerTitle = 'Create project label'; - const vmWithHeaderTitle = createComponent(headerTitle); - const headerEl = vmWithHeaderTitle.$el.querySelector('.dropdown-title'); - - expect(headerEl.innerText.trim()).toContain(headerTitle); - vmWithHeaderTitle.$destroy(); - }); - - it('renders `Close` button on component header', () => { - const closeButtonEl = vm.$el.querySelector( - '.dropdown-title button.dropdown-title-button.dropdown-menu-close', - ); - - expect(closeButtonEl).not.toBe(null); - expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null); - }); - - it('renders `Name new label` input element', () => { - expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null); - expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null); - }); - - it('renders suggested colors list elements', () => { - const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown'); - - expect(colorsListContainerEl).not.toBe(null); - expect(colorsListContainerEl.querySelectorAll('a').length).toBe(mockSuggestedColors.length); - - const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0]; - - expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0]); - expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);'); - }); - - it('renders color input element', () => { - expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null); - expect( - vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview'), - ).not.toBe(null); - - expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null); - }); - - it('renders component action buttons', () => { - const createBtnEl = vm.$el.querySelector('button.js-new-label-btn'); - const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn'); - - expect(createBtnEl).not.toBe(null); - expect(createBtnEl.innerText.trim()).toBe('Create'); - expect(cancelBtnEl.innerText.trim()).toBe('Cancel'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js deleted file mode 100644 index 3711e9dac8c..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js +++ /dev/null @@ -1,76 +0,0 @@ -import Vue from 'vue'; - -import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig } from './mock_data'; - -const createComponent = ( - labelsWebUrl = mockConfig.labelsWebUrl, - createLabelTitle, - manageLabelsTitle, -) => { - const Component = Vue.extend(dropdownFooterComponent); - - return mountComponent(Component, { - labelsWebUrl, - createLabelTitle, - manageLabelsTitle, - }); -}; - -describe('DropdownFooterComponent', () => { - const createLabelTitle = 'Create project label'; - const manageLabelsTitle = 'Manage project labels'; - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('template', () => { - it('renders link element with `Create new label` when `createLabelTitle` prop is not provided', () => { - const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page'); - - expect(createLabelEl).not.toBeNull(); - expect(createLabelEl.innerText.trim()).toBe('Create new label'); - }); - - it('renders link element with value of `createLabelTitle` prop', () => { - const vmWithCreateLabelTitle = createComponent(mockConfig.labelsWebUrl, createLabelTitle); - const createLabelEl = vmWithCreateLabelTitle.$el.querySelector( - '.dropdown-footer-list .dropdown-toggle-page', - ); - - expect(createLabelEl.innerText.trim()).toBe(createLabelTitle); - vmWithCreateLabelTitle.$destroy(); - }); - - it('renders link element with `Manage labels` when `manageLabelsTitle` prop is not provided', () => { - const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link'); - - expect(manageLabelsEl).not.toBeNull(); - expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl); - expect(manageLabelsEl.innerText.trim()).toBe('Manage labels'); - }); - - it('renders link element with value of `manageLabelsTitle` prop', () => { - const vmWithManageLabelsTitle = createComponent( - mockConfig.labelsWebUrl, - createLabelTitle, - manageLabelsTitle, - ); - const manageLabelsEl = vmWithManageLabelsTitle.$el.querySelector( - '.dropdown-footer-list .dropdown-external-link', - ); - - expect(manageLabelsEl.innerText.trim()).toBe(manageLabelsTitle); - vmWithManageLabelsTitle.$destroy(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js deleted file mode 100644 index 115e21e4f9f..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import Vue from 'vue'; - -import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const createComponent = () => { - const Component = Vue.extend(dropdownHeaderComponent); - - return mountComponent(Component); -}; - -describe('DropdownHeaderComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('template', () => { - it('renders header text element', () => { - const headerEl = vm.$el.querySelector('.dropdown-title span'); - - expect(headerEl.innerText.trim()).toBe('Assign labels'); - }); - - it('renders `Close` button element', () => { - const closeBtnEl = vm.$el.querySelector( - '.dropdown-title button.dropdown-title-button.dropdown-menu-close', - ); - - expect(closeBtnEl).not.toBeNull(); - expect(closeBtnEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js deleted file mode 100644 index c30e619e76b..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import Vue from 'vue'; - -import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const createComponent = () => { - const Component = Vue.extend(dropdownSearchInputComponent); - - return mountComponent(Component); -}; - -describe('DropdownSearchInputComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('template', () => { - it('renders input element with type `search`', () => { - const inputEl = vm.$el.querySelector('input.dropdown-input-field'); - - expect(inputEl).not.toBeNull(); - expect(inputEl.getAttribute('type')).toBe('search'); - }); - - it('renders search icon element', () => { - expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull(); - }); - - it('renders clear search icon element', () => { - expect( - vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'), - ).not.toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js deleted file mode 100644 index 6c84d2e167c..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import Vue from 'vue'; - -import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const createComponent = (canEdit = true) => { - const Component = Vue.extend(dropdownTitleComponent); - - return mountComponent(Component, { - canEdit, - }); -}; - -describe('DropdownTitleComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('template', () => { - it('renders title text', () => { - expect(vm.$el.classList.contains('title', 'hide-collapsed')).toBe(true); - expect(vm.$el.innerText.trim()).toContain('Labels'); - }); - - it('renders spinner icon element', () => { - expect(vm.$el.querySelector('.fa-spinner.fa-spin.block-loading')).not.toBeNull(); - }); - - it('renders `Edit` button element', () => { - const editBtnEl = vm.$el.querySelector('button.edit-link.js-sidebar-dropdown-toggle'); - - expect(editBtnEl).not.toBeNull(); - expect(editBtnEl.innerText.trim()).toBe('Edit'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js deleted file mode 100644 index 4d3de5e474d..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js +++ /dev/null @@ -1,98 +0,0 @@ -import Vue from 'vue'; - -import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockLabels } from './mock_data'; - -const createComponent = (labels = mockLabels) => { - const Component = Vue.extend(dropdownValueCollapsedComponent); - - return mountComponent(Component, { - labels, - }); -}; - -describe('DropdownValueCollapsedComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('labelsList', () => { - it('returns default text when `labels` prop is empty array', () => { - const vmEmptyLabels = createComponent([]); - - expect(vmEmptyLabels.labelsList).toBe('Labels'); - vmEmptyLabels.$destroy(); - }); - - it('returns labels names separated by coma when `labels` prop has more than one item', () => { - const labels = mockLabels.concat(mockLabels); - const vmMoreLabels = createComponent(labels); - - const expectedText = labels.map(label => label.title).join(', '); - - expect(vmMoreLabels.labelsList).toBe(expectedText); - vmMoreLabels.$destroy(); - }); - - it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => { - const mockMoreLabels = Object.assign([], mockLabels); - for (let i = 0; i < 6; i += 1) { - mockMoreLabels.unshift(mockLabels[0]); - } - - const vmMoreLabels = createComponent(mockMoreLabels); - - const expectedText = `${mockMoreLabels - .slice(0, 5) - .map(label => label.title) - .join(', ')}, and ${mockMoreLabels.length - 5} more`; - - expect(vmMoreLabels.labelsList).toBe(expectedText); - vmMoreLabels.$destroy(); - }); - - it('returns first label name when `labels` prop has only one item present', () => { - const text = mockLabels.map(label => label.title).join(', '); - - expect(vm.labelsList).toBe(text); - }); - }); - }); - - describe('methods', () => { - describe('handleClick', () => { - it('emits onValueClick event on component', () => { - spyOn(vm, '$emit'); - vm.handleClick(); - - expect(vm.$emit).toHaveBeenCalledWith('onValueClick'); - }); - }); - }); - - describe('template', () => { - it('renders component container element with tooltip`', () => { - expect(vm.$el.dataset.placement).toBe('left'); - expect(vm.$el.dataset.container).toBe('body'); - expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList); - }); - - it('renders tags icon element', () => { - expect(vm.$el.querySelector('.fa-tags')).not.toBeNull(); - }); - - it('renders labels count', () => { - expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js deleted file mode 100644 index 35a9c300953..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js +++ /dev/null @@ -1,137 +0,0 @@ -import Vue from 'vue'; -import $ from 'jquery'; - -import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -import { mockConfig, mockLabels } from './mock_data'; - -const createComponent = ( - labels = mockLabels, - labelFilterBasePath = mockConfig.labelFilterBasePath, -) => { - const Component = Vue.extend(dropdownValueComponent); - - return mountComponent(Component, { - labels, - labelFilterBasePath, - enableScopedLabels: true, - }); -}; - -describe('DropdownValueComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('isEmpty', () => { - it('returns true if `labels` prop is empty', () => { - const vmEmptyLabels = createComponent([]); - - expect(vmEmptyLabels.isEmpty).toBe(true); - vmEmptyLabels.$destroy(); - }); - - it('returns false if `labels` prop is empty', () => { - expect(vm.isEmpty).toBe(false); - }); - }); - }); - - describe('methods', () => { - describe('labelFilterUrl', () => { - it('returns URL string starting with labelFilterBasePath and encoded label.title', () => { - expect( - vm.labelFilterUrl({ - title: 'Foo bar', - }), - ).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20bar'); - }); - }); - - describe('labelStyle', () => { - it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => { - const label = { - textColor: '#FFFFFF', - color: '#BADA55', - }; - const styleObj = vm.labelStyle(label); - - expect(styleObj.color).toBe(label.textColor); - expect(styleObj.backgroundColor).toBe(label.color); - }); - }); - - describe('scopedLabelsDescription', () => { - it('returns html for tooltip', () => { - const html = vm.scopedLabelsDescription(mockLabels[1]); - const $el = $.parseHTML(html); - - expect($el[0]).toHaveClass('scoped-label-tooltip-title'); - expect($el[2].textContent).toEqual(mockLabels[1].description); - }); - }); - - describe('showScopedLabels', () => { - it('returns true if the label is scoped label', () => { - expect(vm.showScopedLabels(mockLabels[1])).toBe(true); - }); - - it('returns false when label is a regular label', () => { - expect(vm.showScopedLabels(mockLabels[0])).toBe(false); - }); - }); - }); - - describe('template', () => { - it('renders component container element with classes `hide-collapsed value issuable-show-labels`', () => { - expect(vm.$el.classList.contains('hide-collapsed', 'value', 'issuable-show-labels')).toBe( - true, - ); - }); - - it('render slot content inside component when `labels` prop is empty', () => { - const vmEmptyLabels = createComponent([]); - - expect(vmEmptyLabels.$el.querySelector('.text-secondary').innerText.trim()).toBe( - mockConfig.emptyValueText, - ); - vmEmptyLabels.$destroy(); - }); - - it('renders label element with filter URL', () => { - expect(vm.$el.querySelector('a').getAttribute('href')).toBe( - '/gitlab-org/my-project/issues?label_name[]=Foo%20Label', - ); - }); - - it('renders label element and styles based on label details', () => { - const labelEl = vm.$el.querySelector('a span.badge.color-label'); - - expect(labelEl).not.toBeNull(); - expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);'); - expect(labelEl.innerText.trim()).toBe(mockLabels[0].title); - }); - - describe('label is of scoped-label type', () => { - it('renders a scoped-label-wrapper span to incorporate 2 anchors', () => { - expect(vm.$el.querySelector('span.scoped-label-wrapper')).not.toBeNull(); - }); - - it('renders anchor tag containing question icon', () => { - const anchor = vm.$el.querySelector('.scoped-label-wrapper a.scoped-label'); - - expect(anchor).not.toBeNull(); - expect(anchor.querySelector('i.fa-question-circle')).not.toBeNull(); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js deleted file mode 100644 index c911a129173..00000000000 --- a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js +++ /dev/null @@ -1,32 +0,0 @@ -import Vue from 'vue'; -import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('toggleSidebar', () => { - let vm; - beforeEach(() => { - const ToggleSidebar = Vue.extend(toggleSidebar); - vm = mountComponent(ToggleSidebar, { - collapsed: true, - }); - }); - - it('should render << when collapsed', () => { - expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-left')).toEqual(true); - }); - - it('should render >> when collapsed', () => { - vm.collapsed = false; - Vue.nextTick(() => { - expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-right')).toEqual(true); - }); - }); - - it('should emit toggle event when button clicked', () => { - const toggle = jasmine.createSpy(); - vm.$on('toggle', toggle); - vm.$el.click(); - - expect(toggle).toHaveBeenCalled(); - }); -}); -- cgit v1.2.1 From 8703fdaae8243e9934d2456d57859f066020ccc4 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Fri, 26 Apr 2019 10:20:04 -0400 Subject: Fix base domain help text update --- spec/frontend/clusters/clusters_bundle_spec.js | 35 ++++++-------------------- 1 file changed, 7 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index 5103cb4f69f..a61103397eb 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -6,7 +6,7 @@ import { loadHTMLFixture } from 'helpers/fixtures'; import { setTestTimeout } from 'helpers/timeout'; import $ from 'jquery'; -const { INSTALLING, INSTALLABLE, INSTALLED, NOT_INSTALLABLE } = APPLICATION_STATUS; +const { INSTALLING, INSTALLABLE, INSTALLED } = APPLICATION_STATUS; describe('Clusters', () => { setTestTimeout(1000); @@ -317,13 +317,12 @@ describe('Clusters', () => { let ingressNewState; beforeEach(() => { - ingressPreviousState = { status: INSTALLABLE }; - ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + ingressPreviousState = { externalIp: null }; + ingressNewState = { externalIp: '127.0.0.1' }; }); - describe(`when ingress application new status is ${INSTALLED}`, () => { + describe(`when ingress have an external ip assigned`, () => { beforeEach(() => { - ingressNewState.status = INSTALLED; cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); }); @@ -338,31 +337,11 @@ describe('Clusters', () => { }); }); - describe(`when ingress application new status is different from ${INSTALLED}`, () => { + describe(`when ingress does not have an external ip assigned`, () => { it('hides custom domain help text', () => { - ingressNewState.status = NOT_INSTALLABLE; - cluster.ingressDomainHelpText.classList.remove('hide'); - - cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); - - expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); - }); - }); - - describe('when ingress application new status and old status are the same', () => { - it('does not display custom domain help text', () => { - ingressPreviousState.status = INSTALLED; - ingressNewState.status = ingressPreviousState.status; - - cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); - - expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); - }); - }); - - describe(`when ingress new status is ${INSTALLED} and there isn’t an ip assigned`, () => { - it('does not display custom domain help text', () => { + ingressPreviousState.externalIp = '127.0.0.1'; ingressNewState.externalIp = null; + cluster.ingressDomainHelpText.classList.remove('hide'); cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); -- cgit v1.2.1 From 4376167a04aaeed084ad93aab2e65550109235c6 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Fri, 26 Apr 2019 17:23:26 +0000 Subject: Add ProjectMetricsDashboardSetting model and table This new table will be used to store the external_dashboard_url which allows users to add a link to their external dashboards (ex Grafana) to the Metrics dashboard. --- .../settings/operations_controller_spec.rb | 14 ++++++ spec/factories/project_metrics_settings.rb | 8 ++++ spec/lib/gitlab/import_export/all_models.yml | 3 ++ .../gitlab/import_export/safe_model_attributes.yml | 6 ++- spec/models/project_metrics_setting_spec.rb | 55 ++++++++++++++++++++++ .../projects/operations/update_service_spec.rb | 50 ++++++++++++++++++++ 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 spec/factories/project_metrics_settings.rb create mode 100644 spec/models/project_metrics_setting_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb index 0dd73972098..aa9cd41ed19 100644 --- a/spec/controllers/projects/settings/operations_controller_spec.rb +++ b/spec/controllers/projects/settings/operations_controller_spec.rb @@ -166,6 +166,20 @@ describe Projects::Settings::OperationsController do end end + context 'metrics dashboard setting' do + describe 'PATCH #update' do + let(:params) do + { + metrics_setting_attributes: { + external_dashboard_url: 'https://gitlab.com' + } + } + end + + it_behaves_like 'PATCHable' + end + end + private def project_params(project, params = {}) diff --git a/spec/factories/project_metrics_settings.rb b/spec/factories/project_metrics_settings.rb new file mode 100644 index 00000000000..234753f9b87 --- /dev/null +++ b/spec/factories/project_metrics_settings.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :project_metrics_setting, class: ProjectMetricsSetting do + project + external_dashboard_url 'https://grafana.com' + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 54369ff75f4..482e9c05da8 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -322,6 +322,7 @@ project: - pool_repository - kubernetes_namespaces - error_tracking_setting +- metrics_setting award_emoji: - awardable - user @@ -360,3 +361,5 @@ error_tracking_setting: - project suggestions: - note +metrics_setting: +- project diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ebb62124cb1..591a3d296c2 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -606,7 +606,6 @@ ResourceLabelEvent: - user_id - created_at ErrorTracking::ProjectErrorTrackingSetting: -- id - api_url - project_id - project_name @@ -626,3 +625,8 @@ MergeRequestAssignee: - id - user_id - merge_request_id +ProjectMetricsSetting: +- project_id +- external_dashboard_url +- created_at +- updated_at diff --git a/spec/models/project_metrics_setting_spec.rb b/spec/models/project_metrics_setting_spec.rb new file mode 100644 index 00000000000..7df01625ba1 --- /dev/null +++ b/spec/models/project_metrics_setting_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectMetricsSetting do + describe 'Associations' do + it { is_expected.to belong_to(:project) } + end + + describe 'Validations' do + context 'when external_dashboard_url is over 255 chars' do + before do + subject.external_dashboard_url = 'https://' + 'a' * 250 + end + + it 'fails validation' do + expect(subject).not_to be_valid + expect(subject.errors.messages[:external_dashboard_url]) + .to include('is too long (maximum is 255 characters)') + end + end + + context 'with unsafe url' do + before do + subject.external_dashboard_url = %{https://replaceme.com/'>} + end + + it { is_expected.to be_invalid } + end + + context 'non ascii chars in external_dashboard_url' do + before do + subject.external_dashboard_url = 'http://gitlab.com/api/0/projects/project1/something€' + end + + it { is_expected.to be_invalid } + end + + context 'internal url in external_dashboard_url' do + before do + subject.external_dashboard_url = 'http://192.168.1.1' + end + + it { is_expected.to be_valid } + end + + context 'external_dashboard_url is blank' do + before do + subject.external_dashboard_url = '' + end + + it { is_expected.to be_invalid } + end + end +end diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb index 86b1ec83f50..7e765659b9d 100644 --- a/spec/services/projects/operations/update_service_spec.rb +++ b/spec/services/projects/operations/update_service_spec.rb @@ -11,6 +11,56 @@ describe Projects::Operations::UpdateService do subject { described_class.new(project, user, params) } describe '#execute' do + context 'metrics dashboard setting' do + let(:params) do + { + metrics_setting_attributes: { + external_dashboard_url: 'http://gitlab.com' + } + } + end + + context 'without existing metrics dashboard setting' do + it 'creates a setting' do + expect(result[:status]).to eq(:success) + + expect(project.reload.metrics_setting.external_dashboard_url).to eq( + 'http://gitlab.com' + ) + end + end + + context 'with existing metrics dashboard setting' do + before do + create(:project_metrics_setting, project: project) + end + + it 'updates the settings' do + expect(result[:status]).to eq(:success) + + expect(project.reload.metrics_setting.external_dashboard_url).to eq( + 'http://gitlab.com' + ) + end + + context 'with blank external_dashboard_url in params' do + let(:params) do + { + metrics_setting_attributes: { + external_dashboard_url: '' + } + } + end + + it 'destroys the metrics_setting entry in DB' do + expect(result[:status]).to eq(:success) + + expect(project.reload.metrics_setting).to be_nil + end + end + end + end + context 'error tracking' do context 'with existing error tracking setting' do let(:params) do -- cgit v1.2.1 From ba8113c3238d04ed753c49303b81a4b8e5ec3f23 Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Fri, 26 Apr 2019 10:14:29 -0400 Subject: Init MR Popovers on system note mounted All `renderGFM()` calls are finished before the system note is mounted so this MR inits the popover for each system note, and only the elements in the system note --- spec/frontend/vue_shared/components/notes/system_note_spec.js | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js index adcb1c858aa..dc66150ab8d 100644 --- a/spec/frontend/vue_shared/components/notes/system_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js @@ -1,6 +1,9 @@ import Vue from 'vue'; import issueSystemNote from '~/vue_shared/components/notes/system_note.vue'; import createStore from '~/notes/stores'; +import initMRPopovers from '~/mr_popover/index'; + +jest.mock('~/mr_popover/index', () => jest.fn()); describe('system note component', () => { let vm; @@ -56,4 +59,8 @@ describe('system note component', () => { it('removes wrapping paragraph from note HTML', () => { expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('closed'); }); + + it('should initMRPopovers onMount', () => { + expect(initMRPopovers).toHaveBeenCalled(); + }); }); -- cgit v1.2.1 From 07b5c50381884e06322a0dea3363f87bf62f399a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 26 Apr 2019 14:30:40 -0400 Subject: Remove variable masking feature flag --- spec/lib/gitlab/ci/variables/collection/item_spec.rb | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index 3ff2fe18c15..613814df23f 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -137,19 +137,5 @@ describe Gitlab::Ci::Variables::Collection::Item do .to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false) end end - - context 'when variable masking is disabled' do - before do - stub_feature_flags(variable_masking: false) - end - - it 'does not expose the masked field to the runner' do - runner_variable = described_class - .new(key: 'VAR', value: 'value', masked: true) - .to_runner_variable - - expect(runner_variable).to eq(key: 'VAR', value: 'value', public: true) - end - end end end -- cgit v1.2.1 From c948babf7088e26ae025c1476022699bfe9fb107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Fri, 26 Apr 2019 06:11:47 +0200 Subject: Add specs to group routing spec --- spec/routing/group_routing_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb index 53271550e8b..00ca394a50b 100644 --- a/spec/routing/group_routing_spec.rb +++ b/spec/routing/group_routing_spec.rb @@ -133,5 +133,19 @@ describe "Groups", "routing" do let(:resource) { create(:group, parent: parent, path: 'activity') } end end + + describe 'subgroup "boards"' do + it 'shows group show page' do + allow(Group).to receive(:find_by_full_path).with('gitlabhq/boards', any_args).and_return(true) + + expect(get('/groups/gitlabhq/boards')).to route_to('groups#show', id: 'gitlabhq/boards') + end + + it 'shows boards index page' do + allow(Group).to receive(:find_by_full_path).with('gitlabhq', any_args).and_return(true) + + expect(get('/groups/gitlabhq/-/boards')).to route_to('groups/boards#index', group_id: 'gitlabhq') + end + end end end -- cgit v1.2.1 From fe86890b9d7a720648e7570d227c438d6ca7f25a Mon Sep 17 00:00:00 2001 From: Jason Goodman Date: Fri, 26 Apr 2019 21:08:41 +0000 Subject: Add deployment events to chat notification services This enables sending a chat message to Slack or Mattermost upon a successful, failed, or canceled deployment --- spec/factories/deployments.rb | 2 +- spec/features/admin/admin_settings_spec.rb | 15 ++- spec/lib/gitlab/data_builder/deployment_spec.rb | 36 +++++ .../gitlab/import_export/safe_model_attributes.yml | 1 + spec/models/ci/build_spec.rb | 4 + spec/models/deployment_spec.rb | 21 +++ .../chat_message/deployment_message_spec.rb | 150 +++++++++++++++++++++ .../microsoft_teams_service_spec.rb | 6 + spec/services/update_deployment_service_spec.rb | 1 + .../shared_examples/models/chat_service_spec.rb | 6 + ...ack_mattermost_notifications_shared_examples.rb | 8 ++ spec/workers/build_success_worker_spec.rb | 1 + spec/workers/deployments/finished_worker_spec.rb | 39 ++++++ 13 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 spec/lib/gitlab/data_builder/deployment_spec.rb create mode 100644 spec/models/project_services/chat_message/deployment_message_spec.rb create mode 100644 spec/workers/deployments/finished_worker_spec.rb (limited to 'spec') diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index 011c98599a3..db438ad32d3 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :deployment, class: Deployment do - sha '97de212e80737a608d939f648d959671fb0a0142' + sha 'b83d6e391c22777fca1ed3012fce84f633d7fed0' ref 'master' tag false user nil diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 04f39b807d7..3c9a9d61756 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -230,6 +230,13 @@ describe 'Admin updates settings' do expect(find_field('Username').value).to eq 'test_user' expect(find('#service_push_channel').value).to eq '#test_channel' end + + it 'defaults Deployment events to false for chat notification template settings' do + first(:link, 'Service Templates').click + click_link 'Slack notifications' + + expect(find_field('Deployment')).not_to be_checked + end end context 'CI/CD page' do @@ -373,10 +380,14 @@ describe 'Admin updates settings' do def check_all_events page.check('Active') page.check('Push') - page.check('Tag push') - page.check('Note') page.check('Issue') + page.check('Confidential issue') page.check('Merge request') + page.check('Note') + page.check('Confidential note') + page.check('Tag push') page.check('Pipeline') + page.check('Wiki page') + page.check('Deployment') end end diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb new file mode 100644 index 00000000000..b89a44e178b --- /dev/null +++ b/spec/lib/gitlab/data_builder/deployment_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::DataBuilder::Deployment do + describe '.build' do + it 'returns the object kind for a deployment' do + deployment = build(:deployment) + + data = described_class.build(deployment) + + expect(data[:object_kind]).to eq('deployment') + end + + it 'returns data for the given build' do + environment = create(:environment, name: "somewhere") + project = create(:project, :repository, name: 'myproj') + commit = project.commit('HEAD') + deployment = create(:deployment, status: :failed, environment: environment, sha: commit.sha, project: project) + deployable = deployment.deployable + expected_deployable_url = Gitlab::Routing.url_helpers.project_job_url(deployable.project, deployable) + expected_commit_url = Gitlab::UrlBuilder.build(commit) + + data = described_class.build(deployment) + + expect(data[:status]).to eq('failed') + expect(data[:deployable_id]).to eq(deployable.id) + expect(data[:deployable_url]).to eq(expected_deployable_url) + expect(data[:environment]).to eq("somewhere") + expect(data[:project]).to eq(project.hook_attrs) + expect(data[:short_sha]).to eq(deployment.short_sha) + expect(data[:user]).to eq(deployment.user.hook_attrs) + expect(data[:commit_url]).to eq(expected_commit_url) + end + end +end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 591a3d296c2..9093d21647a 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -423,6 +423,7 @@ Service: - wiki_page_events - confidential_issues_events - confidential_note_events +- deployment_events ProjectHook: - id - url diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 3a7d20a58c8..339483d4f7d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -856,6 +856,10 @@ describe Ci::Build do let(:deployment) { build.deployment } let(:environment) { deployment.environment } + before do + allow(Deployments::FinishedWorker).to receive(:perform_async) + end + it 'has deployments record with created status' do expect(deployment).to be_created expect(environment.name).to eq('review/master') diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index d9170d5fa07..0ca758429b8 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -102,6 +102,13 @@ describe Deployment do deployment.succeed! end + + it 'executes Deployments::FinishedWorker asynchronously' do + expect(Deployments::FinishedWorker) + .to receive(:perform_async).with(deployment.id) + + deployment.succeed! + end end context 'when deployment failed' do @@ -115,6 +122,13 @@ describe Deployment do expect(deployment.finished_at).to be_like_time(Time.now) end end + + it 'executes Deployments::FinishedWorker asynchronously' do + expect(Deployments::FinishedWorker) + .to receive(:perform_async).with(deployment.id) + + deployment.drop! + end end context 'when deployment was canceled' do @@ -128,6 +142,13 @@ describe Deployment do expect(deployment.finished_at).to be_like_time(Time.now) end end + + it 'executes Deployments::FinishedWorker asynchronously' do + expect(Deployments::FinishedWorker) + .to receive(:perform_async).with(deployment.id) + + deployment.cancel! + end end end diff --git a/spec/models/project_services/chat_message/deployment_message_spec.rb b/spec/models/project_services/chat_message/deployment_message_spec.rb new file mode 100644 index 00000000000..86565ce8b01 --- /dev/null +++ b/spec/models/project_services/chat_message/deployment_message_spec.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ChatMessage::DeploymentMessage do + describe '#pretext' do + it 'returns a message with the data returned by the deployment data builder' do + environment = create(:environment, name: "myenvironment") + project = create(:project, :repository) + commit = project.commit('HEAD') + deployment = create(:deployment, status: :success, environment: environment, project: project, sha: commit.sha) + data = Gitlab::DataBuilder::Deployment.build(deployment) + + message = described_class.new(data) + + expect(message.pretext).to eq("Deploy to myenvironment succeeded") + end + + it 'returns a message for a successful deployment' do + data = { + status: 'success', + environment: 'production' + } + + message = described_class.new(data) + + expect(message.pretext).to eq('Deploy to production succeeded') + end + + it 'returns a message for a failed deployment' do + data = { + status: 'failed', + environment: 'production' + } + + message = described_class.new(data) + + expect(message.pretext).to eq('Deploy to production failed') + end + + it 'returns a message for a canceled deployment' do + data = { + status: 'canceled', + environment: 'production' + } + + message = described_class.new(data) + + expect(message.pretext).to eq('Deploy to production canceled') + end + + it 'returns a message for a deployment to another environment' do + data = { + status: 'success', + environment: 'staging' + } + + message = described_class.new(data) + + expect(message.pretext).to eq('Deploy to staging succeeded') + end + + it 'returns a message for a deployment with any other status' do + data = { + status: 'unknown', + environment: 'staging' + } + + message = described_class.new(data) + + expect(message.pretext).to eq('Deploy to staging unknown') + end + end + + describe '#attachments' do + def deployment_data(params) + { + object_kind: "deployment", + status: "success", + deployable_id: 3, + deployable_url: "deployable_url", + environment: "sandbox", + project: { + name: "greatproject", + web_url: "project_web_url", + path_with_namespace: "project_path_with_namespace" + }, + user: { + name: "Jane Person", + username: "jane" + }, + short_sha: "12345678", + commit_url: "commit_url" + }.merge(params) + end + + it 'returns attachments with the data returned by the deployment data builder' do + user = create(:user, name: "John Smith", username: "smith") + namespace = create(:namespace, name: "myspace") + project = create(:project, :repository, namespace: namespace, name: "myproject") + commit = project.commit('HEAD') + environment = create(:environment, name: "myenvironment", project: project) + ci_build = create(:ci_build, project: project) + deployment = create(:deployment, :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha) + job_url = Gitlab::Routing.url_helpers.project_job_url(project, ci_build) + commit_url = Gitlab::UrlBuilder.build(deployment.commit) + data = Gitlab::DataBuilder::Deployment.build(deployment) + + message = described_class.new(data) + + expect(message.attachments).to eq([{ + text: "[myspace/myproject](#{project.web_url})\n[Job ##{ci_build.id}](#{job_url}), SHA [#{deployment.short_sha}](#{commit_url}), by John Smith (smith)", + color: "good" + }]) + end + + it 'returns attachments for a failed deployment' do + data = deployment_data(status: 'failed') + + message = described_class.new(data) + + expect(message.attachments).to eq([{ + text: "[project_path_with_namespace](project_web_url)\n[Job #3](deployable_url), SHA [12345678](commit_url), by Jane Person (jane)", + color: "danger" + }]) + end + + it 'returns attachments for a canceled deployment' do + data = deployment_data(status: 'canceled') + + message = described_class.new(data) + + expect(message.attachments).to eq([{ + text: "[project_path_with_namespace](project_web_url)\n[Job #3](deployable_url), SHA [12345678](commit_url), by Jane Person (jane)", + color: "warning" + }]) + end + + it 'uses a neutral color for a deployment with any other status' do + data = deployment_data(status: 'some-new-status-we-make-in-the-future') + + message = described_class.new(data) + + expect(message.attachments).to eq([{ + text: "[project_path_with_namespace](project_web_url)\n[Job #3](deployable_url), SHA [12345678](commit_url), by Jane Person (jane)", + color: "#334455" + }]) + end + end +end diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb index 521d5265753..c025d7c882e 100644 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ b/spec/models/project_services/microsoft_teams_service_spec.rb @@ -30,6 +30,12 @@ describe MicrosoftTeamsService do end end + describe '.supported_events' do + it 'does not support deployment_events' do + expect(described_class.supported_events).not_to include('deployment') + end + end + describe "#execute" do let(:user) { create(:user) } set(:project) { create(:project, :repository, :wiki_repo) } diff --git a/spec/services/update_deployment_service_spec.rb b/spec/services/update_deployment_service_spec.rb index c664bac39fc..7dc52f6816a 100644 --- a/spec/services/update_deployment_service_spec.rb +++ b/spec/services/update_deployment_service_spec.rb @@ -22,6 +22,7 @@ describe UpdateDeploymentService do subject(:service) { described_class.new(deployment) } before do + allow(Deployments::FinishedWorker).to receive(:perform_async) job.success! # Create/Succeed deployment end diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_spec.rb index cf1d52a9616..9d3ce5e2be1 100644 --- a/spec/support/shared_examples/models/chat_service_spec.rb +++ b/spec/support/shared_examples/models/chat_service_spec.rb @@ -25,6 +25,12 @@ shared_examples_for "chat service" do |service_name| end end + describe '.supported_events' do + it 'does not support deployment_events' do + expect(described_class.supported_events).not_to include('deployment') + end + end + describe "#execute" do let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb index 940c24c8d67..c31346374f4 100644 --- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb @@ -106,6 +106,14 @@ RSpec.shared_examples 'slack or mattermost notifications' do expect(WebMock).to have_requested(:post, webhook_url).once end + it "calls Slack/Mattermost API for deployment events" do + deployment_event_data = { object_kind: 'deployment' } + + chat_service.execute(deployment_event_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + it 'uses the username as an option for slack when configured' do allow(chat_service).to receive(:username).and_return(username) diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index 065aeaf2b65..ffe8796ded9 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -15,6 +15,7 @@ describe BuildSuccessWorker do let!(:build) { create(:ci_build, :deploy_to_production) } before do + allow(Deployments::FinishedWorker).to receive(:perform_async) Deployment.delete_all build.reload end diff --git a/spec/workers/deployments/finished_worker_spec.rb b/spec/workers/deployments/finished_worker_spec.rb new file mode 100644 index 00000000000..df62821e2cd --- /dev/null +++ b/spec/workers/deployments/finished_worker_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Deployments::FinishedWorker do + let(:worker) { described_class.new } + + describe '#perform' do + before do + allow(ProjectServiceWorker).to receive(:perform_async) + end + + it 'executes project services for deployment_hooks' do + deployment = create(:deployment) + project = deployment.project + service = create(:service, type: 'SlackService', project: project, deployment_events: true, active: true) + + worker.perform(deployment.id) + + expect(ProjectServiceWorker).to have_received(:perform_async).with(service.id, an_instance_of(Hash)) + end + + it 'does not execute an inactive service' do + deployment = create(:deployment) + project = deployment.project + create(:service, type: 'SlackService', project: project, deployment_events: true, active: false) + + worker.perform(deployment.id) + + expect(ProjectServiceWorker).not_to have_received(:perform_async) + end + + it 'does nothing if a deployment with the given id does not exist' do + worker.perform(0) + + expect(ProjectServiceWorker).not_to have_received(:perform_async) + end + end +end -- cgit v1.2.1 From 266ba11ed77f00f5fd83070a85c0bc8101203eab Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 26 Apr 2019 15:25:51 -0700 Subject: Fix Profiler to work with Ruby 5.1 Ruby 5.1 now requires keyword arguments to pass along parameters and headers for ActionDispatch::Integration::RequestHelpers. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/61051 --- spec/lib/gitlab/profiler_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index 9f2214f7ce7..5af52db7a1f 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -27,13 +27,13 @@ describe Gitlab::Profiler do it 'sends a POST request when data is passed' do post_data = '{"a":1}' - expect(app).to receive(:post).with(anything, post_data, anything) + expect(app).to receive(:post).with(anything, params: post_data, headers: anything) described_class.profile('/', post_data: post_data) end it 'uses the private_token for auth if given' do - expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token) + expect(app).to receive(:get).with('/', params: nil, headers: { 'Private-Token' => private_token }) expect(app).to receive(:get).with('/api/v4/users') described_class.profile('/', private_token: private_token) @@ -51,7 +51,7 @@ describe Gitlab::Profiler do user = double(:user) expect(described_class).to receive(:with_user).with(nil).and_call_original - expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token) + expect(app).to receive(:get).with('/', params: nil, headers: { 'Private-Token' => private_token }) expect(app).to receive(:get).with('/api/v4/users') described_class.profile('/', user: user, private_token: private_token) -- cgit v1.2.1 From db6989ddb0dc1dc630803ea3748542779b5f9090 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Sat, 27 Apr 2019 04:38:01 +0000 Subject: Add Let's Encrypt application settings Store Let's Encrypt account email in application settings Also add explicit terms of service consent --- spec/features/admin/admin_settings_spec.rb | 31 ++++++++++++++++++++++++++++++ spec/models/application_setting_spec.rb | 14 ++++++++++++++ 2 files changed, 45 insertions(+) (limited to 'spec') diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 3c9a9d61756..f9950b5b03f 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -375,6 +375,37 @@ describe 'Admin updates settings' do expect(Gitlab::CurrentSettings.pages_domain_verification_enabled?).to be_truthy expect(page).to have_content "Application settings saved successfully" end + + context 'When pages_auto_ssl is enabled' do + before do + stub_feature_flags(pages_auto_ssl: true) + visit preferences_admin_application_settings_path + end + + it "Change Pages Let's Encrypt settings" do + page.within('.as-pages') do + fill_in 'Email', with: 'my@test.example.com' + check "I have read and agree to the Let's Encrypt Terms of Service" + click_button 'Save changes' + end + + expect(Gitlab::CurrentSettings.lets_encrypt_notification_email).to eq 'my@test.example.com' + expect(Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted).to eq true + end + end + + context 'When pages_auto_ssl is disabled' do + before do + stub_feature_flags(pages_auto_ssl: false) + visit preferences_admin_application_settings_path + end + + it "Doesn't show Let's Encrypt options" do + page.within('.as-pages') do + expect(page).not_to have_content('Email') + end + end + end end def check_all_events diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index c7d7dbac736..f8dc1541dd3 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -31,6 +31,20 @@ describe ApplicationSetting do it { is_expected.to allow_value("dev.gitlab.com").for(:commit_email_hostname) } it { is_expected.not_to allow_value("@dev.gitlab").for(:commit_email_hostname) } + it { is_expected.to allow_value("myemail@gitlab.com").for(:lets_encrypt_notification_email) } + it { is_expected.to allow_value(nil).for(:lets_encrypt_notification_email) } + it { is_expected.not_to allow_value("notanemail").for(:lets_encrypt_notification_email) } + it { is_expected.not_to allow_value("myemail@example.com").for(:lets_encrypt_notification_email) } + it { is_expected.to allow_value("myemail@test.example.com").for(:lets_encrypt_notification_email) } + + context "when user accepted let's encrypt terms of service" do + before do + setting.update(lets_encrypt_terms_of_service_accepted: true) + end + + it { is_expected.not_to allow_value(nil).for(:lets_encrypt_notification_email) } + end + describe 'default_artifacts_expire_in' do it 'sets an error if it cannot parse' do setting.update(default_artifacts_expire_in: 'a') -- cgit v1.2.1 From 28f785404a6659d61c69ee4bfdaca915652d1759 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sun, 28 Apr 2019 10:27:08 +0700 Subject: Prevent concurrent execution of PipelineScheduleWorker Currently, PipelineScheduleWorker is fired in a short period on our production server. We can stop this behavior by locking the execution thread with in_lock method. --- spec/workers/pipeline_schedule_worker_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index f23910d23be..8c604b13297 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe PipelineScheduleWorker do + include ExclusiveLeaseHelpers + subject { described_class.new.perform } set(:project) { create(:project, :repository) } @@ -39,6 +41,16 @@ describe PipelineScheduleWorker do it_behaves_like 'successful scheduling' + context 'when exclusive lease has already been taken by the other instance' do + before do + stub_exclusive_lease_taken(described_class::EXCLUSIVE_LOCK_KEY, timeout: described_class::LOCK_TIMEOUT) + end + + it 'raises an error and does not start creating pipelines' do + expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError) + end + end + context 'when the latest commit contains [ci skip]' do before do allow_any_instance_of(Ci::Pipeline) -- cgit v1.2.1 From 825d759634773c82b6417ba8e7a9fdcb0f76561c Mon Sep 17 00:00:00 2001 From: Tiger Date: Fri, 26 Apr 2019 09:49:02 +1000 Subject: Fix deployments for Kubernetes service templates https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27687 --- spec/models/deployment_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 0ca758429b8..f51322e1404 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -400,6 +400,12 @@ describe Deployment do it { is_expected.to be_nil } end + context 'project uses the kubernetes service for deployments' do + let!(:service) { create(:kubernetes_service, project: project) } + + it { is_expected.to be_nil } + end + context 'project has a deployment platform' do let!(:cluster) { create(:cluster, projects: [project]) } let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) } -- cgit v1.2.1 From 33cf8edae170890ccd4510490f1dcd2f345c81fa Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 22 Apr 2019 18:07:19 +0200 Subject: Port changes for design management to CE This ports the changes from https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10462/ to CE --- spec/graphql/resolvers/issues_resolver_spec.rb | 155 ++++++++++++++----------- spec/lib/gitlab/git/repository_spec.rb | 6 + spec/models/repository_spec.rb | 65 +++++++++++ spec/support/helpers/graphql_helpers.rb | 13 ++- 4 files changed, 168 insertions(+), 71 deletions(-) (limited to 'spec') diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 5f9c180cbb7..399a33dae75 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -4,104 +4,119 @@ describe Resolvers::IssuesResolver do include GraphqlHelpers let(:current_user) { create(:user) } - set(:project) { create(:project) } - set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) } - set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) } - set(:label1) { create(:label, project: project) } - set(:label2) { create(:label, project: project) } - - before do - project.add_developer(current_user) - create(:label_link, label: label1, target: issue1) - create(:label_link, label: label1, target: issue2) - create(:label_link, label: label2, target: issue2) - end - - describe '#resolve' do - it 'finds all issues' do - expect(resolve_issues).to contain_exactly(issue1, issue2) - end - it 'filters by state' do - expect(resolve_issues(state: 'opened')).to contain_exactly(issue1) - expect(resolve_issues(state: 'closed')).to contain_exactly(issue2) + context "with a project" do + set(:project) { create(:project) } + set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) } + set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) } + set(:label1) { create(:label, project: project) } + set(:label2) { create(:label, project: project) } + + before do + project.add_developer(current_user) + create(:label_link, label: label1, target: issue1) + create(:label_link, label: label1, target: issue2) + create(:label_link, label: label2, target: issue2) end - it 'filters by labels' do - expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2) - expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2) - end + describe '#resolve' do + it 'finds all issues' do + expect(resolve_issues).to contain_exactly(issue1, issue2) + end - describe 'filters by created_at' do - it 'filters by created_before' do - expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1) + it 'filters by state' do + expect(resolve_issues(state: 'opened')).to contain_exactly(issue1) + expect(resolve_issues(state: 'closed')).to contain_exactly(issue2) end - it 'filters by created_after' do - expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2) + it 'filters by labels' do + expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2) + expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2) end - end - describe 'filters by updated_at' do - it 'filters by updated_before' do - expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1) + describe 'filters by created_at' do + it 'filters by created_before' do + expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1) + end + + it 'filters by created_after' do + expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2) + end end - it 'filters by updated_after' do - expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2) + describe 'filters by updated_at' do + it 'filters by updated_before' do + expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1) + end + + it 'filters by updated_after' do + expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2) + end end - end - describe 'filters by closed_at' do - let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) } + describe 'filters by closed_at' do + let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) } - it 'filters by closed_before' do - expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3) + it 'filters by closed_before' do + expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3) + end + + it 'filters by closed_after' do + expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2) + end end - it 'filters by closed_after' do - expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2) + it 'searches issues' do + expect(resolve_issues(search: 'foo')).to contain_exactly(issue2) end - end - it 'searches issues' do - expect(resolve_issues(search: 'foo')).to contain_exactly(issue2) - end + it 'sort issues' do + expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1] + end - it 'sort issues' do - expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1] - end + it 'returns issues user can see' do + project.add_guest(current_user) - it 'returns issues user can see' do - project.add_guest(current_user) + create(:issue, confidential: true) - create(:issue, confidential: true) + expect(resolve_issues).to contain_exactly(issue1, issue2) + end - expect(resolve_issues).to contain_exactly(issue1, issue2) - end + it 'finds a specific issue with iid' do + expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1) + end - it 'finds a specific issue with iid' do - expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1) - end + it 'finds a specific issue with iids' do + expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1) + end - it 'finds a specific issue with iids' do - expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1) - end + it 'finds multiple issues with iids' do + expect(resolve_issues(iids: [issue1.iid, issue2.iid])) + .to contain_exactly(issue1, issue2) + end - it 'finds multiple issues with iids' do - expect(resolve_issues(iids: [issue1.iid, issue2.iid])) - .to contain_exactly(issue1, issue2) - end + it 'finds only the issues within the project we are looking at' do + another_project = create(:project) + iids = [issue1, issue2].map(&:iid) + + iids.each do |iid| + create(:issue, project: another_project, iid: iid) + end - it 'finds only the issues within the project we are looking at' do - another_project = create(:project) - iids = [issue1, issue2].map(&:iid) + expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2) + end + end + end - iids.each do |iid| - create(:issue, project: another_project, iid: iid) + context "when passing a non existent, batch loaded project" do + let(:project) do + BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _| + loader.call("non-existent-path", nil) end + end - expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2) + it "returns nil without breaking" do + expect(resolve_issues(iids: ["don't", "break"])).to be_empty end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 45fe5d72937..5f8a2848944 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -95,6 +95,12 @@ describe Gitlab::Git::Repository, :seed_helper do end end + describe '#create_repository' do + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :create_repository do + subject { repository.create_repository } + end + end + describe '#branch_names' do subject { repository.branch_names } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4c354593b57..43ec1125087 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2487,4 +2487,69 @@ describe Repository do repository.merge_base('master', 'fix') end end + + describe '#create_if_not_exists' do + let(:project) { create(:project) } + let(:repository) { project.repository } + + it 'creates the repository if it did not exist' do + expect { repository.create_if_not_exists }.to change { repository.exists? }.from(false).to(true) + end + + it 'calls out to the repository client to create a repo' do + expect(repository.raw.gitaly_repository_client).to receive(:create_repository) + + repository.create_if_not_exists + end + + context 'it does nothing if the repository already existed' do + let(:project) { create(:project, :repository) } + + it 'does nothing if the repository already existed' do + expect(repository.raw.gitaly_repository_client).not_to receive(:create_repository) + + repository.create_if_not_exists + end + end + + context 'when the repository exists but the cache is not up to date' do + let(:project) { create(:project, :repository) } + + it 'does not raise errors' do + allow(repository).to receive(:exists?).and_return(false) + expect(repository.raw).to receive(:create_repository).and_call_original + + expect { repository.create_if_not_exists }.not_to raise_error + end + end + end + + describe "#blobs_metadata" do + set(:project) { create(:project, :repository) } + let(:repository) { project.repository } + + def expect_metadata_blob(thing) + expect(thing).to be_a(Blob) + expect(thing.data).to be_empty + end + + it "returns blob metadata in batch for HEAD" do + result = repository.blobs_metadata(["bar/branch-test.txt", "README.md", "does/not/exist"]) + + expect_metadata_blob(result.first) + expect_metadata_blob(result.second) + expect(result.size).to eq(2) + end + + it "returns blob metadata for a specified ref" do + result = repository.blobs_metadata(["files/ruby/feature.rb"], "feature") + + expect_metadata_blob(result.first) + end + + it "performs a single gitaly call", :request_store do + expect { repository.blobs_metadata(["bar/branch-test.txt", "readme.txt", "does/not/exist"]) } + .to change { Gitlab::GitalyClient.get_request_count }.by(1) + end + end end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 2f4e6e4c934..b49d743fb9a 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -61,7 +61,14 @@ module GraphqlHelpers def variables_for_mutation(name, input) graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h - { input_variable_name_for_mutation(name) => graphql_input }.to_json + result = { input_variable_name_for_mutation(name) => graphql_input } + + # Avoid trying to serialize multipart data into JSON + if graphql_input.values.none? { |value| io_value?(value) } + result.to_json + else + result + end end def input_variable_name_for_mutation(mutation_name) @@ -162,6 +169,10 @@ module GraphqlHelpers field.arguments.values.any? { |argument| argument.type.non_null? } end + def io_value?(value) + Array.wrap(value).any? { |v| v.respond_to?(:to_io) } + end + def field_type(field) field_type = field.type -- cgit v1.2.1 From fe31f63ce82e79332df9cf853c01ce318b9f92d5 Mon Sep 17 00:00:00 2001 From: John Cai Date: Mon, 29 Apr 2019 07:21:36 +0000 Subject: Add gitaly session id & catfile-cache feature flag --- spec/lib/gitlab/gitaly_client_spec.rb | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index f1acb1d9bc4..da1eb0c2618 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -142,6 +142,48 @@ describe Gitlab::GitalyClient do end end + describe '.request_kwargs' do + context 'when catfile-cache feature is enabled' do + before do + stub_feature_flags('gitaly_catfile-cache': true) + end + + it 'sets the gitaly-session-id in the metadata' do + results = described_class.request_kwargs('default', nil) + expect(results[:metadata]).to include('gitaly-session-id') + end + + context 'when RequestStore is not enabled' do + it 'sets a different gitaly-session-id per request' do + gitaly_session_id = described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id'] + + expect(described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']).not_to eq(gitaly_session_id) + end + end + + context 'when RequestStore is enabled', :request_store do + it 'sets the same gitaly-session-id on every outgoing request metadata' do + gitaly_session_id = described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id'] + + 3.times do + expect(described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']).to eq(gitaly_session_id) + end + end + end + end + + context 'when catfile-cache feature is disabled' do + before do + stub_feature_flags({ 'gitaly_catfile-cache': false }) + end + + it 'does not set the gitaly-session-id in the metadata' do + results = described_class.request_kwargs('default', nil) + expect(results[:metadata]).not_to include('gitaly-session-id') + end + end + end + describe 'enforce_gitaly_request_limits?' do def call_gitaly(count = 1) (1..count).each do -- cgit v1.2.1 From de69a808a015514fa7e4451f406fcc3d73734919 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Sat, 27 Apr 2019 16:35:32 +0200 Subject: Use correct k8s namespace in Prometheus queries Before this commit the wrong namespace could have been used in Prometheus queries for group-level installations. --- spec/lib/gitlab/prometheus/query_variables_spec.rb | 28 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb index 78c74266c61..048f4af6020 100644 --- a/spec/lib/gitlab/prometheus/query_variables_spec.rb +++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::Prometheus::QueryVariables do describe '.call' do + let(:project) { environment.project } let(:environment) { create(:environment) } let(:slug) { environment.slug } @@ -21,13 +22,32 @@ describe Gitlab::Prometheus::QueryVariables do end context 'with deployment platform' do - let(:kube_namespace) { environment.deployment_platform.actual_namespace } + context 'with project cluster' do + let(:kube_namespace) { environment.deployment_platform.actual_namespace } - before do - create(:cluster, :provided_by_user, projects: [environment.project]) + before do + create(:cluster, :project, :provided_by_user, projects: [project]) + end + + it { is_expected.to include(kube_namespace: kube_namespace) } end - it { is_expected.to include(kube_namespace: kube_namespace) } + context 'with group cluster' do + let(:cluster) { create(:cluster, :group, :provided_by_user, groups: [group]) } + let(:group) { create(:group) } + let(:project2) { create(:project) } + let(:kube_namespace) { k8s_ns.namespace } + + let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project) } + let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2) } + + before do + group.projects << project + group.projects << project2 + end + + it { is_expected.to include(kube_namespace: kube_namespace) } + end end end end -- cgit v1.2.1 From 80fec5c3295977c5559c420f51fd774391d3ca91 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Mon, 29 Apr 2019 10:25:31 +0000 Subject: Load environment metrics only for JSON endpoint When showing the HTML version of the environment metrics we don't need to fetch their metrics because we don't use them anymore on the HTML version. We use additional_metrics.json endpoint now. --- spec/controllers/projects/environments_controller_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index c1c4be45168..a62422d0229 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -342,11 +342,9 @@ describe Projects::EnvironmentsController do end context 'when environment has no metrics' do - before do - expect(environment).to receive(:metrics).and_return(nil) - end - it 'returns a metrics page' do + expect(environment).not_to receive(:metrics) + get :metrics, params: environment_params expect(response).to be_ok @@ -354,6 +352,8 @@ describe Projects::EnvironmentsController do context 'when requesting metrics as JSON' do it 'returns a metrics JSON document' do + expect(environment).to receive(:metrics).and_return(nil) + get :metrics, params: environment_params(format: :json) expect(response).to have_gitlab_http_status(204) -- cgit v1.2.1 From 2f36efa0871b61df2aa5f8c7546ac95148ecf4de Mon Sep 17 00:00:00 2001 From: khm Date: Mon, 29 Apr 2019 11:31:28 +0000 Subject: Fix links to wiki pages with subdirectories This commit contains the patch suggested by @fjsanpedro in the issue covering broken links in wiki search results. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/56234 --- .../search/user_searches_for_wiki_pages_spec.rb | 36 +++++++++++++++------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb index 7225ca65492..6d4facd0649 100644 --- a/spec/features/search/user_searches_for_wiki_pages_spec.rb +++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb @@ -14,22 +14,36 @@ describe 'User searches for wiki pages', :js do include_examples 'top right search form' - it 'finds a page' do - find('.js-search-project-dropdown').click + shared_examples 'search wiki blobs' do + it 'finds a page' do + find('.js-search-project-dropdown').click - page.within('.project-filter') do - click_link(project.full_name) - end + page.within('.project-filter') do + click_link(project.full_name) + end + + fill_in('dashboard_search', with: 'content') + find('.btn-search').click + + page.within('.search-filter') do + click_link('Wiki') + end - fill_in('dashboard_search', with: 'content') - find('.btn-search').click + page.within('.results') do + expect(find(:css, '.search-results')).to have_link(wiki_page.title, href: project_wiki_path(project, wiki_page.slug)) + end + end + end - page.within('.search-filter') do - click_link('Wiki') + context 'when searching by content' do + it_behaves_like 'search wiki blobs' do + let(:search_term) { 'content' } end + end - page.within('.results') do - expect(find(:css, '.search-results')).to have_link(wiki_page.title) + context 'when searching by title' do + it_behaves_like 'search wiki blobs' do + let(:search_term) { 'test_wiki' } end end end -- cgit v1.2.1 From ed3a2fc8d7fef157706859dd009e1662fdc3d4b5 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 25 Apr 2019 15:23:39 +0700 Subject: Fix CI_COMMIT_REF_NAME and SLUG variable With Pipelines for Merge Requests feature, users cannout keep using $CI_COMMIT_REF_NAME and _SLUG predefined variables for dynamic environments. We fix this problem by explicitly looking at the source ref. --- spec/models/ci/bridge_spec.rb | 2 ++ spec/models/ci/build_spec.rb | 18 ++++++++++++++++ spec/models/ci/pipeline_spec.rb | 48 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) (limited to 'spec') diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 44b5af5e5aa..eb32198265b 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -10,6 +10,8 @@ describe Ci::Bridge do create(:ci_bridge, pipeline: pipeline) end + it { is_expected.to include_module(Ci::PipelineDelegator) } + describe '#tags' do it 'only has a bridge tag' do expect(bridge.tags).to eq [:bridge] diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 339483d4f7d..59ec7310391 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -28,6 +28,7 @@ describe Ci::Build do it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) } it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) } it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } + it { is_expected.to include_module(Ci::PipelineDelegator) } it { is_expected.to be_a(ArtifactMigratable) } @@ -2273,6 +2274,19 @@ describe Ci::Build do it { user_variables.each { |v| is_expected.to include(v) } } end + context 'when build belongs to a pipeline for merge request' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_branch: 'improve/awesome') } + let(:pipeline) { merge_request.all_pipelines.first } + let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } + + it 'returns values based on source ref' do + is_expected.to include( + { key: 'CI_COMMIT_REF_NAME', value: 'improve/awesome', public: true, masked: false }, + { key: 'CI_COMMIT_REF_SLUG', value: 'improve-awesome', public: true, masked: false } + ) + end + end + context 'when build has an environment' do let(:environment_variables) do [ @@ -2664,6 +2678,8 @@ describe Ci::Build do ) end + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') } + it 'returns static predefined variables' do expect(build.variables.size).to be >= 28 expect(build.variables) @@ -2713,6 +2729,8 @@ describe Ci::Build do ) end + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') } + it 'does not persist the build' do expect(build).to be_valid expect(build).not_to be_persisted diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 3c823b78be7..9d0cd654f13 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -382,6 +382,54 @@ describe Ci::Pipeline, :mailer do end end + describe '#source_ref' do + subject { pipeline.source_ref } + + let(:pipeline) { create(:ci_pipeline, ref: 'feature') } + + it 'returns source ref' do + is_expected.to eq('feature') + end + + context 'when the pipeline is a detached merge request pipeline' do + let(:merge_request) { create(:merge_request) } + + let(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: merge_request.ref_path) + end + + it 'returns source ref' do + is_expected.to eq(merge_request.source_branch) + end + end + end + + describe '#source_ref_slug' do + subject { pipeline.source_ref_slug } + + let(:pipeline) { create(:ci_pipeline, ref: 'feature') } + + it 'slugifies with the source ref' do + expect(Gitlab::Utils).to receive(:slugify).with('feature') + + subject + end + + context 'when the pipeline is a detached merge request pipeline' do + let(:merge_request) { create(:merge_request) } + + let(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: merge_request.ref_path) + end + + it 'slugifies with the source ref of the merge request' do + expect(Gitlab::Utils).to receive(:slugify).with(merge_request.source_branch) + + subject + end + end + end + describe '.triggered_for_branch' do subject { described_class.triggered_for_branch(ref) } -- cgit v1.2.1 From fad99d934f73536929c4a12e25308473b52769b5 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 27 Apr 2019 23:38:05 -0700 Subject: Add Rubocop rule to ban include ActionView::Context --- .../cop/include_action_view_context_spec.rb | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 spec/rubocop/cop/include_action_view_context_spec.rb (limited to 'spec') diff --git a/spec/rubocop/cop/include_action_view_context_spec.rb b/spec/rubocop/cop/include_action_view_context_spec.rb new file mode 100644 index 00000000000..c888555b54f --- /dev/null +++ b/spec/rubocop/cop/include_action_view_context_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../rubocop/cop/include_action_view_context' + +describe RuboCop::Cop::IncludeActionViewContext do + include CopHelper + + subject(:cop) { described_class.new } + + context 'when `ActionView::Context` is included' do + let(:source) { 'include ActionView::Context' } + let(:correct_source) { 'include ::Gitlab::ActionViewOutput::Context' } + + it 'registers an offense' do + inspect_source(source) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + expect(cop.highlights).to eq(['ActionView::Context']) + end + end + + it 'autocorrects to the right version' do + autocorrected = autocorrect_source(source) + + expect(autocorrected).to eq(correct_source) + end + end + + context 'when `ActionView::Context` is not included' do + it 'registers no offense' do + inspect_source('include Context') + + aggregate_failures do + expect(cop.offenses.size).to eq(0) + end + end + end +end -- cgit v1.2.1 From d80fcccfb1f6df295c0b37ae36ebd1084c454a2b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 25 Apr 2019 12:43:28 -0700 Subject: Add full backtrace for RSpec output Attempt to debug https://gitlab.com/gitlab-org/gitlab-ce/issues/60953 --- spec/spec_helper.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8ca4c172707..fbc5fcea7b9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -53,6 +53,7 @@ RSpec.configure do |config| config.display_try_failure_messages = true config.infer_spec_type_from_file_location! + config.full_backtrace = true config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| location = metadata[:location] -- cgit v1.2.1 From 9bc3dfea4f365d5dc0b93a5fef0418dfad971361 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 29 Apr 2019 14:42:16 +0100 Subject: Stop serialising project when removing todos `Todos::Destroy::EntityLeaveService#project_ids` was returning ActiveRecord objects with IDs, not simply IDs. That means we were serialising more than we needed to in Sidekiq. We can simply rename this method to `#projects` as this class doesn't use any of the superclass methods that would use `#project_ids`. --- spec/services/todos/destroy/entity_leave_service_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb index 1447b9d4126..2a553e18807 100644 --- a/spec/services/todos/destroy/entity_leave_service_spec.rb +++ b/spec/services/todos/destroy/entity_leave_service_spec.rb @@ -75,6 +75,13 @@ describe Todos::Destroy::EntityLeaveService do project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) end + it 'enqueues the PrivateFeaturesWorker' do + expect(TodosDestroyer::PrivateFeaturesWorker) + .to receive(:perform_async).with(project.id, user.id) + + subject + end + context 'confidential issues' do context 'when a user is not an author of confidential issue' do it 'removes only confidential issues todos' do @@ -246,6 +253,13 @@ describe Todos::Destroy::EntityLeaveService do project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) end + it 'enqueues the PrivateFeaturesWorker' do + expect(TodosDestroyer::PrivateFeaturesWorker) + .to receive(:perform_async).with(project.id, user.id) + + subject + end + context 'when user is not member' do it 'removes only confidential issues todos' do expect { subject }.to change { Todo.count }.from(5).to(4) -- cgit v1.2.1 From 96b28d8387b97afae5a77eb6a4b240b67b1ced7f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 29 Apr 2019 14:29:54 +0700 Subject: Fix ref_text of merge request pipelines Source branch can be removed after the merge and we have to make sure to avoid rendering links if it's the case. --- spec/features/projects/pipelines/pipeline_spec.rb | 38 ++++++++++++++++--- spec/presenters/merge_request_presenter_spec.rb | 46 +++++++++++++++++++++++ 2 files changed, 79 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index cf334e1e4da..4ec44cb05b3 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -331,11 +331,9 @@ describe 'Pipeline', :js do merge_request.all_pipelines.last end - before do + it 'shows the pipeline information' do visit_pipeline - end - it 'shows the pipeline information' do within '.pipeline-info' do expect(page).to have_content("#{pipeline.statuses.count} jobs " \ "for !#{merge_request.iid} " \ @@ -347,6 +345,21 @@ describe 'Pipeline', :js do end end + context 'when source branch does not exist' do + before do + project.repository.rm_branch(user, merge_request.source_branch) + end + + it 'does not link to the source branch commit path' do + visit_pipeline + + within '.pipeline-info' do + expect(page).not_to have_link(merge_request.source_branch) + expect(page).to have_content(merge_request.source_branch) + end + end + end + context 'when source project is a forked project' do let(:source_project) { fork_project(project, user, repository: true) } @@ -386,11 +399,11 @@ describe 'Pipeline', :js do before do pipeline.update(user: user) - - visit_pipeline end it 'shows the pipeline information' do + visit_pipeline + within '.pipeline-info' do expect(page).to have_content("#{pipeline.statuses.count} jobs " \ "for !#{merge_request.iid} " \ @@ -405,6 +418,21 @@ describe 'Pipeline', :js do end end + context 'when target branch does not exist' do + before do + project.repository.rm_branch(user, merge_request.target_branch) + end + + it 'does not link to the target branch commit path' do + visit_pipeline + + within '.pipeline-info' do + expect(page).not_to have_link(merge_request.target_branch) + expect(page).to have_content(merge_request.target_branch) + end + end + end + context 'when source project is a forked project' do let(:source_project) { fork_project(project, user, repository: true) } diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index e5f08aeb1fa..451dc88880c 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -439,6 +439,52 @@ describe MergeRequestPresenter do end end + describe '#source_branch_link' do + subject { presenter.source_branch_link } + + let(:presenter) { described_class.new(resource, current_user: user) } + + context 'when source branch exists' do + it 'returns link' do + allow(resource).to receive(:source_branch_exists?) { true } + + is_expected + .to eq("#{presenter.source_branch}") + end + end + + context 'when source branch does not exist' do + it 'returns text' do + allow(resource).to receive(:source_branch_exists?) { false } + + is_expected.to eq("#{presenter.source_branch}") + end + end + end + + describe '#target_branch_link' do + subject { presenter.target_branch_link } + + let(:presenter) { described_class.new(resource, current_user: user) } + + context 'when target branch exists' do + it 'returns link' do + allow(resource).to receive(:target_branch_exists?) { true } + + is_expected + .to eq("#{presenter.target_branch}") + end + end + + context 'when target branch does not exist' do + it 'returns text' do + allow(resource).to receive(:target_branch_exists?) { false } + + is_expected.to eq("#{presenter.target_branch}") + end + end + end + describe '#source_branch_with_namespace_link' do subject do described_class.new(resource, current_user: user).source_branch_with_namespace_link -- cgit v1.2.1 From 25818bd7ae765422c934d0a32efb4ba353d11183 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 29 Apr 2019 17:07:42 -0700 Subject: Disable method replacement in avatar loading We've seen a significant performance penalty when using `BatchLoader#__replace_with!`. This defines methods on the batch loader that proxy to the 'real' object using send. The alternative is `method_missing`, which is slower. However, we've noticed that `method_missing` can be faster if: 1. The objects being loaded have a large interface. 2. We don't call too many methods on the loaded object. Avatar uploads meet both criteria above, so let's use the newly-released feature in https://github.com/exAspArk/batch-loader/pull/45. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/60903 --- spec/uploaders/object_storage_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 9ce9a353913..a62830c35f1 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -771,6 +771,14 @@ describe ObjectStorage do expect { avatars }.not_to exceed_query_limit(1) end + it 'does not attempt to replace methods' do + models.each do |model| + expect(model.avatar.upload).to receive(:method_missing).and_call_original + + model.avatar.upload.path + end + end + it 'fetches a unique upload for each model' do expect(avatars.map(&:url).uniq).to eq(avatars.map(&:url)) expect(avatars.map(&:upload).uniq).to eq(avatars.map(&:upload)) -- cgit v1.2.1 From 43be4d54f3940633ad76e746a9a999c4a9a65870 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 8 Apr 2019 13:26:05 +1200 Subject: Define state transitions for uninstalling apps Added :uninstalled state as wasn't sure if we should be destroying the cluster apps --- spec/factories/clusters/applications/helm.rb | 21 ++++-- .../cluster_application_status_shared_examples.rb | 76 +++++++++++++++++++--- 2 files changed, 83 insertions(+), 14 deletions(-) (limited to 'spec') diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index fe56ac5b71d..ac230950fce 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -6,6 +6,11 @@ FactoryBot.define do status(-2) end + trait :errored do + status(-1) + status_reason 'something went wrong' + end + trait :installable do status 0 end @@ -30,16 +35,24 @@ FactoryBot.define do status 5 end - trait :errored do - status(-1) + trait :update_errored do + status(6) status_reason 'something went wrong' end - trait :update_errored do - status(6) + trait :uninstalling do + status 7 + end + + trait :uninstall_errored do + status(8) status_reason 'something went wrong' end + trait :uninstalled do + status 9 + end + trait :timeouted do installing updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index b8c19cab0c4..c56b148cb8c 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -114,6 +114,17 @@ shared_examples 'cluster application status specs' do |application_name| expect(subject.status_reason).to eq(reason) end end + + context 'application is uninstalling' do + subject { create(application_name, :uninstalling) } + + it 'is uninstall_errored' do + subject.make_errored(reason) + + expect(subject).to be_uninstall_errored + expect(subject.status_reason).to eq(reason) + end + end end describe '#make_scheduled' do @@ -125,6 +136,16 @@ shared_examples 'cluster application status specs' do |application_name| expect(subject).to be_scheduled end + describe 'when installed' do + subject { create(application_name, :installed) } + + it 'is scheduled' do + subject.make_scheduled + + expect(subject).to be_scheduled + end + end + describe 'when was errored' do subject { create(application_name, :errored) } @@ -148,6 +169,38 @@ shared_examples 'cluster application status specs' do |application_name| expect(subject.status_reason).to be_nil end end + + describe 'when was uninstall_errored' do + subject { create(application_name, :uninstall_errored) } + + it 'clears #status_reason' do + expect(subject.status_reason).not_to be_nil + + subject.make_scheduled! + + expect(subject.status_reason).to be_nil + end + end + end + + describe '#make_uninstalling' do + subject { create(application_name, :scheduled) } + + it 'is uninstalling' do + subject.make_uninstalling! + + expect(subject).to be_uninstalling + end + end + + describe '#make_uninstalled' do + subject { create(application_name, :uninstalling) } + + it 'is uninstalled' do + subject.make_uninstalled! + + expect(subject).to be_uninstalled + end end end @@ -155,16 +208,19 @@ shared_examples 'cluster application status specs' do |application_name| using RSpec::Parameterized::TableSyntax where(:trait, :available) do - :not_installable | false - :installable | false - :scheduled | false - :installing | false - :installed | true - :updating | false - :updated | true - :errored | false - :update_errored | false - :timeouted | false + :not_installable | false + :installable | false + :scheduled | false + :installing | false + :installed | true + :updating | false + :updated | true + :errored | false + :update_errored | false + :uninstalling | false + :uninstall_errored | false + :uninstalled | false + :timeouted | false end with_them do -- cgit v1.2.1 From 33a765c17a246e4a2376056b1c301707c78806d0 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 10 Apr 2019 10:57:23 +1200 Subject: Teach Helm::Api about #uninstall --- spec/lib/gitlab/kubernetes/helm/api_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb index 8433d40b2ea..24ce397ec3d 100644 --- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb @@ -33,6 +33,28 @@ describe Gitlab::Kubernetes::Helm::Api do end end + describe '#uninstall' do + before do + allow(client).to receive(:create_pod).and_return(nil) + allow(client).to receive(:delete_pod).and_return(nil) + allow(namespace).to receive(:ensure_exists!).once + end + + it 'ensures the namespace exists before creating the POD' do + expect(namespace).to receive(:ensure_exists!).once.ordered + expect(client).to receive(:create_pod).once.ordered + + subject.uninstall(command) + end + + it 'removes an existing pod before installing' do + expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps').once.ordered + expect(client).to receive(:create_pod).once.ordered + + subject.uninstall(command) + end + end + describe '#install' do before do allow(client).to receive(:create_pod).and_return(nil) -- cgit v1.2.1 From 938e90f47288901790a96c50a8c0dfa2b7eab137 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 10 Apr 2019 14:50:14 +1200 Subject: Services to uninstall cluster application + to monitor progress of uninstallation pod --- .../check_uninstall_progress_service_spec.rb | 127 +++++++++++++++++++++ .../applications/uninstall_service_spec.rb | 77 +++++++++++++ .../wait_for_uninstall_app_worker_spec.rb | 32 ++++++ 3 files changed, 236 insertions(+) create mode 100644 spec/services/clusters/applications/check_uninstall_progress_service_spec.rb create mode 100644 spec/services/clusters/applications/uninstall_service_spec.rb create mode 100644 spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb (limited to 'spec') diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb new file mode 100644 index 00000000000..ccae7fd133f --- /dev/null +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::CheckUninstallProgressService do + RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze + + let(:application) { create(:clusters_applications_helm, :uninstalling) } + let(:service) { described_class.new(application) } + let(:phase) { Gitlab::Kubernetes::Pod::UNKNOWN } + let(:errors) { nil } + let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker } + + shared_examples 'a not yet terminated installation' do |a_phase| + let(:phase) { a_phase } + + before do + expect(service).to receive(:installation_phase).once.and_return(phase) + end + + context "when phase is #{a_phase}" do + context 'when not timeouted' do + it 'reschedule a new check' do + expect(worker_class).to receive(:perform_in).once + expect(service).not_to receive(:remove_installation_pod) + + expect do + service.execute + + application.reload + end.not_to change(application, :status) + + expect(application.status_reason).to be_nil + end + end + end + end + + before do + allow(service).to receive(:installation_errors).and_return(errors) + allow(service).to receive(:remove_installation_pod).and_return(nil) + end + + context 'when application is installing' do + RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase } + + context 'when installation POD succeeded' do + let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED } + before do + expect(service).to receive(:installation_phase).once.and_return(phase) + end + + it 'removes the installation POD' do + expect(service).to receive(:remove_installation_pod).once + + service.execute + end + + it 'make the application installed' do + expect(worker_class).not_to receive(:perform_in) + + service.execute + + expect(application).to be_uninstalled + expect(application.status_reason).to be_nil + end + end + + context 'when installation POD failed' do + let(:phase) { Gitlab::Kubernetes::Pod::FAILED } + let(:errors) { 'test installation failed' } + + before do + expect(service).to receive(:installation_phase).once.and_return(phase) + end + + it 'make the application errored' do + service.execute + + expect(application).to be_uninstall_errored + expect(application.status_reason).to eq('Operation failed. Check pod logs for uninstall-helm for more details.') + end + end + + context 'when timed out' do + let(:application) { create(:clusters_applications_helm, :timeouted, :uninstalling) } + + before do + expect(service).to receive(:installation_phase).once.and_return(phase) + end + + it 'make the application errored' do + expect(worker_class).not_to receive(:perform_in) + + service.execute + + expect(application).to be_uninstall_errored + expect(application.status_reason).to eq('Operation timed out. Check pod logs for uninstall-helm for more details.') + end + end + + context 'when installation raises a Kubeclient::HttpError' do + let(:cluster) { create(:cluster, :provided_by_user, :project) } + let(:logger) { service.send(:logger) } + let(:error) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) } + + before do + application.update!(cluster: cluster) + + expect(service).to receive(:installation_phase).and_raise(error) + end + + include_examples 'logs kubernetes errors' do + let(:error_name) { 'Kubeclient::HttpError' } + let(:error_message) { 'Unauthorized' } + let(:error_code) { 401 } + end + + it 'shows the response code from the error' do + service.execute + + expect(application).to be_uninstall_errored + expect(application.status_reason).to eq('Kubernetes error: 401') + end + end + end +end diff --git a/spec/services/clusters/applications/uninstall_service_spec.rb b/spec/services/clusters/applications/uninstall_service_spec.rb new file mode 100644 index 00000000000..d1d0e923e18 --- /dev/null +++ b/spec/services/clusters/applications/uninstall_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::UninstallService, '#execute' do + let(:application) { create(:clusters_applications_helm, :scheduled) } + let(:service) { described_class.new(application) } + let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } + let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker } + + before do + allow(service).to receive(:helm_api).and_return(helm_client) + end + + context 'when there are no errors' do + before do + expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::ResetCommand)) + allow(worker_class).to receive(:perform_in).and_return(nil) + end + + it 'make the application to be uninstalling' do + expect(application.cluster).not_to be_nil + service.execute + + expect(application).to be_uninstalling + end + + it 'schedule async installation status check' do + expect(worker_class).to receive(:perform_in).once + + service.execute + end + end + + context 'when k8s cluster communication fails' do + let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) } + + before do + expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::ResetCommand)).and_raise(error) + end + + include_examples 'logs kubernetes errors' do + let(:error_name) { 'Kubeclient::HttpError' } + let(:error_message) { 'system failure' } + let(:error_code) { 500 } + end + + it 'make the application errored' do + service.execute + + expect(application).to be_uninstall_errored + expect(application.status_reason).to match('Kubernetes error: 500') + end + end + + context 'a non kubernetes error happens' do + let(:application) { create(:clusters_applications_helm, :scheduled) } + let(:error) { StandardError.new('something bad happened') } + + before do + expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::ResetCommand)).and_raise(error) + end + + include_examples 'logs kubernetes errors' do + let(:error_name) { 'StandardError' } + let(:error_message) { 'something bad happened' } + let(:error_code) { nil } + end + + it 'make the application errored' do + service.execute + + expect(application).to be_uninstall_errored + expect(application.status_reason).to eq('Failed to uninstall.') + end + end +end diff --git a/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb new file mode 100644 index 00000000000..aaf5c9defc4 --- /dev/null +++ b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::WaitForUninstallAppWorker, '#perform' do + let(:app) { create(:clusters_applications_helm) } + let(:app_name) { app.name } + let(:app_id) { app.id } + + subject { described_class.new.perform(app_name, app_id) } + + context 'app exists' do + let(:service) { instance_double(Clusters::Applications::CheckUninstallProgressService) } + + it 'calls the check service' do + expect(Clusters::Applications::CheckUninstallProgressService).to receive(:new).with(app).and_return(service) + expect(service).to receive(:execute).once + + subject + end + end + + context 'app does not exist' do + let(:app_id) { 0 } + + it 'does not call the check service' do + expect(Clusters::Applications::CheckUninstallProgressService).not_to receive(:new) + + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end -- cgit v1.2.1 From 3c8df0c944f0b23f9ee8b6b08a0a355b00456dd9 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 12 Apr 2019 17:28:06 +1200 Subject: Destroy app on successful uninstallation Rescue and put into :uninstall_errored if something goes wrong while destroying, which can happen. I think it is safe to expose the full error message from the destroy error. Remove the :uninstalled state as no longer used. --- spec/factories/clusters/applications/helm.rb | 4 ---- .../check_uninstall_progress_service_spec.rb | 23 +++++++++++++++++++--- .../cluster_application_status_shared_examples.rb | 11 ----------- 3 files changed, 20 insertions(+), 18 deletions(-) (limited to 'spec') diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index ac230950fce..22a0888947e 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -49,10 +49,6 @@ FactoryBot.define do status_reason 'something went wrong' end - trait :uninstalled do - status 9 - end - trait :timeouted do installing updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb index ccae7fd133f..084f29d9d2d 100644 --- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -56,13 +56,30 @@ describe Clusters::Applications::CheckUninstallProgressService do service.execute end - it 'make the application installed' do + it 'destroys the application' do expect(worker_class).not_to receive(:perform_in) service.execute + expect(application).to be_destroyed + end + + context 'an error occurs while destroying' do + before do + expect(application).to receive(:destroy!).once.and_raise("destroy failed") + end + + it 'still removes the installation POD' do + expect(service).to receive(:remove_installation_pod).once - expect(application).to be_uninstalled - expect(application.status_reason).to be_nil + service.execute + end + + it 'makes the application uninstall_errored' do + service.execute + + expect(application).to be_uninstall_errored + expect(application.status_reason).to eq('Application uninstalled but failed to destroy: destroy failed') + end end end diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index c56b148cb8c..233b9db8f7b 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -192,16 +192,6 @@ shared_examples 'cluster application status specs' do |application_name| expect(subject).to be_uninstalling end end - - describe '#make_uninstalled' do - subject { create(application_name, :uninstalling) } - - it 'is uninstalled' do - subject.make_uninstalled! - - expect(subject).to be_uninstalled - end - end end describe '#available?' do @@ -219,7 +209,6 @@ shared_examples 'cluster application status specs' do |application_name| :update_errored | false :uninstalling | false :uninstall_errored | false - :uninstalled | false :timeouted | false end -- cgit v1.2.1 From 44eec56834b7f524a2bf99d0f5e1571b52576d72 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 12 Apr 2019 17:42:48 +1200 Subject: Expose can_uninstall in cluster_status.json Only prometheus can be uninstalled atm, the rest will be dealt with later. Presumption is that new application types will have uninstallation implmemented at the same time. --- spec/fixtures/api/schemas/cluster_status.json | 3 ++- spec/models/clusters/applications/cert_manager_spec.rb | 6 ++++++ spec/models/clusters/applications/helm_spec.rb | 8 ++++++++ spec/models/clusters/applications/ingress_spec.rb | 6 ++++++ spec/models/clusters/applications/jupyter_spec.rb | 9 +++++++++ spec/models/clusters/applications/knative_spec.rb | 6 ++++++ spec/models/clusters/applications/prometheus_spec.rb | 8 ++++++++ spec/models/clusters/applications/runner_spec.rb | 8 ++++++++ spec/serializers/cluster_application_entity_spec.rb | 4 ++++ .../models/cluster_application_core_shared_examples.rb | 8 ++++++++ 10 files changed, 65 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json index 9da07a0b253..695175689b9 100644 --- a/spec/fixtures/api/schemas/cluster_status.json +++ b/spec/fixtures/api/schemas/cluster_status.json @@ -36,7 +36,8 @@ "external_hostname": { "type": ["string", "null"] }, "hostname": { "type": ["string", "null"] }, "email": { "type": ["string", "null"] }, - "update_available": { "type": ["boolean", "null"] } + "update_available": { "type": ["boolean", "null"] }, + "can_uninstall": { "type": "boolean" } }, "required" : [ "name", "status" ] } diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index 5cd80edb3a1..8d853a04e33 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -10,6 +10,12 @@ describe Clusters::Applications::CertManager do include_examples 'cluster application version specs', :clusters_applications_cert_managers include_examples 'cluster application initial status specs' + describe '#can_uninstall?' do + subject { cert_manager.can_uninstall? } + + it { is_expected.to be_falsey } + end + describe '#install_command' do let(:cert_email) { 'admin@example.com' } diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index f177d493a2e..6ea6c110d62 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -18,6 +18,14 @@ describe Clusters::Applications::Helm do it { is_expected.to contain_exactly(installed_cluster, updated_cluster) } end + describe '#can_uninstall?' do + let(:helm) { create(:clusters_applications_helm) } + + subject { helm.can_uninstall? } + + it { is_expected.to be_falsey } + end + describe '#issue_client_cert' do let(:application) { create(:clusters_applications_helm) } subject { application.issue_client_cert } diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 113d29b5551..292ddabd2d8 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -18,6 +18,12 @@ describe Clusters::Applications::Ingress do allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async) end + describe '#can_uninstall?' do + subject { ingress.can_uninstall? } + + it { is_expected.to be_falsey } + end + describe '#make_installed!' do before do application.make_installed! diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 1a7363b64f9..fc9ebed863e 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -10,6 +10,15 @@ describe Clusters::Applications::Jupyter do it { is_expected.to belong_to(:oauth_application) } + describe '#can_uninstall?' do + let(:ingress) { create(:clusters_applications_ingress, :installed, external_hostname: 'localhost.localdomain') } + let(:jupyter) { create(:clusters_applications_jupyter, cluster: ingress.cluster) } + + subject { jupyter.can_uninstall? } + + it { is_expected.to be_falsey } + end + describe '#set_initial_status' do before do jupyter.set_initial_status diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 405b5ad691c..d5974f47190 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -39,6 +39,12 @@ describe Clusters::Applications::Knative do end end + describe '#can_uninstall?' do + subject { knative.can_uninstall? } + + it { is_expected.to be_falsey } + end + describe '#schedule_status_update with external_ip' do let(:application) { create(:clusters_applications_knative, :installed) } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index e8ba9737c23..f390afe9b1f 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -29,6 +29,14 @@ describe Clusters::Applications::Prometheus do end end + describe '#can_uninstall?' do + let(:prometheus) { create(:clusters_applications_prometheus) } + + subject { prometheus.can_uninstall? } + + it { is_expected.to be_truthy } + end + describe '#prometheus_client' do context 'cluster is nil' do it 'returns nil' do diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index b66acf13135..bdc0cb8ed86 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -13,6 +13,14 @@ describe Clusters::Applications::Runner do it { is_expected.to belong_to(:runner) } + describe '#can_uninstall?' do + let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) } + + subject { gitlab_runner.can_uninstall? } + + it { is_expected.to be_falsey } + end + describe '#install_command' do let(:kubeclient) { double('kubernetes client') } let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) } diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb index 7e151c3744e..f38a18fcf59 100644 --- a/spec/serializers/cluster_application_entity_spec.rb +++ b/spec/serializers/cluster_application_entity_spec.rb @@ -21,6 +21,10 @@ describe ClusterApplicationEntity do expect(subject[:status_reason]).to be_nil end + it 'has can_uninstall' do + expect(subject[:can_uninstall]).to be_falsey + end + context 'non-helm application' do let(:application) { build(:clusters_applications_runner, version: '0.0.0') } diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb index 1f76b981292..d6490a808ce 100644 --- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb @@ -2,6 +2,14 @@ shared_examples 'cluster application core specs' do |application_name| it { is_expected.to belong_to(:cluster) } it { is_expected.to validate_presence_of(:cluster) } + describe '#can_uninstall?' do + it 'calls allowed_to_uninstall?' do + expect(subject).to receive(:allowed_to_uninstall?).and_return(true) + + expect(subject.can_uninstall?).to be_truthy + end + end + describe '#name' do it 'is .application_name' do expect(subject.name).to eq(described_class.application_name) -- cgit v1.2.1 From 43c284b711ddd4db55908de0590f946de5227db6 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 12 Apr 2019 23:15:50 +1200 Subject: Teach Prometheus about #uninstall_command Add specs --- .../clusters/applications/prometheus_spec.rb | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'spec') diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index f390afe9b1f..4022e01195d 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -142,6 +142,34 @@ describe Clusters::Applications::Prometheus do end end + describe '#uninstall_command' do + let(:prometheus) { create(:clusters_applications_prometheus) } + + subject { prometheus.uninstall_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) } + + it 'has the application name' do + expect(subject.name).to eq('prometheus') + end + + it 'has files' do + expect(subject.files).to eq(prometheus.files) + end + + it 'is rbac' do + expect(subject).to be_rbac + end + + context 'on a non rbac enabled cluster' do + before do + prometheus.cluster.platform_kubernetes.abac! + end + + it { is_expected.not_to be_rbac } + end + end + describe '#upgrade_command' do let(:prometheus) { build(:clusters_applications_prometheus) } let(:values) { prometheus.values } -- cgit v1.2.1 From abb530a61958518f6e0c739406f34c558c504206 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Sat, 13 Apr 2019 00:19:28 +1200 Subject: DELETE clusters/:id/:application endpoint Add endpoint to delete/uninstall a cluster application --- .../clusters/applications_controller_spec.rb | 60 +++++++++++++++++++++ .../clusters/applications_controller_spec.rb | 62 +++++++++++++++++++++ .../clusters/applications/destroy_service_spec.rb | 63 ++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 spec/services/clusters/applications/destroy_service_spec.rb (limited to 'spec') diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb index 16a63536ea6..acb9405d1a6 100644 --- a/spec/controllers/groups/clusters/applications_controller_spec.rb +++ b/spec/controllers/groups/clusters/applications_controller_spec.rb @@ -144,4 +144,64 @@ describe Groups::Clusters::ApplicationsController do it_behaves_like 'a secure endpoint' end end + + describe 'DELETE destroy' do + subject do + delete :destroy, params: params.merge(group_id: group) + end + + let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) } + let(:application_name) { application.name } + let(:params) { { application: application_name, id: cluster.id } } + let(:worker_class) { Clusters::Applications::UninstallWorker } + + describe 'functionality' do + let(:user) { create(:user) } + + before do + group.add_maintainer(user) + sign_in(user) + end + + context "when cluster and app exists" do + xit "schedules an application update" do + expect(worker_class).to receive(:perform_async).with(application.name, application.id).once + + is_expected.to have_http_status(:no_content) + + expect(cluster.application_cert_manager).to be_scheduled + end + end + + context 'when cluster do not exists' do + before do + cluster.destroy! + end + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is unknown' do + let(:application_name) { 'unkwnown-app' } + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is already scheduled' do + before do + application.make_scheduled! + end + + xit { is_expected.to have_http_status(:bad_request) } + end + end + + describe 'security' do + before do + allow(worker_class).to receive(:perform_async) + end + + it_behaves_like 'a secure endpoint' + end + end end diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb index cd1a01f8acc..70b34f071c8 100644 --- a/spec/controllers/projects/clusters/applications_controller_spec.rb +++ b/spec/controllers/projects/clusters/applications_controller_spec.rb @@ -145,4 +145,66 @@ describe Projects::Clusters::ApplicationsController do it_behaves_like 'a secure endpoint' end end + + describe 'DELETE destroy' do + subject do + delete :destroy, params: params.merge(namespace_id: project.namespace, project_id: project) + end + + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + let!(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + let(:application_name) { application.name } + let(:params) { { application: application_name, id: cluster.id } } + let(:worker_class) { Clusters::Applications::UninstallWorker } + + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_maintainer(user) + sign_in(user) + end + + context "when cluster and app exists" do + it "schedules an application update" do + expect(worker_class).to receive(:perform_async).with(application.name, application.id).once + + is_expected.to have_http_status(:no_content) + + expect(cluster.application_prometheus).to be_scheduled + end + end + + context 'when cluster do not exists' do + before do + cluster.destroy! + end + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is unknown' do + let(:application_name) { 'unkwnown-app' } + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is already scheduled' do + before do + application.make_scheduled! + end + + it { is_expected.to have_http_status(:bad_request) } + end + end + + describe 'security' do + before do + allow(worker_class).to receive(:perform_async) + end + + it_behaves_like 'a secure endpoint' + end + end end diff --git a/spec/services/clusters/applications/destroy_service_spec.rb b/spec/services/clusters/applications/destroy_service_spec.rb new file mode 100644 index 00000000000..8d9dc6a0f11 --- /dev/null +++ b/spec/services/clusters/applications/destroy_service_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Applications::DestroyService, '#execute' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:user) { create(:user) } + let(:params) { { application: 'prometheus' } } + let(:service) { described_class.new(cluster, user, params) } + let(:test_request) { double } + let(:worker_class) { Clusters::Applications::UninstallWorker } + + subject { service.execute(test_request) } + + before do + allow(worker_class).to receive(:perform_async) + end + + context 'application is not installed' do + it 'raises Clusters::Applications::BaseService::InvalidApplicationError' do + expect(worker_class).not_to receive(:perform_async) + + expect { subject } + .to raise_exception { Clusters::Applications::BaseService::InvalidApplicationError } + .and not_change { Clusters::Applications::Prometheus.count } + .and not_change { Clusters::Applications::Prometheus.with_status(:scheduled).count } + end + end + + context 'application is installed' do + context 'application is schedulable' do + let!(:application) do + create(:clusters_applications_prometheus, :installed, cluster: cluster) + end + + it 'makes application scheduled!' do + subject + + expect(application.reload).to be_scheduled + end + + it 'schedules UninstallWorker' do + expect(worker_class).to receive(:perform_async).with(application.name, application.id) + + subject + end + end + + context 'application is not schedulable' do + let!(:application) do + create(:clusters_applications_prometheus, :updating, cluster: cluster) + end + + it 'raises StateMachines::InvalidTransition' do + expect(worker_class).not_to receive(:perform_async) + + expect { subject } + .to raise_exception { StateMachines::InvalidTransition } + .and not_change { Clusters::Applications::Prometheus.with_status(:scheduled).count } + end + end + end +end -- cgit v1.2.1 From eae0fc2bcd6f7e2e183a922321ace3380c329adc Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Mon, 15 Apr 2019 10:10:59 +1000 Subject: Remove xit test for uninstall group cluster app --- .../clusters/applications_controller_spec.rb | 60 ---------------------- 1 file changed, 60 deletions(-) (limited to 'spec') diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb index acb9405d1a6..16a63536ea6 100644 --- a/spec/controllers/groups/clusters/applications_controller_spec.rb +++ b/spec/controllers/groups/clusters/applications_controller_spec.rb @@ -144,64 +144,4 @@ describe Groups::Clusters::ApplicationsController do it_behaves_like 'a secure endpoint' end end - - describe 'DELETE destroy' do - subject do - delete :destroy, params: params.merge(group_id: group) - end - - let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) } - let(:application_name) { application.name } - let(:params) { { application: application_name, id: cluster.id } } - let(:worker_class) { Clusters::Applications::UninstallWorker } - - describe 'functionality' do - let(:user) { create(:user) } - - before do - group.add_maintainer(user) - sign_in(user) - end - - context "when cluster and app exists" do - xit "schedules an application update" do - expect(worker_class).to receive(:perform_async).with(application.name, application.id).once - - is_expected.to have_http_status(:no_content) - - expect(cluster.application_cert_manager).to be_scheduled - end - end - - context 'when cluster do not exists' do - before do - cluster.destroy! - end - - it { is_expected.to have_http_status(:not_found) } - end - - context 'when application is unknown' do - let(:application_name) { 'unkwnown-app' } - - it { is_expected.to have_http_status(:not_found) } - end - - context 'when application is already scheduled' do - before do - application.make_scheduled! - end - - xit { is_expected.to have_http_status(:bad_request) } - end - end - - describe 'security' do - before do - allow(worker_class).to receive(:perform_async) - end - - it_behaves_like 'a secure endpoint' - end - end end -- cgit v1.2.1 From 024ddcab17517befafc0c5163d66cdcaae1b69e6 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 15 Apr 2019 16:02:52 +1200 Subject: Deactivate any prometheus_service upon destroy Basically does the reverse of after_transition to :installed. --- spec/models/clusters/applications/prometheus_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'spec') diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 4022e01195d..76d2c4a9d1c 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -11,6 +11,23 @@ describe Clusters::Applications::Prometheus do include_examples 'cluster application helm specs', :clusters_applications_prometheus include_examples 'cluster application initial status specs' + describe 'after_destroy' do + let(:project) { create(:project) } + let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } + let!(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + let!(:prometheus_service) { project.create_prometheus_service(active: true) } + + it 'deactivates prometheus_service after destroy' do + expect do + application.destroy + + prometheus_service.reload + end.to change(prometheus_service, :active) + + expect(prometheus_service).not_to be_active + end + end + describe 'transition to installed' do let(:project) { create(:project) } let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } -- cgit v1.2.1 From 3cac5b05b2fd8377883d42be5ee5e0fb2370db04 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Mon, 15 Apr 2019 16:42:43 +1000 Subject: Fix uninstall specs: helm not uninstallable --- .../applications/check_uninstall_progress_service_spec.rb | 8 ++++---- spec/services/clusters/applications/uninstall_service_spec.rb | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'spec') diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb index 084f29d9d2d..fedca51a1b4 100644 --- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Clusters::Applications::CheckUninstallProgressService do RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze - let(:application) { create(:clusters_applications_helm, :uninstalling) } + let(:application) { create(:clusters_applications_prometheus, :uninstalling) } let(:service) { described_class.new(application) } let(:phase) { Gitlab::Kubernetes::Pod::UNKNOWN } let(:errors) { nil } @@ -95,12 +95,12 @@ describe Clusters::Applications::CheckUninstallProgressService do service.execute expect(application).to be_uninstall_errored - expect(application.status_reason).to eq('Operation failed. Check pod logs for uninstall-helm for more details.') + expect(application.status_reason).to eq('Operation failed. Check pod logs for uninstall-prometheus for more details.') end end context 'when timed out' do - let(:application) { create(:clusters_applications_helm, :timeouted, :uninstalling) } + let(:application) { create(:clusters_applications_prometheus, :timeouted, :uninstalling) } before do expect(service).to receive(:installation_phase).once.and_return(phase) @@ -112,7 +112,7 @@ describe Clusters::Applications::CheckUninstallProgressService do service.execute expect(application).to be_uninstall_errored - expect(application.status_reason).to eq('Operation timed out. Check pod logs for uninstall-helm for more details.') + expect(application.status_reason).to eq('Operation timed out. Check pod logs for uninstall-prometheus for more details.') end end diff --git a/spec/services/clusters/applications/uninstall_service_spec.rb b/spec/services/clusters/applications/uninstall_service_spec.rb index d1d0e923e18..16497d752b2 100644 --- a/spec/services/clusters/applications/uninstall_service_spec.rb +++ b/spec/services/clusters/applications/uninstall_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Clusters::Applications::UninstallService, '#execute' do - let(:application) { create(:clusters_applications_helm, :scheduled) } + let(:application) { create(:clusters_applications_prometheus, :scheduled) } let(:service) { described_class.new(application) } let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker } @@ -14,7 +14,7 @@ describe Clusters::Applications::UninstallService, '#execute' do context 'when there are no errors' do before do - expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::ResetCommand)) + expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand)) allow(worker_class).to receive(:perform_in).and_return(nil) end @@ -36,7 +36,7 @@ describe Clusters::Applications::UninstallService, '#execute' do let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) } before do - expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::ResetCommand)).and_raise(error) + expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand)).and_raise(error) end include_examples 'logs kubernetes errors' do @@ -54,11 +54,11 @@ describe Clusters::Applications::UninstallService, '#execute' do end context 'a non kubernetes error happens' do - let(:application) { create(:clusters_applications_helm, :scheduled) } + let(:application) { create(:clusters_applications_prometheus, :scheduled) } let(:error) { StandardError.new('something bad happened') } before do - expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::ResetCommand)).and_raise(error) + expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand)).and_raise(error) end include_examples 'logs kubernetes errors' do -- cgit v1.2.1 From 0a4817dd77eb371f171b634e4df180eafa115721 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Fri, 26 Apr 2019 13:28:49 +1000 Subject: In Prometheus use update! instead of update In order to not miss any errors since we are not checking the return value of update --- spec/models/clusters/applications/prometheus_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 76d2c4a9d1c..8649d3e2710 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -40,7 +40,7 @@ describe Clusters::Applications::Prometheus do end it 'ensures Prometheus service is activated' do - expect(prometheus_service).to receive(:update).with(active: true) + expect(prometheus_service).to receive(:update!).with(active: true) subject.make_installed end -- cgit v1.2.1 From 416f3971e66762246f0af3cda97c4b55e101f61b Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Fri, 26 Apr 2019 13:30:08 +1000 Subject: Minor refactoring in check_uninstall_progress_service_spec --- .../applications/check_uninstall_progress_service_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb index fedca51a1b4..8ff92eb2d09 100644 --- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -11,6 +11,11 @@ describe Clusters::Applications::CheckUninstallProgressService do let(:errors) { nil } let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker } + before do + allow(service).to receive(:installation_errors).and_return(errors) + allow(service).to receive(:remove_installation_pod) + end + shared_examples 'a not yet terminated installation' do |a_phase| let(:phase) { a_phase } @@ -36,11 +41,6 @@ describe Clusters::Applications::CheckUninstallProgressService do end end - before do - allow(service).to receive(:installation_errors).and_return(errors) - allow(service).to receive(:remove_installation_pod).and_return(nil) - end - context 'when application is installing' do RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase } -- cgit v1.2.1 From dbb284de5a18361eae937926052abd1ea3c261b5 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Fri, 26 Apr 2019 14:25:51 +1000 Subject: Minor refactor of prometheus_spec --- spec/models/clusters/applications/prometheus_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 8649d3e2710..26267c64112 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -19,12 +19,10 @@ describe Clusters::Applications::Prometheus do it 'deactivates prometheus_service after destroy' do expect do - application.destroy + application.destroy! prometheus_service.reload - end.to change(prometheus_service, :active) - - expect(prometheus_service).not_to be_active + end.to change(prometheus_service, :active).from(true).to(false) end end -- cgit v1.2.1 From f84fae1386eec1b22c1405a8b87beb30d8f10519 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Mon, 29 Apr 2019 15:57:12 +1000 Subject: Rename #timeouted -> #timed_out --- spec/factories/clusters/applications/helm.rb | 2 +- .../applications/check_installation_progress_service_spec.rb | 6 +++--- .../clusters/applications/check_uninstall_progress_service_spec.rb | 4 ++-- .../models/cluster_application_status_shared_examples.rb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 22a0888947e..d78f01828d7 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -49,7 +49,7 @@ FactoryBot.define do status_reason 'something went wrong' end - trait :timeouted do + trait :timed_out do installing updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } end diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb index 8ad90aaf720..a54bd85a11a 100644 --- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb @@ -18,7 +18,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do end context "when phase is #{a_phase}" do - context 'when not timeouted' do + context 'when not timed_out' do it 'reschedule a new check' do expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once expect(service).not_to receive(:remove_installation_pod) @@ -113,7 +113,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do end context 'when timed out' do - let(:application) { create(:clusters_applications_helm, :timeouted, :updating) } + let(:application) { create(:clusters_applications_helm, :timed_out, :updating) } before do expect(service).to receive(:installation_phase).once.and_return(phase) @@ -174,7 +174,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do end context 'when timed out' do - let(:application) { create(:clusters_applications_helm, :timeouted) } + let(:application) { create(:clusters_applications_helm, :timed_out) } before do expect(service).to receive(:installation_phase).once.and_return(phase) diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb index 8ff92eb2d09..d0730399268 100644 --- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -24,7 +24,7 @@ describe Clusters::Applications::CheckUninstallProgressService do end context "when phase is #{a_phase}" do - context 'when not timeouted' do + context 'when not timed_out' do it 'reschedule a new check' do expect(worker_class).to receive(:perform_in).once expect(service).not_to receive(:remove_installation_pod) @@ -100,7 +100,7 @@ describe Clusters::Applications::CheckUninstallProgressService do end context 'when timed out' do - let(:application) { create(:clusters_applications_prometheus, :timeouted, :uninstalling) } + let(:application) { create(:clusters_applications_prometheus, :timed_out, :uninstalling) } before do expect(service).to receive(:installation_phase).once.and_return(phase) diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index 233b9db8f7b..4525c03837f 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -209,7 +209,7 @@ shared_examples 'cluster application status specs' do |application_name| :update_errored | false :uninstalling | false :uninstall_errored | false - :timeouted | false + :timed_out | false end with_them do -- cgit v1.2.1 From 3c52ff8c78b4e9174214d5efe5a8664d8cc608ca Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 29 Apr 2019 22:54:17 -0700 Subject: Add a newline in spec for readability --- .../clusters/applications/check_uninstall_progress_service_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb index d0730399268..9ab83d913f5 100644 --- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb @@ -60,6 +60,7 @@ describe Clusters::Applications::CheckUninstallProgressService do expect(worker_class).not_to receive(:perform_in) service.execute + expect(application).to be_destroyed end -- cgit v1.2.1 From fc22626f453f64409ad5b2967cdec541dbcc041d Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Tue, 23 Apr 2019 17:30:18 +0800 Subject: Remove deprecated uses of attribute_changed? Prepares us for upgrade to Rails 5.2 --- spec/models/commit_status_spec.rb | 4 ++-- spec/models/namespace_spec.rb | 30 ++++++++++++++++++------------ spec/services/system_hooks_service_spec.rb | 12 ++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) (limited to 'spec') diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index ca2f9e36c98..017cca0541e 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -449,7 +449,7 @@ describe CommitStatus do end it "lock" do - is_expected.to be true + is_expected.to be_truthy end it "raise exception when trying to update" do @@ -463,7 +463,7 @@ describe CommitStatus do end it "do not lock" do - is_expected.to be false + is_expected.to be_falsey end it "save correctly" do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 95a4b0f6d71..dd5edca5059 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -743,22 +743,25 @@ describe Namespace do end end - describe '#full_path_was' do + describe '#full_path_before_last_save' do context 'when the group has no parent' do - it 'returns the path was' do - group = create(:group, parent: nil) - expect(group.full_path_was).to eq(group.path_was) + it 'returns the path before last save' do + group = create(:group) + + group.update(parent: nil) + + expect(group.full_path_before_last_save).to eq(group.path_before_last_save) end end context 'when a parent is assigned to a group with no previous parent' do - it 'returns the path was' do + it 'returns the path before last save' do group = create(:group, parent: nil) - parent = create(:group) - group.parent = parent - expect(group.full_path_was).to eq("#{group.path_was}") + group.update(parent: parent) + + expect(group.full_path_before_last_save).to eq("#{group.path_before_last_save}") end end @@ -766,9 +769,10 @@ describe Namespace do it 'returns the parent full path' do parent = create(:group) group = create(:group, parent: parent) - group.parent = nil - expect(group.full_path_was).to eq("#{parent.full_path}/#{group.path}") + group.update(parent: nil) + + expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}") end end @@ -777,8 +781,10 @@ describe Namespace do parent = create(:group) group = create(:group, parent: parent) new_parent = create(:group) - group.parent = new_parent - expect(group.full_path_was).to eq("#{parent.full_path}/#{group.path}") + + group.update(parent: new_parent) + + expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}") end end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 5dc7394b84f..f5c6e972953 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -86,20 +86,20 @@ describe SystemHooksService do context 'group_rename' do it 'contains old and new path' do - allow(group).to receive(:path_was).and_return('old-path') + allow(group).to receive(:path_before_last_save).and_return('old-path') data = event_data(group, :rename) expect(data).to include(:event_name, :name, :created_at, :updated_at, :full_path, :path, :group_id, :old_path, :old_full_path) expect(data[:path]).to eq(group.path) expect(data[:full_path]).to eq(group.path) - expect(data[:old_path]).to eq(group.path_was) - expect(data[:old_full_path]).to eq(group.path_was) + expect(data[:old_path]).to eq(group.path_before_last_save) + expect(data[:old_full_path]).to eq(group.path_before_last_save) end it 'contains old and new full_path for subgroup' do subgroup = create(:group, parent: group) - allow(subgroup).to receive(:path_was).and_return('old-path') + allow(subgroup).to receive(:path_before_last_save).and_return('old-path') data = event_data(subgroup, :rename) @@ -110,13 +110,13 @@ describe SystemHooksService do context 'user_rename' do it 'contains old and new username' do - allow(user).to receive(:username_was).and_return('old-username') + allow(user).to receive(:username_before_last_save).and_return('old-username') data = event_data(user, :rename) expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :old_username) expect(data[:username]).to eq(user.username) - expect(data[:old_username]).to eq(user.username_was) + expect(data[:old_username]).to eq(user.username_before_last_save) end end -- cgit v1.2.1 From 6f0d8ebc45ef1d0e5a03a0d2965169483bf7224d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Tue, 30 Apr 2019 08:21:21 +0000 Subject: Refactored LfsImportService and ImportService In order to make `LfsImportService` more reusable, we need to extract the logic inside `ImportService` and encapsulate it into the service. --- spec/services/projects/import_service_spec.rb | 45 +++--- .../lfs_download_link_list_service_spec.rb | 3 +- .../lfs_pointers/lfs_download_service_spec.rb | 1 - .../lfs_pointers/lfs_import_service_spec.rb | 153 +++++---------------- .../projects/lfs_pointers/lfs_link_service_spec.rb | 1 - .../lfs_object_download_list_service_spec.rb | 148 ++++++++++++++++++++ 6 files changed, 211 insertions(+), 140 deletions(-) create mode 100644 spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb (limited to 'spec') diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index 7f233a52f50..d9f9ede8ecd 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -5,15 +5,11 @@ require 'spec_helper' describe Projects::ImportService do let!(:project) { create(:project) } let(:user) { project.creator } - let(:import_url) { 'http://www.gitlab.com/demo/repo.git' } - let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } } subject { described_class.new(project, user) } before do allow(project).to receive(:lfs_enabled?).and_return(true) - allow_any_instance_of(Projects::LfsPointers::LfsDownloadService).to receive(:execute) - allow_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links) end describe '#async?' do @@ -77,7 +73,6 @@ describe Projects::ImportService do context 'when repository creation succeeds' do it 'does not download lfs files' do expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute) - expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute) subject.execute end @@ -114,7 +109,6 @@ describe Projects::ImportService do context 'when repository import scheduled' do it 'does not download lfs objects' do expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute) - expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute) subject.execute end @@ -130,7 +124,7 @@ describe Projects::ImportService do it 'succeeds if repository import is successful' do expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true) expect_any_instance_of(Gitlab::BitbucketImport::Importer).to receive(:execute).and_return(true) - expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return({}) + expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(status: :success) result = subject.execute @@ -146,6 +140,19 @@ describe Projects::ImportService do expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]" end + context 'when lfs import fails' do + it 'logs the error' do + error_message = 'error message' + + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true) + expect_any_instance_of(Gitlab::BitbucketImport::Importer).to receive(:execute).and_return(true) + expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(status: :error, message: error_message) + expect(Gitlab::AppLogger).to receive(:error).with("The Lfs import process failed. #{error_message}") + + subject.execute + end + end + context 'when repository import scheduled' do before do allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true) @@ -155,10 +162,7 @@ describe Projects::ImportService do it 'downloads lfs objects if lfs_enabled is enabled for project' do allow(project).to receive(:lfs_enabled?).and_return(true) - service = double - expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links) - expect(Projects::LfsPointers::LfsDownloadService).to receive(:new).and_return(service).twice - expect(service).to receive(:execute).twice + expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute) subject.execute end @@ -166,7 +170,6 @@ describe Projects::ImportService do it 'does not download lfs objects if lfs_enabled is not enabled for project' do allow(project).to receive(:lfs_enabled?).and_return(false) expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute) - expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute) subject.execute end @@ -208,7 +211,6 @@ describe Projects::ImportService do allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(true) expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute) - expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute) subject.execute end @@ -216,13 +218,22 @@ describe Projects::ImportService do it 'does not have a custom repository importer downloads lfs objects' do allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(false) - service = double - expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links) - expect(Projects::LfsPointers::LfsDownloadService).to receive(:new).and_return(service).twice - expect(service).to receive(:execute).twice + expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute) subject.execute end + + context 'when lfs import fails' do + it 'logs the error' do + error_message = 'error message' + + allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(false) + expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(status: :error, message: error_message) + expect(Gitlab::AppLogger).to receive(:error).with("The Lfs import process failed. #{error_message}") + + subject.execute + end + end end end diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb index f1c0f5b9576..d8427d0bf78 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - require 'spec_helper' describe Projects::LfsPointers::LfsDownloadLinkListService do @@ -85,7 +84,7 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do end describe '#get_download_links' do - it 'raise errorif request fails' do + it 'raise error if request fails' do allow(Gitlab::HTTP).to receive(:post).and_return(Struct.new(:success?, :message).new(false, 'Failed request')) expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError) diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index cde3f2d6155..f4470b50753 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - require 'spec_helper' describe Projects::LfsPointers::LfsDownloadService do diff --git a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb index 5c9ca99df7c..7ca20a6d751 100644 --- a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb @@ -1,148 +1,63 @@ # frozen_string_literal: true - require 'spec_helper' describe Projects::LfsPointers::LfsImportService do + let(:project) { create(:project) } + let(:user) { project.creator } let(:import_url) { 'http://www.gitlab.com/demo/repo.git' } - let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch"} - let(:group) { create(:group, lfs_enabled: true)} - let!(:project) { create(:project, namespace: group, import_url: import_url, lfs_enabled: true) } - let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) } - let!(:existing_lfs_objects) { LfsObject.pluck(:oid, :size).to_h } - let(:oids) { { 'oid1' => 123, 'oid2' => 125 } } let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } } - let(:all_oids) { existing_lfs_objects.merge(oids) } - let(:remote_uri) { URI.parse(lfs_endpoint) } - - subject { described_class.new(project) } - - before do - allow(project.repository).to receive(:lfsconfig_for).and_return(nil) - allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - allow_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute).and_return(all_oids) - end - - describe '#execute' do - context 'when no lfs pointer is linked' do - before do - allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return([]) - allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links) - expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: URI.parse(default_endpoint)).and_call_original - end - - it 'retrieves all lfs pointers in the project repository' do - expect_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute) - - subject.execute - end - - it 'links existent lfs objects to the project' do - expect_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute) - - subject.execute - end - it 'retrieves the download links of non existent objects' do - expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(all_oids) + subject { described_class.new(project, user) } - subject.execute - end + context 'when lfs is enabled for the project' do + before do + allow(project).to receive(:lfs_enabled?).and_return(true) end - context 'when some lfs objects are linked' do - before do - allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(existing_lfs_objects.keys) - allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links) - end + it 'downloads lfs objects' do + service = double + expect_any_instance_of(Projects::LfsPointers::LfsObjectDownloadListService).to receive(:execute).and_return(oid_download_links) + expect(Projects::LfsPointers::LfsDownloadService).to receive(:new).and_return(service).twice + expect(service).to receive(:execute).twice - it 'retrieves the download links of non existent objects' do - expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(oids) + result = subject.execute - subject.execute - end + expect(result[:status]).to eq :success end - context 'when all lfs objects are linked' do - before do - allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(all_oids.keys) - allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute) - end + context 'when no downloadable lfs object links' do + it 'does not call LfsDownloadService' do + expect_any_instance_of(Projects::LfsPointers::LfsObjectDownloadListService).to receive(:execute).and_return({}) + expect(Projects::LfsPointers::LfsDownloadService).not_to receive(:new) - it 'retrieves no download links' do - expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with({}).and_call_original + result = subject.execute - expect(subject.execute).to be_empty + expect(result[:status]).to eq :success end end - context 'when lfsconfig file exists' do - before do - allow(project.repository).to receive(:lfsconfig_for).and_return("[lfs]\n\turl = #{lfs_endpoint}\n") - end - - context 'when url points to the same import url host' do - let(:lfs_endpoint) { "#{import_url}/different_endpoint" } - let(:service) { double } - - before do - allow(service).to receive(:execute) - end - it 'downloads lfs object using the new endpoint' do - expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: remote_uri).and_return(service) - - subject.execute - end - - context 'when import url has credentials' do - let(:import_url) { 'http://user:password@www.gitlab.com/demo/repo.git'} - - it 'adds the credentials to the new endpoint' do - expect(Projects::LfsPointers::LfsDownloadLinkListService) - .to receive(:new).with(project, remote_uri: URI.parse("http://user:password@www.gitlab.com/demo/repo.git/different_endpoint")) - .and_return(service) - - subject.execute - end - - context 'when url has its own credentials' do - let(:lfs_endpoint) { "http://user1:password1@www.gitlab.com/demo/repo.git/different_endpoint" } + context 'when an exception is raised' do + it 'returns error' do + error_message = "error message" + expect_any_instance_of(Projects::LfsPointers::LfsObjectDownloadListService).to receive(:execute).and_raise(StandardError, error_message) - it 'does not add the import url credentials' do - expect(Projects::LfsPointers::LfsDownloadLinkListService) - .to receive(:new).with(project, remote_uri: remote_uri) - .and_return(service) + result = subject.execute - subject.execute - end - end - end - end - - context 'when url points to a third party service' do - let(:lfs_endpoint) { 'http://third_party_service.com/info/lfs/objects/' } - - it 'disables lfs from the project' do - expect(project.lfs_enabled?).to be_truthy - - subject.execute - - expect(project.lfs_enabled?).to be_falsey - end - - it 'does not download anything' do - expect_any_instance_of(Projects::LfsPointers::LfsListService).not_to receive(:execute) - - subject.execute - end + expect(result[:status]).to eq :error + expect(result[:message]).to eq error_message end end end - describe '#default_endpoint_uri' do - let(:import_url) { 'http://www.gitlab.com/demo/repo' } + context 'when lfs is not enabled for the project' do + it 'does not download lfs objects' do + allow(project).to receive(:lfs_enabled?).and_return(false) + expect(Projects::LfsPointers::LfsObjectDownloadListService).not_to receive(:new) + expect(Projects::LfsPointers::LfsDownloadService).not_to receive(:new) + + result = subject.execute - it 'adds suffix .git if the url does not have it' do - expect(subject.send(:default_endpoint_uri).path).to match(/repo.git/) + expect(result[:status]).to eq :success end end end diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb index 5caa9de732e..849601c4a63 100644 --- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - require 'spec_helper' describe Projects::LfsPointers::LfsLinkService do diff --git a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb new file mode 100644 index 00000000000..9dac29765a2 --- /dev/null +++ b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Projects::LfsPointers::LfsObjectDownloadListService do + let(:import_url) { 'http://www.gitlab.com/demo/repo.git' } + let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch"} + let(:group) { create(:group, lfs_enabled: true)} + let!(:project) { create(:project, namespace: group, import_url: import_url, lfs_enabled: true) } + let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) } + let!(:existing_lfs_objects) { LfsObject.pluck(:oid, :size).to_h } + let(:oids) { { 'oid1' => 123, 'oid2' => 125 } } + let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } } + let(:all_oids) { existing_lfs_objects.merge(oids) } + let(:remote_uri) { URI.parse(lfs_endpoint) } + + subject { described_class.new(project) } + + before do + allow(project.repository).to receive(:lfsconfig_for).and_return(nil) + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + allow_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute).and_return(all_oids) + end + + describe '#execute' do + context 'when no lfs pointer is linked' do + before do + allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return([]) + allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links) + expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: URI.parse(default_endpoint)).and_call_original + end + + it 'retrieves all lfs pointers in the project repository' do + expect_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute) + + subject.execute + end + + it 'links existent lfs objects to the project' do + expect_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute) + + subject.execute + end + + it 'retrieves the download links of non existent objects' do + expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(all_oids) + + subject.execute + end + end + + context 'when some lfs objects are linked' do + before do + allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(existing_lfs_objects.keys) + allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links) + end + + it 'retrieves the download links of non existent objects' do + expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(oids) + + subject.execute + end + end + + context 'when all lfs objects are linked' do + before do + allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(all_oids.keys) + allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute) + end + + it 'retrieves no download links' do + expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with({}).and_call_original + + expect(subject.execute).to be_empty + end + end + + context 'when lfsconfig file exists' do + before do + allow(project.repository).to receive(:lfsconfig_for).and_return("[lfs]\n\turl = #{lfs_endpoint}\n") + end + + context 'when url points to the same import url host' do + let(:lfs_endpoint) { "#{import_url}/different_endpoint" } + let(:service) { double } + + before do + allow(service).to receive(:execute) + end + + it 'downloads lfs object using the new endpoint' do + expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: remote_uri).and_return(service) + + subject.execute + end + + context 'when import url has credentials' do + let(:import_url) { 'http://user:password@www.gitlab.com/demo/repo.git'} + + it 'adds the credentials to the new endpoint' do + expect(Projects::LfsPointers::LfsDownloadLinkListService) + .to receive(:new).with(project, remote_uri: URI.parse("http://user:password@www.gitlab.com/demo/repo.git/different_endpoint")) + .and_return(service) + + subject.execute + end + + context 'when url has its own credentials' do + let(:lfs_endpoint) { "http://user1:password1@www.gitlab.com/demo/repo.git/different_endpoint" } + + it 'does not add the import url credentials' do + expect(Projects::LfsPointers::LfsDownloadLinkListService) + .to receive(:new).with(project, remote_uri: remote_uri) + .and_return(service) + + subject.execute + end + end + end + end + + context 'when url points to a third party service' do + let(:lfs_endpoint) { 'http://third_party_service.com/info/lfs/objects/' } + + it 'disables lfs from the project' do + expect(project.lfs_enabled?).to be_truthy + + subject.execute + + expect(project.lfs_enabled?).to be_falsey + end + + it 'does not download anything' do + expect_any_instance_of(Projects::LfsPointers::LfsListService).not_to receive(:execute) + + subject.execute + end + end + end + end + + describe '#default_endpoint_uri' do + let(:import_url) { 'http://www.gitlab.com/demo/repo' } + + it 'adds suffix .git if the url does not have it' do + expect(subject.send(:default_endpoint_uri).path).to match(/repo.git/) + end + end +end -- cgit v1.2.1 From 3d0fc7fe2e9a5e9f3a24d3eed875044c196e5050 Mon Sep 17 00:00:00 2001 From: Ash McKenzie Date: Tue, 30 Apr 2019 17:43:01 +1200 Subject: Ensure Issue & MR note_html cannot be imported --- spec/lib/gitlab/import_export/project.json | 6 +++-- .../import_export/project_tree_restorer_spec.rb | 26 ++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 24d376999fc..43afc067e08 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -158,8 +158,8 @@ { "id": 351, "note": "Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi.", - "note_html": "

      dodgy html

      ", - "cached_markdown_version": 434343, + "note_html": "

      something else entirely

      ", + "cached_markdown_version": 917504, "noteable_type": "Issue", "author_id": 26, "created_at": "2016-06-14T15:02:47.770Z", @@ -2367,6 +2367,8 @@ { "id": 671, "note": "Sit voluptatibus eveniet architecto quidem.", + "note_html": "

      something else entirely

      ", + "cached_markdown_version": 917504, "noteable_type": "MergeRequest", "author_id": 26, "created_at": "2016-06-14T15:02:56.632Z", diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 7fd0ea539c2..056a1d52d78 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -10,7 +10,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do create(:user, username: 'bernard_willms'), create(:user, username: 'saul_will') ] - @markdown_classes = [AbuseReport, Appearance, ApplicationSetting, BroadcastMessage, Issue, Label, MergeRequest, Milestone, Namespace, Project, Release, ResourceLabelEvent, Snippet, UserStatus] RSpec::Mocks.with_temporary_scope do @project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') @@ -22,7 +21,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA') allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch) - @markdown_classes.each {|klass| allow_any_instance_of(klass).to receive(:latest_cached_markdown_version).and_return(434343)} @project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project) @@ -61,20 +59,20 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'when importing a project with cached_markdown_version and note_html' do - let!(:issue) { Issue.find_by(description: 'Aliquam enim illo et possimus.') } - let(:note1) { issue.notes.select {|n| n.note.match(/Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi/)}.first } - let(:note2) { issue.notes.select {|n| n.note.match(/Est reprehenderit quas aut aspernatur autem recusandae voluptatem/)}.first } - - it 'does not import the note_html' do - expect(note1.note_html).to match(/Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi/) - end - - it 'does not set the old cached_markdown_version' do - expect(note2.cached_markdown_version).not_to eq(121212) + context 'for an Issue' do + it 'does not import note_html' do + note_content = 'Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi' + issue_note = Issue.find_by(description: 'Aliquam enim illo et possimus.').notes.select { |n| n.note.match(/#{note_content}/)}.first + expect(issue_note.note_html).to match(/#{note_content}/) + end end - it 'does not import the note_html' do - expect(note2.note_html).to match(/Est reprehenderit quas aut aspernatur autem recusandae voluptatem/) + context 'for a Merge Request' do + it 'does not import note_html' do + note_content = 'Sit voluptatibus eveniet architecto quidem' + merge_request_note = MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{note_content}/)}.first + expect(merge_request_note.note_html).to match(/#{note_content}/) + end end end -- cgit v1.2.1 From f2bc55d76f278e492902bec99534600d589177b7 Mon Sep 17 00:00:00 2001 From: charlieablett Date: Tue, 30 Apr 2019 20:57:52 +1200 Subject: Remove accidental regressions --- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 056a1d52d78..9aafa41feee 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -7,8 +7,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do # Using an admin for import, so we can check assignment of existing members @user = create(:admin) @existing_members = [ - create(:user, username: 'bernard_willms'), - create(:user, username: 'saul_will') + create(:user, username: 'bernard_willms'), + create(:user, username: 'saul_will') ] RSpec::Mocks.with_temporary_scope do @@ -22,12 +22,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA') allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch) - @project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project) + project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project) expect(Gitlab::ImportExport::RelationFactory).to receive(:create).with(hash_including(excluded_keys: ['whatever'])).and_call_original.at_least(:once) - allow(@project_tree_restorer).to receive(:excluded_keys_for_relation).and_return(['whatever']) + allow(project_tree_restorer).to receive(:excluded_keys_for_relation).and_return(['whatever']) - @restored_project_json = @project_tree_restorer.restore + @restored_project_json = project_tree_restorer.restore end end @@ -76,6 +76,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end end + it 'creates a valid pipeline note' do expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty end -- cgit v1.2.1 From c5305a02e3db04f4e903d77fc3de4528b75b7131 Mon Sep 17 00:00:00 2001 From: Evan Read Date: Tue, 30 Apr 2019 10:38:46 +0000 Subject: Docs aren't reviewed using roulette --- spec/lib/gitlab/danger/helper_spec.rb | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 66cd8171c12..32b90041c64 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -191,9 +191,8 @@ describe Gitlab::Danger::Helper do expect(helper.changes_by_category).to eq( backend: %w[foo.rb], database: %w[db/foo], - docs: %w[foo.md], frontend: %w[foo.js], - none: %w[ee/changelogs/foo.yml], + none: %w[ee/changelogs/foo.yml foo.md], qa: %w[qa/foo], unknown: %w[foo] ) @@ -202,13 +201,13 @@ describe Gitlab::Danger::Helper do describe '#category_for_file' do where(:path, :expected_category) do - 'doc/foo' | :docs - 'CONTRIBUTING.md' | :docs - 'LICENSE' | :docs - 'MAINTENANCE.md' | :docs - 'PHILOSOPHY.md' | :docs - 'PROCESS.md' | :docs - 'README.md' | :docs + 'doc/foo' | :none + 'CONTRIBUTING.md' | :none + 'LICENSE' | :none + 'MAINTENANCE.md' | :none + 'PHILOSOPHY.md' | :none + 'PROCESS.md' | :none + 'README.md' | :none 'ee/doc/foo' | :unknown 'ee/README' | :unknown @@ -272,8 +271,8 @@ describe Gitlab::Danger::Helper do 'foo/bar.rb' | :backend 'foo/bar.js' | :frontend - 'foo/bar.txt' | :docs - 'foo/bar.md' | :docs + 'foo/bar.txt' | :none + 'foo/bar.md' | :none end with_them do -- cgit v1.2.1 From 96ad838433829e5432595ace85993f141cf4291a Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 25 Apr 2019 14:11:01 +0200 Subject: Use FIXTURES_PATH in balsamiq_viewer_integration_spec.js --- spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js index 68b4f261617..33210794ba1 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js @@ -1,5 +1,7 @@ import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer'; -import bmprPath from '../../fixtures/blob/balsamiq/test.bmpr'; +import { FIXTURES_PATH } from 'spec/test_constants'; + +const bmprPath = `${FIXTURES_PATH}/blob/balsamiq/test.bmpr`; describe('Balsamiq integration spec', () => { let container; -- cgit v1.2.1 From d89b19476d80612e97a2853249fabc63a5ff3f42 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 25 Apr 2019 14:22:00 +0200 Subject: Use FIXTURES_PATH for loading test.pdf --- spec/javascripts/blob/pdf/index_spec.js | 4 +++- spec/javascripts/pdf/index_spec.js | 4 +++- spec/javascripts/pdf/page_spec.js | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index acf87580777..6fa3890483c 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -1,5 +1,7 @@ import renderPDF from '~/blob/pdf'; -import testPDF from '../../fixtures/blob/pdf/test.pdf'; +import { FIXTURES_PATH } from 'spec/test_constants'; + +const testPDF = `${FIXTURES_PATH}/blob/pdf/test.pdf`; describe('PDF renderer', () => { let viewer; diff --git a/spec/javascripts/pdf/index_spec.js b/spec/javascripts/pdf/index_spec.js index 6df4ded92f0..7191b65b4cd 100644 --- a/spec/javascripts/pdf/index_spec.js +++ b/spec/javascripts/pdf/index_spec.js @@ -3,7 +3,9 @@ import { GlobalWorkerOptions } from 'vendor/pdf'; import workerSrc from 'vendor/pdf.worker.min'; import PDFLab from '~/pdf/index.vue'; -import pdf from '../fixtures/blob/pdf/test.pdf'; +import { FIXTURES_PATH } from 'spec/test_constants'; + +const pdf = `${FIXTURES_PATH}/blob/pdf/test.pdf`; GlobalWorkerOptions.workerSrc = workerSrc; const Component = Vue.extend(PDFLab); diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js index b2e9fa42a63..f899b5b3a0d 100644 --- a/spec/javascripts/pdf/page_spec.js +++ b/spec/javascripts/pdf/page_spec.js @@ -4,7 +4,9 @@ import workerSrc from 'vendor/pdf.worker.min'; import PageComponent from '~/pdf/page/index.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import testPDF from 'spec/fixtures/blob/pdf/test.pdf'; +import { FIXTURES_PATH } from 'spec/test_constants'; + +const testPDF = `${FIXTURES_PATH}/blob/pdf/test.pdf`; describe('Page component', () => { const Component = Vue.extend(PageComponent); -- cgit v1.2.1 From ac744fd4fceb996dcafe7958dd35b081331f46fe Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Tue, 30 Apr 2019 12:05:54 +0000 Subject: Remove disabled pages domains Domain will be removed by verification worker after 1 week of being disabled --- spec/factories/pages_domains.rb | 4 +++ spec/models/pages_domain_spec.rb | 28 +++++++++++++++ spec/services/verify_pages_domain_service_spec.rb | 12 +++---- .../pages_domain_removal_cron_worker_spec.rb | 42 ++++++++++++++++++++++ .../pages_domain_verification_cron_worker_spec.rb | 8 ++--- 5 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 spec/workers/pages_domain_removal_cron_worker_spec.rb (limited to 'spec') diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index b74f72f2bd3..db8384877b0 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -45,6 +45,10 @@ nNp/xedE1YxutQ== remove_at { 1.day.from_now } end + trait :should_be_removed do + remove_at { 1.day.ago } + end + trait :unverified do verified_at nil end diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 142ddebbbf8..ec4d4517f82 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -344,4 +344,32 @@ describe PagesDomain do end end end + + describe '.for_removal' do + subject { described_class.for_removal } + + context 'when domain is not schedule for removal' do + let!(:domain) { create :pages_domain } + + it 'does not return domain' do + is_expected.to be_empty + end + end + + context 'when domain is scheduled for removal yesterday' do + let!(:domain) { create :pages_domain, remove_at: 1.day.ago } + + it 'returns domain' do + is_expected.to eq([domain]) + end + end + + context 'when domain is scheduled for removal tomorrow' do + let!(:domain) { create :pages_domain, remove_at: 1.day.from_now } + + it 'does not return domain' do + is_expected.to be_empty + end + end + end end diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb index e5c7b5bb9a7..f2b3b44d223 100644 --- a/spec/services/verify_pages_domain_service_spec.rb +++ b/spec/services/verify_pages_domain_service_spec.rb @@ -57,12 +57,12 @@ describe VerifyPagesDomainService do expect(domain).not_to be_verified end - it 'disables domain and shedules it for removal' do - Timecop.freeze do - service.execute - expect(domain).not_to be_enabled - expect(domain.remove_at).to be_within(1.second).of(1.week.from_now) - end + it 'disables domain and shedules it for removal in 1 week' do + service.execute + + expect(domain).not_to be_enabled + + expect(domain.remove_at).to be_like_time(7.days.from_now) end end diff --git a/spec/workers/pages_domain_removal_cron_worker_spec.rb b/spec/workers/pages_domain_removal_cron_worker_spec.rb new file mode 100644 index 00000000000..0e1171e8491 --- /dev/null +++ b/spec/workers/pages_domain_removal_cron_worker_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomainRemovalCronWorker do + subject(:worker) { described_class.new } + + describe '#perform' do + context 'when there is domain which should be removed' do + let!(:domain_for_removal) { create(:pages_domain, :should_be_removed) } + + before do + stub_feature_flags(remove_disabled_domains: true) + end + + it 'removes domain' do + expect { worker.perform }.to change { PagesDomain.count }.by(-1) + expect(PagesDomain.exists?).to eq(false) + end + + context 'when domain removal is disabled' do + before do + stub_feature_flags(remove_disabled_domains: false) + end + + it 'does not remove pages domain' do + expect { worker.perform }.not_to change { PagesDomain.count } + expect(PagesDomain.find_by(domain: domain_for_removal.domain)).to be_present + end + end + end + + context 'where there is a domain which scheduled for removal in the future' do + let!(:domain_for_removal) { create(:pages_domain, :scheduled_for_removal) } + + it 'does not remove pages domain' do + expect { worker.perform }.not_to change { PagesDomain.count } + expect(PagesDomain.find_by(domain: domain_for_removal.domain)).to be_present + end + end + end +end diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb index 9b479da1cb6..186824a444f 100644 --- a/spec/workers/pages_domain_verification_cron_worker_spec.rb +++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb @@ -6,11 +6,11 @@ describe PagesDomainVerificationCronWorker do subject(:worker) { described_class.new } describe '#perform' do - it 'enqueues a PagesDomainVerificationWorker for domains needing verification' do - verified = create(:pages_domain) - reverify = create(:pages_domain, :reverify) - disabled = create(:pages_domain, :disabled) + let!(:verified) { create(:pages_domain) } + let!(:reverify) { create(:pages_domain, :reverify) } + let!(:disabled) { create(:pages_domain, :disabled) } + it 'enqueues a PagesDomainVerificationWorker for domains needing verification' do [reverify, disabled].each do |domain| expect(PagesDomainVerificationWorker).to receive(:perform_async).with(domain.id) end -- cgit v1.2.1 From daa8f784d016091fd2a56fd195cbd6da2f199350 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 24 Apr 2019 18:37:29 +0700 Subject: Fix environment automatic on_stop trigger Due to the nature of pipelines for merge requests, deployments.ref can be a merge request ref instead of a branch name. We support the environment auto-stop hook for this case --- spec/models/merge_request_spec.rb | 44 +++++++++++++ spec/services/ci/stop_environments_service_spec.rb | 76 ++++++++++++++++++++++ spec/services/merge_requests/close_service_spec.rb | 8 +++ .../merge_requests/post_merge_service_spec.rb | 8 +++ 4 files changed, 136 insertions(+) (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index f61857ea5ff..fb7f43b25cf 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2262,6 +2262,50 @@ describe MergeRequest do end end + describe "#environments" do + subject { merge_request.environments } + + let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } + let(:project) { merge_request.project } + + let(:pipeline) do + create(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, project: project, + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + let!(:job) { create(:ci_build, :start_review_app, pipeline: pipeline, project: project) } + + it 'returns environments' do + is_expected.to eq(pipeline.environments) + expect(subject.count).to be(1) + end + + context 'when pipeline is not associated with environments' do + let!(:job) { create(:ci_build, pipeline: pipeline, project: project) } + + it 'returns empty array' do + is_expected.to be_empty + end + end + + context 'when pipeline is not a pipeline for merge request' do + let(:pipeline) do + create(:ci_pipeline, + project: project, + ref: 'feature', + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + it 'returns empty relation' do + is_expected.to be_empty + end + end + end + describe "#reload_diff" do it 'calls MergeRequests::ReloadDiffsService#execute with correct params' do user = create(:user) diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb index 31b8d540356..890fa5bc009 100644 --- a/spec/services/ci/stop_environments_service_spec.rb +++ b/spec/services/ci/stop_environments_service_spec.rb @@ -105,6 +105,82 @@ describe Ci::StopEnvironmentsService do end end + describe '#execute_for_merge_request' do + subject { service.execute_for_merge_request(merge_request) } + + let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } + let(:project) { merge_request.project } + let(:user) { create(:user) } + + let(:pipeline) do + create(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, + project: project, + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + let!(:review_job) { create(:ci_build, :start_review_app, pipeline: pipeline, project: project) } + let!(:stop_review_job) { create(:ci_build, :stop_review_app, :manual, pipeline: pipeline, project: project) } + + before do + review_job.deployment.success! + end + + it 'has active environment at first' do + expect(pipeline.environments.first).to be_available + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it 'stops the active environment' do + subject + + expect(pipeline.environments.first).to be_stopped + end + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it 'does not stop the active environment' do + subject + + expect(pipeline.environments.first).to be_available + end + end + + context 'when pipeline is not associated with environments' do + let!(:job) { create(:ci_build, pipeline: pipeline, project: project) } + + it 'does not raise exception' do + expect { subject }.not_to raise_exception + end + end + + context 'when pipeline is not a pipeline for merge request' do + let(:pipeline) do + create(:ci_pipeline, + project: project, + ref: 'feature', + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + it 'does not stop the active environment' do + subject + + expect(pipeline.environments.first).to be_available + end + end + end + def expect_environment_stopped_on(branch) expect_any_instance_of(Environment) .to receive(:stop!) diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index aa7dfda4950..ffa612cf315 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -74,6 +74,14 @@ describe MergeRequests::CloseService do .to change { project.open_merge_requests_count }.from(1).to(0) end + it 'clean up environments for the merge request' do + expect_next_instance_of(Ci::StopEnvironmentsService) do |service| + expect(service).to receive(:execute_for_merge_request).with(merge_request) + end + + described_class.new(project, user).execute(merge_request) + end + context 'current user is not authorized to close merge request' do before do perform_enqueued_jobs do diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index 7b87913ab8b..ffc86f68469 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -62,5 +62,13 @@ describe MergeRequests::PostMergeService do expect(merge_request.reload).to be_merged end + + it 'clean up environments for the merge request' do + expect_next_instance_of(Ci::StopEnvironmentsService) do |service| + expect(service).to receive(:execute_for_merge_request).with(merge_request) + end + + described_class.new(project, user).execute(merge_request) + end end end -- cgit v1.2.1 From 5ee7876534891df9c2f5ab6c5112cc47f98b1df5 Mon Sep 17 00:00:00 2001 From: John Cai Date: Wed, 24 Apr 2019 18:50:45 -0700 Subject: Add client methods for FetchIntoObjectPool RPC Gitaly's FetchIntoObjectPool RPC will idempotently fetch objects into an object pool. If the pool doesn't exist, it will create an empty pool before attempting the fetch. This change adds client code as well as specs to cover this behavior. --- spec/lib/gitlab/git/object_pool_spec.rb | 41 ++++++++++++ spec/lib/gitlab/git/repository_spec.rb | 78 +--------------------- .../gitaly_client/object_pool_service_spec.rb | 20 ++++++ spec/support/helpers/repo_helpers.rb | 78 ++++++++++++++++++++++ 4 files changed, 140 insertions(+), 77 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb index 0d5069568e1..6511c2b61bf 100644 --- a/spec/lib/gitlab/git/object_pool_spec.rb +++ b/spec/lib/gitlab/git/object_pool_spec.rb @@ -3,8 +3,12 @@ require 'spec_helper' describe Gitlab::Git::ObjectPool do + include RepoHelpers + let(:pool_repository) { create(:pool_repository) } let(:source_repository) { pool_repository.source_project.repository } + let(:source_repository_path) { File.join(TestEnv.repos_path, source_repository.relative_path) } + let(:source_repository_rugged) { Rugged::Repository.new(source_repository_path) } subject { pool_repository.object_pool } @@ -76,4 +80,41 @@ describe Gitlab::Git::ObjectPool do end end end + + describe '#fetch' do + let(:commit_count) { source_repository.commit_count } + + context "when the object's pool repository exists" do + it 'does not raise an error' do + expect { subject.fetch }.not_to raise_error + end + end + + context "when the object's pool repository does not exist" do + before do + subject.delete + end + + it "re-creates the object pool's repository" do + subject.fetch + + expect(subject.repository.exists?).to be(true) + end + + it 'does not raise an error' do + expect { subject.fetch }.not_to raise_error + end + + it 'fetches objects from the source repository' do + new_commit_id = new_commit_edit_old_file(source_repository_rugged).oid + + expect(subject.repository.exists?).to be false + + subject.fetch + + expect(subject.repository.commit_count('refs/remotes/origin/master')).to eq(commit_count) + expect(subject.repository.commit(new_commit_id).id).to eq(new_commit_id) + end + end + end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 5f8a2848944..0f6aac9b6de 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -3,6 +3,7 @@ require "spec_helper" describe Gitlab::Git::Repository, :seed_helper do include Gitlab::EncodingHelper + include RepoHelpers using RSpec::Parameterized::TableSyntax shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method| @@ -2209,83 +2210,6 @@ describe Gitlab::Git::Repository, :seed_helper do repository_rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha) end - # Build the options hash that's passed to Rugged::Commit#create - def commit_options(repo, index, target, ref, message) - options = {} - options[:tree] = index.write_tree(repo) - options[:author] = { - email: "test@example.com", - name: "Test Author", - time: Time.gm(2014, "mar", 3, 20, 15, 1) - } - options[:committer] = { - email: "test@example.com", - name: "Test Author", - time: Time.gm(2014, "mar", 3, 20, 15, 1) - } - options[:message] ||= message - options[:parents] = repo.empty? ? [] : [target].compact - options[:update_ref] = ref - - options - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the - # contents of CHANGELOG with a single new line of text. - def new_commit_edit_old_file(repo) - oid = repo.write("I replaced the changelog with this text", :blob) - index = repo.index - index.read_tree(repo.head.target.tree) - index.add(path: "CHANGELOG", oid: oid, mode: 0100644) - - options = commit_options( - repo, - index, - repo.head.target, - "HEAD", - "Edit CHANGELOG in its original location" - ) - - sha = Rugged::Commit.create(repo, options) - repo.lookup(sha) - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the - # contents of the specified file_path with new text. - def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head) - oid = repo.write(text, :blob) - index = repo.index - index.read_tree(branch.target.tree) - index.add(path: file_path, oid: oid, mode: 0100644) - options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message) - sha = Rugged::Commit.create(repo, options) - repo.lookup(sha) - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the - # contents of encoding/CHANGELOG with new text. - def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text) - branch = repo.branches[branch_name] - new_commit_edit_new_file(repo, file_path, commit_message, text, branch) - end - - # Writes a new commit to the repo and returns a Rugged::Commit. Moves the - # CHANGELOG file to the encoding/ directory. - def new_commit_move_file(repo) - blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid] - file_content = repo.lookup(blob_oid).content - oid = repo.write(file_content, :blob) - index = repo.index - index.read_tree(repo.head.target.tree) - index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644) - index.remove("CHANGELOG") - - options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/") - - sha = Rugged::Commit.create(repo, options) - repo.lookup(sha) - end - def refs(dir) IO.popen(%W[git -C #{dir} for-each-ref], &:read).split("\n").map do |line| line.split("\t").last diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb index 149b7ec5bb0..0e0c3d329b5 100644 --- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb @@ -43,4 +43,24 @@ describe Gitlab::GitalyClient::ObjectPoolService do end end end + + describe '#fetch' do + before do + subject.delete + end + + it 'restores the pool repository objects' do + subject.fetch(project.repository) + + expect(object_pool.repository.exists?).to be(true) + end + + context 'when called twice' do + it "doesn't raise an error" do + subject.delete + + expect { subject.fetch(project.repository) }.not_to raise_error + end + end + end end diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb index 4af90f4af79..44d95a029af 100644 --- a/spec/support/helpers/repo_helpers.rb +++ b/spec/support/helpers/repo_helpers.rb @@ -11,6 +11,8 @@ module RepoHelpers # blob.path # => 'files/js/commit.js.coffee' # blob.data # => 'class Commit...' # + # Build the options hash that's passed to Rugged::Commit#create + def sample_blob OpenStruct.new( oid: '5f53439ca4b009096571d3c8bc3d09d30e7431b3', @@ -129,4 +131,80 @@ eos file_content: content ).execute end + + def commit_options(repo, index, target, ref, message) + options = {} + options[:tree] = index.write_tree(repo) + options[:author] = { + email: "test@example.com", + name: "Test Author", + time: Time.gm(2014, "mar", 3, 20, 15, 1) + } + options[:committer] = { + email: "test@example.com", + name: "Test Author", + time: Time.gm(2014, "mar", 3, 20, 15, 1) + } + options[:message] ||= message + options[:parents] = repo.empty? ? [] : [target].compact + options[:update_ref] = ref + + options + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the + # contents of CHANGELOG with a single new line of text. + def new_commit_edit_old_file(repo) + oid = repo.write("I replaced the changelog with this text", :blob) + index = repo.index + index.read_tree(repo.head.target.tree) + index.add(path: "CHANGELOG", oid: oid, mode: 0100644) + + options = commit_options( + repo, + index, + repo.head.target, + "HEAD", + "Edit CHANGELOG in its original location" + ) + + sha = Rugged::Commit.create(repo, options) + repo.lookup(sha) + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the + # contents of the specified file_path with new text. + def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head) + oid = repo.write(text, :blob) + index = repo.index + index.read_tree(branch.target.tree) + index.add(path: file_path, oid: oid, mode: 0100644) + options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message) + sha = Rugged::Commit.create(repo, options) + repo.lookup(sha) + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the + # contents of encoding/CHANGELOG with new text. + def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text) + branch = repo.branches[branch_name] + new_commit_edit_new_file(repo, file_path, commit_message, text, branch) + end + + # Writes a new commit to the repo and returns a Rugged::Commit. Moves the + # CHANGELOG file to the encoding/ directory. + def new_commit_move_file(repo) + blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid] + file_content = repo.lookup(blob_oid).content + oid = repo.write(file_content, :blob) + index = repo.index + index.read_tree(repo.head.target.tree) + index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644) + index.remove("CHANGELOG") + + options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/") + + sha = Rugged::Commit.create(repo, options) + repo.lookup(sha) + end end -- cgit v1.2.1 From aebb2f70257882dd530b820f3cfdd67621d2a3fd Mon Sep 17 00:00:00 2001 From: Roger Meier Date: Sun, 7 Apr 2019 21:21:52 +0200 Subject: feat: allow Sentry configuration to be passed on gitlab.yml --- spec/javascripts/raven/index_spec.js | 10 +++--- spec/javascripts/raven/raven_config_spec.js | 10 +++--- .../application_setting_examples.rb | 37 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/raven/index_spec.js b/spec/javascripts/raven/index_spec.js index a503a54029f..6b9fe923624 100644 --- a/spec/javascripts/raven/index_spec.js +++ b/spec/javascripts/raven/index_spec.js @@ -5,19 +5,19 @@ describe('RavenConfig options', () => { const sentryDsn = 'sentryDsn'; const currentUserId = 'currentUserId'; const gitlabUrl = 'gitlabUrl'; - const isProduction = 'isProduction'; + const environment = 'test'; const revision = 'revision'; let indexReturnValue; beforeEach(() => { window.gon = { sentry_dsn: sentryDsn, + sentry_environment: environment, current_user_id: currentUserId, gitlab_url: gitlabUrl, revision, }; - process.env.NODE_ENV = isProduction; process.env.HEAD_COMMIT_SHA = revision; spyOn(RavenConfig, 'init'); @@ -25,12 +25,12 @@ describe('RavenConfig options', () => { indexReturnValue = index(); }); - it('should init with .sentryDsn, .currentUserId, .whitelistUrls and .isProduction', () => { + it('should init with .sentryDsn, .currentUserId, .whitelistUrls and environment', () => { expect(RavenConfig.init).toHaveBeenCalledWith({ sentryDsn, currentUserId, - whitelistUrls: [gitlabUrl], - isProduction, + whitelistUrls: [gitlabUrl, 'webpack-internal://'], + environment, release: revision, tags: { revision, diff --git a/spec/javascripts/raven/raven_config_spec.js b/spec/javascripts/raven/raven_config_spec.js index 5cc59cc28d3..af634a0c196 100644 --- a/spec/javascripts/raven/raven_config_spec.js +++ b/spec/javascripts/raven/raven_config_spec.js @@ -69,8 +69,8 @@ describe('RavenConfig', () => { let ravenConfig; const options = { sentryDsn: '//sentryDsn', - whitelistUrls: ['//gitlabUrl'], - isProduction: true, + whitelistUrls: ['//gitlabUrl', 'webpack-internal://'], + environment: 'test', release: 'revision', tags: { revision: 'revision', @@ -95,7 +95,7 @@ describe('RavenConfig', () => { release: options.release, tags: options.tags, whitelistUrls: options.whitelistUrls, - environment: 'production', + environment: 'test', ignoreErrors: ravenConfig.IGNORE_ERRORS, ignoreUrls: ravenConfig.IGNORE_URLS, shouldSendCallback: jasmine.any(Function), @@ -106,8 +106,8 @@ describe('RavenConfig', () => { expect(raven.install).toHaveBeenCalled(); }); - it('should set .environment to development if isProduction is false', () => { - ravenConfig.options.isProduction = false; + it('should set environment from options', () => { + ravenConfig.options.environment = 'development'; RavenConfig.configure.call(ravenConfig); diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb index e7ec24c5b7e..d8f7ba1185e 100644 --- a/spec/support/shared_examples/application_setting_examples.rb +++ b/spec/support/shared_examples/application_setting_examples.rb @@ -249,4 +249,41 @@ RSpec.shared_examples 'application settings examples' do expect(setting.password_authentication_enabled_for_web?).to be_falsey end + + describe 'sentry settings' do + context 'when the sentry settings are not set in gitlab.yml' do + it 'fallbacks to the settings in the database' do + setting.sentry_enabled = true + setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' + setting.clientside_sentry_enabled = true + setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' + + allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) + allow(Gitlab.config.sentry).to receive(:dsn).and_return(nil) + + expect(setting.sentry_enabled).to eq true + expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' + expect(setting.clientside_sentry_enabled).to eq true + expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' + end + end + + context 'when the sentry settings are set in gitlab.yml' do + it 'does not fallback to the settings in the database' do + setting.sentry_enabled = false + setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' + setting.clientside_sentry_enabled = false + setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' + + allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) + allow(Gitlab.config.sentry).to receive(:dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42') + + expect(setting).not_to receive(:read_attribute) + expect(setting.sentry_enabled).to eq true + expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' + expect(setting.clientside_sentry_enabled).to eq true + expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' + end + end + end end -- cgit v1.2.1 From e51912543c24c2dcf25b2c8d775fc5f57f85e639 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 16 Apr 2019 18:56:48 +0700 Subject: Add project level config for merge train We add the option and expose the parameter for frontend work --- spec/models/merge_request_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index f61857ea5ff..06dcc44d35b 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2123,7 +2123,7 @@ describe MergeRequest do end context 'when merges are not restricted to green builds' do - subject { build(:merge_request, target_project: build(:project, only_allow_merge_if_pipeline_succeeds: false)) } + subject { build(:merge_request, target_project: create(:project, only_allow_merge_if_pipeline_succeeds: false)) } context 'and a failed pipeline is associated' do before do -- cgit v1.2.1 From ee32e06f1f117e748963a8badf6abfd5b57f339b Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Tue, 30 Apr 2019 11:35:12 -0500 Subject: Fix IDE get file data with '/' as relative root https://gitlab.com/gitlab-org/gitlab-ce/issues/60932 --- spec/javascripts/ide/stores/actions/file_spec.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index 1e5b55af4ba..e6fb08bcc49 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -10,11 +10,19 @@ import eventHub from '~/ide/eventhub'; import { file, resetStore } from '../../helpers'; import testAction from '../../../helpers/vuex_action_helper'; +const RELATIVE_URL_ROOT = '/gitlab'; + describe('IDE store file actions', () => { let mock; + let originalGon; beforeEach(() => { mock = new MockAdapter(axios); + originalGon = window.gon; + window.gon = { + ...window.gon, + relative_url_root: RELATIVE_URL_ROOT, + }; spyOn(router, 'push'); }); @@ -22,6 +30,7 @@ describe('IDE store file actions', () => { afterEach(() => { mock.restore(); resetStore(store); + window.gon = originalGon; }); describe('closeFile', () => { @@ -173,13 +182,13 @@ describe('IDE store file actions', () => { spyOn(service, 'getFileData').and.callThrough(); localFile = file(`newCreate-${Math.random()}`); - localFile.url = `${gl.TEST_HOST}/getFileDataURL`; + localFile.url = `project/getFileDataURL`; store.state.entries[localFile.path] = localFile; }); describe('success', () => { beforeEach(() => { - mock.onGet(`${gl.TEST_HOST}/getFileDataURL`).replyOnce( + mock.onGet(`${RELATIVE_URL_ROOT}/project/getFileDataURL`).replyOnce( 200, { blame_path: 'blame_path', @@ -200,7 +209,9 @@ describe('IDE store file actions', () => { store .dispatch('getFileData', { path: localFile.path }) .then(() => { - expect(service.getFileData).toHaveBeenCalledWith(`${gl.TEST_HOST}/getFileDataURL`); + expect(service.getFileData).toHaveBeenCalledWith( + `${RELATIVE_URL_ROOT}/project/getFileDataURL`, + ); done(); }) @@ -266,7 +277,7 @@ describe('IDE store file actions', () => { describe('error', () => { beforeEach(() => { - mock.onGet(`${gl.TEST_HOST}/getFileDataURL`).networkError(); + mock.onGet(`project/getFileDataURL`).networkError(); }); it('dispatches error action', done => { -- cgit v1.2.1 From 8ad7cbf7739ef1111a816e18786ac8cea8d303a4 Mon Sep 17 00:00:00 2001 From: Ryan Cobb Date: Tue, 30 Apr 2019 12:26:16 -0600 Subject: Cleanup spec by removing not needed let --- spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb index 0edbfc869a6..090e456644f 100644 --- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb @@ -79,12 +79,11 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do end context 'additional metrics' do - let(:cpu_time) { 3.14 } let(:unicorn_workers) { 2 } before do allow(unicorn).to receive(:listener_names).and_return([""]) - allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(cpu_time) + allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14) allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers) end -- cgit v1.2.1 From 32ddc3fed616b9eebdd7b5553d4aa08c0572ff1d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 30 Apr 2019 11:39:15 -0700 Subject: Allow a member to have an access level equal to parent group Suppose you have this configuration: 1. Subgroup `hello/world` 2. Subgroup `hello/mergers`. 3. Project `hello/world/my-project` has invited group `hello/world` to access protected branches. 4. The rule allows the group to merge but no one can push. 5. User `newuser` has Owner access to the parent group `hello`. Previously, there was no way for the user `newuser` to be added to the `hello/mergers` group since the validation only allowed a user to be added at a higher access level. Since membership in a subgroup confers certain access rights, such as being able to merge or push code to protected branches, we have to loosen the validation and allow someone to be added at an equal level granted by the parent group. Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/11323 --- spec/models/member_spec.rb | 10 ++++++++++ spec/requests/api/members_spec.rb | 2 +- spec/support/shared_examples/models/member_shared_examples.rb | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index c68c3ce2abe..782a84f922b 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -70,6 +70,16 @@ describe Member do expect(child_member).not_to be_valid end + # Membership in a subgroup confers certain access rights, such as being + # able to merge or push code to protected branches. + it "is valid with an equal level" do + child_member.access_level = GroupMember::DEVELOPER + + child_member.validate + + expect(child_member).to be_valid + end + it "is valid with a higher level" do child_member.access_level = GroupMember::MAINTAINER diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 79edbb301f2..48869cab4da 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -236,7 +236,7 @@ describe API::Members do params: { user_id: stranger.id, access_level: Member::REPORTER } expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['access_level']).to eq(["should be higher than Developer inherited membership from group #{parent.name}"]) + expect(json_response['message']['access_level']).to eq(["should be greater than or equal to Developer inherited membership from group #{parent.name}"]) end it 'creates the member if group level is lower', :nested_groups do diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb index 77376496854..e5375bc8280 100644 --- a/spec/support/shared_examples/models/member_shared_examples.rb +++ b/spec/support/shared_examples/models/member_shared_examples.rb @@ -41,7 +41,7 @@ shared_examples_for 'inherited access level as a member of entity' do member.update(access_level: Gitlab::Access::REPORTER) - expect(member.errors.full_messages).to eq(["Access level should be higher than Developer inherited membership from group #{parent_entity.name}"]) + expect(member.errors.full_messages).to eq(["Access level should be greater than or equal to Developer inherited membership from group #{parent_entity.name}"]) end it 'allows changing the level from a non existing member' do -- cgit v1.2.1 From 4b46b530829cc3dd82c2620a76fbe637ca9009c0 Mon Sep 17 00:00:00 2001 From: charlieablett Date: Wed, 1 May 2019 10:38:41 +1200 Subject: Add `html` to sensitive words --- spec/features/projects/import_export/export_file_spec.rb | 2 +- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index f76f9ba7577..9d74a96ab3d 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -12,7 +12,7 @@ describe 'Import/Export - project export integration test', :js do let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } - let(:sensitive_words) { %w[pass secret token key encrypted] } + let(:sensitive_words) { %w[pass secret token key encrypted html] } let(:safe_list) do { token: [ProjectHook, Ci::Trigger, CommitStatus], diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 9aafa41feee..9d2b69ea798 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -63,6 +63,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do it 'does not import note_html' do note_content = 'Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi' issue_note = Issue.find_by(description: 'Aliquam enim illo et possimus.').notes.select { |n| n.note.match(/#{note_content}/)}.first + expect(issue_note.note_html).to match(/#{note_content}/) end end @@ -71,12 +72,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do it 'does not import note_html' do note_content = 'Sit voluptatibus eveniet architecto quidem' merge_request_note = MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{note_content}/)}.first + expect(merge_request_note.note_html).to match(/#{note_content}/) end end end - it 'creates a valid pipeline note' do expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty end -- cgit v1.2.1 From 911701ae473f28eebf5d1d3cf4d4eef5130f384e Mon Sep 17 00:00:00 2001 From: Paul Gascou-Vaillancourt Date: Wed, 1 May 2019 10:04:07 +0000 Subject: Extract discussion notes into new component - Moved discussion notes out of `NoteableDiscussion` component into a new `DiscussionNotes` component - Wrote Jest tests for the new `DiscussionNotes` component - Updated Jest config for emojis fixtures - Updated Karma tests `NoteableDiscussion` to match its new structure - Convert `DiffDiscussions` tests to use Vue test utils --- .../notes/components/discussion_notes_spec.js | 139 +++++++++++++++++++++ .../diffs/components/diff_discussions_spec.js | 111 ++++++++-------- .../notes/components/noteable_discussion_spec.js | 23 ---- 3 files changed, 201 insertions(+), 72 deletions(-) create mode 100644 spec/frontend/notes/components/discussion_notes_spec.js (limited to 'spec') diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js new file mode 100644 index 00000000000..392c1b6533e --- /dev/null +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -0,0 +1,139 @@ +import { mount, createLocalVue } from '@vue/test-utils'; +import '~/behaviors/markdown/render_gfm'; +import { SYSTEM_NOTE } from '~/notes/constants'; +import DiscussionNotes from '~/notes/components/discussion_notes.vue'; +import NoteableNote from '~/notes/components/noteable_note.vue'; +import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; +import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; +import SystemNote from '~/vue_shared/components/notes/system_note.vue'; +import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; +import createStore from '~/notes/stores'; +import { + noteableDataMock, + discussionMock, + notesDataMock, +} from '../../../javascripts/notes/mock_data'; + +const localVue = createLocalVue(); + +describe('DiscussionNotes', () => { + let wrapper; + + const createComponent = props => { + const store = createStore(); + store.dispatch('setNoteableData', noteableDataMock); + store.dispatch('setNotesData', notesDataMock); + + wrapper = mount(DiscussionNotes, { + localVue, + store, + propsData: { + discussion: discussionMock, + isExpanded: false, + shouldGroupReplies: false, + ...props, + }, + scopedSlots: { + footer: '

      showReplies:{{showReplies}}

      ', + }, + slots: { + 'avatar-badge': '', + }, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('rendering', () => { + it('renders an element for each note in the discussion', () => { + createComponent(); + const notesCount = discussionMock.notes.length; + const els = wrapper.findAll(TimelineEntryItem); + expect(els.length).toBe(notesCount); + }); + + it('renders one element if replies groupping is enabled', () => { + createComponent({ shouldGroupReplies: true }); + const els = wrapper.findAll(TimelineEntryItem); + expect(els.length).toBe(1); + }); + + it('uses proper component to render each note type', () => { + const discussion = { ...discussionMock }; + const notesData = [ + // PlaceholderSystemNote + { + id: 1, + isPlaceholderNote: true, + placeholderType: SYSTEM_NOTE, + notes: [{ body: 'PlaceholderSystemNote' }], + }, + // PlaceholderNote + { + id: 2, + isPlaceholderNote: true, + notes: [{ body: 'PlaceholderNote' }], + }, + // SystemNote + { + id: 3, + system: true, + note: 'SystemNote', + }, + // NoteableNote + discussion.notes[0], + ]; + discussion.notes = notesData; + createComponent({ discussion }); + const notes = wrapper.findAll('.notes > li'); + + expect(notes.at(0).is(PlaceholderSystemNote)).toBe(true); + expect(notes.at(1).is(PlaceholderNote)).toBe(true); + expect(notes.at(2).is(SystemNote)).toBe(true); + expect(notes.at(3).is(NoteableNote)).toBe(true); + }); + + it('renders footer scoped slot with showReplies === true when expanded', () => { + createComponent({ isExpanded: true }); + expect(wrapper.text()).toMatch('showReplies:true'); + }); + + it('renders footer scoped slot with showReplies === false when collapsed', () => { + createComponent({ isExpanded: false }); + expect(wrapper.text()).toMatch('showReplies:false'); + }); + + it('passes down avatar-badge slot content', () => { + createComponent(); + expect(wrapper.find('.avatar-badge-slot-content').exists()).toBe(true); + }); + }); + + describe('componentData', () => { + beforeEach(() => { + createComponent(); + }); + + it('should return first note object for placeholder note', () => { + const data = { + isPlaceholderNote: true, + notes: [{ body: 'hello world!' }], + }; + const note = wrapper.vm.componentData(data); + + expect(note).toEqual(data.notes[0]); + }); + + it('should return given note for nonplaceholder notes', () => { + const data = { + notes: [{ id: 12 }], + }; + const note = wrapper.vm.componentData(data); + + expect(note).toEqual(data); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/diff_discussions_spec.js b/spec/javascripts/diffs/components/diff_discussions_spec.js index 0bc9da5ad0f..f7f0ab83c21 100644 --- a/spec/javascripts/diffs/components/diff_discussions_spec.js +++ b/spec/javascripts/diffs/components/diff_discussions_spec.js @@ -1,90 +1,103 @@ -import Vue from 'vue'; +import { mount, createLocalVue } from '@vue/test-utils'; import DiffDiscussions from '~/diffs/components/diff_discussions.vue'; +import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; +import NoteableDiscussion from '~/notes/components/noteable_discussion.vue'; +import DiscussionNotes from '~/notes/components/discussion_notes.vue'; +import Icon from '~/vue_shared/components/icon.vue'; import { createStore } from '~/mr_notes/stores'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import '~/behaviors/markdown/render_gfm'; import discussionsMockData from '../mock_data/diff_discussions'; +const localVue = createLocalVue(); + describe('DiffDiscussions', () => { - let vm; + let store; + let wrapper; const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; - function createComponent(props = {}) { - const store = createStore(); - - vm = createComponentWithStore(Vue.extend(DiffDiscussions), store, { - discussions: getDiscussionsMockData(), - ...props, - }).$mount(); - } + const createComponent = props => { + store = createStore(); + wrapper = mount(localVue.extend(DiffDiscussions), { + store, + propsData: { + discussions: getDiscussionsMockData(), + ...props, + }, + localVue, + sync: false, + }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); describe('template', () => { it('should have notes list', () => { createComponent(); - expect(vm.$el.querySelectorAll('.discussion .note.timeline-entry').length).toEqual(5); + expect(wrapper.find(NoteableDiscussion).exists()).toBe(true); + expect(wrapper.find(DiscussionNotes).exists()).toBe(true); + expect(wrapper.find(DiscussionNotes).findAll(TimelineEntryItem).length).toBe( + discussionsMockData.notes.length, + ); }); }); describe('image commenting', () => { + const findDiffNotesToggle = () => wrapper.find('.js-diff-notes-toggle'); + it('renders collapsible discussion button', () => { createComponent({ shouldCollapseDiscussions: true }); + const diffNotesToggle = findDiffNotesToggle(); - expect(vm.$el.querySelector('.js-diff-notes-toggle')).not.toBe(null); - expect(vm.$el.querySelector('.js-diff-notes-toggle svg')).not.toBe(null); - expect(vm.$el.querySelector('.js-diff-notes-toggle').classList).toContain( - 'diff-notes-collapse', - ); + expect(diffNotesToggle.exists()).toBe(true); + expect(diffNotesToggle.find(Icon).exists()).toBe(true); + expect(diffNotesToggle.classes('diff-notes-collapse')).toBe(true); }); it('dispatches toggleDiscussion when clicking collapse button', () => { createComponent({ shouldCollapseDiscussions: true }); + spyOn(wrapper.vm.$store, 'dispatch').and.stub(); + const diffNotesToggle = findDiffNotesToggle(); + diffNotesToggle.trigger('click'); - spyOn(vm.$store, 'dispatch').and.stub(); - - vm.$el.querySelector('.js-diff-notes-toggle').click(); - - expect(vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', { - discussionId: vm.discussions[0].id, + expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', { + discussionId: discussionsMockData.id, }); }); - it('renders expand button when discussion is collapsed', done => { - createComponent({ shouldCollapseDiscussions: true }); - - vm.discussions[0].expanded = false; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.js-diff-notes-toggle').textContent.trim()).toBe('1'); - expect(vm.$el.querySelector('.js-diff-notes-toggle').className).toContain( - 'btn-transparent badge badge-pill', - ); + it('renders expand button when discussion is collapsed', () => { + const discussions = getDiscussionsMockData(); + discussions[0].expanded = false; + createComponent({ discussions, shouldCollapseDiscussions: true }); + const diffNotesToggle = findDiffNotesToggle(); - done(); - }); + expect(diffNotesToggle.text().trim()).toBe('1'); + expect(diffNotesToggle.classes()).toEqual( + jasmine.arrayContaining(['btn-transparent', 'badge', 'badge-pill']), + ); }); - it('hides discussion when collapsed', done => { - createComponent({ shouldCollapseDiscussions: true }); + it('hides discussion when collapsed', () => { + const discussions = getDiscussionsMockData(); + discussions[0].expanded = false; + createComponent({ discussions, shouldCollapseDiscussions: true }); - vm.discussions[0].expanded = false; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.note-discussion').style.display).toBe('none'); - - done(); - }); + expect(wrapper.find(NoteableDiscussion).isVisible()).toBe(false); }); it('renders badge on avatar', () => { - createComponent({ renderAvatarBadge: true, discussions: [{ ...discussionsMockData }] }); - - expect(vm.$el.querySelector('.user-avatar-link .badge-pill')).not.toBe(null); - expect(vm.$el.querySelector('.user-avatar-link .badge-pill').textContent.trim()).toBe('1'); + createComponent({ renderAvatarBadge: true }); + const noteableDiscussion = wrapper.find(NoteableDiscussion); + + expect(noteableDiscussion.find('.badge-pill').exists()).toBe(true); + expect( + noteableDiscussion + .find('.badge-pill') + .text() + .trim(), + ).toBe('1'); }); }); }); diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index 3304c79cdb7..efa864e7d00 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -130,29 +130,6 @@ describe('noteable_discussion component', () => { }); }); - describe('componentData', () => { - it('should return first note object for placeholder note', () => { - const data = { - isPlaceholderNote: true, - notes: [{ body: 'hello world!' }], - }; - - const note = wrapper.vm.componentData(data); - - expect(note).toEqual(data.notes[0]); - }); - - it('should return given note for nonplaceholder notes', () => { - const data = { - notes: [{ id: 12 }], - }; - - const note = wrapper.vm.componentData(data); - - expect(note).toEqual(data); - }); - }); - describe('action text', () => { const commitId = 'razupaltuff'; const truncatedCommitId = commitId.substr(0, 8); -- cgit v1.2.1 From 552a3d2fd939d3f8a56cfad9fad62227c1d5aa89 Mon Sep 17 00:00:00 2001 From: Sarah Yasonik Date: Wed, 1 May 2019 10:16:03 +0000 Subject: Update metrics dashboard API to load yml from repo Updates the EnvironmentController#metrics_dashboard endpoint to support a "dashboard" param, which can be used to specify the filepath of a dashboard configuration from a project repository. Dashboard configurations are expected to be stored in .gitlab/dashboards/. Updates dashboard post-processing steps to exclude custom metrics, which should only display on the system dashboard. --- .../projects/environments_controller_spec.rb | 91 ++++++++++++++++++++-- .../gitlab/metrics/dashboard/sample_dashboard.yml | 4 +- spec/lib/gitlab/metrics/dashboard/finder_spec.rb | 62 +++++++++++++++ .../lib/gitlab/metrics/dashboard/processor_spec.rb | 21 +++-- .../dashboard/project_dashboard_service_spec.rb | 62 +++++++++++++++ spec/lib/gitlab/metrics/dashboard/service_spec.rb | 42 ---------- .../dashboard/system_dashboard_service_spec.rb | 32 ++++++++ spec/models/repository_spec.rb | 1 + spec/support/helpers/metrics_dashboard_helpers.rb | 43 ++++++++++ 9 files changed, 302 insertions(+), 56 deletions(-) create mode 100644 spec/lib/gitlab/metrics/dashboard/finder_spec.rb create mode 100644 spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb delete mode 100644 spec/lib/gitlab/metrics/dashboard/service_spec.rb create mode 100644 spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb create mode 100644 spec/support/helpers/metrics_dashboard_helpers.rb (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index a62422d0229..cf23d937037 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -474,25 +474,102 @@ describe Projects::EnvironmentsController do end end - context 'when prometheus endpoint is enabled' do + shared_examples_for '200 response' do |contains_all_dashboards: false| + let(:expected_keys) { %w(dashboard status) } + + before do + expected_keys << 'all_dashboards' if contains_all_dashboards + end + it 'returns a json representation of the environment dashboard' do - get :metrics_dashboard, params: environment_params(format: :json) + get :metrics_dashboard, params: environment_params(dashboard_params) expect(response).to have_gitlab_http_status(:ok) - expect(json_response.keys).to contain_exactly('dashboard', 'status') + expect(json_response.keys).to contain_exactly(*expected_keys) expect(json_response['dashboard']).to be_an_instance_of(Hash) end + end + + shared_examples_for 'error response' do |status_code, contains_all_dashboards: false| + let(:expected_keys) { %w(message status) } + + before do + expected_keys << 'all_dashboards' if contains_all_dashboards + end + + it 'returns an error response' do + get :metrics_dashboard, params: environment_params(dashboard_params) + + expect(response).to have_gitlab_http_status(status_code) + expect(json_response.keys).to contain_exactly(*expected_keys) + end + end + + shared_examples_for 'has all dashboards' do + it 'includes an index of all available dashboards' do + get :metrics_dashboard, params: environment_params(dashboard_params) + + expect(json_response.keys).to include('all_dashboards') + expect(json_response['all_dashboards']).to be_an_instance_of(Array) + expect(json_response['all_dashboards']).to all( include('path', 'default') ) + end + end + + context 'when multiple dashboards is disabled' do + before do + stub_feature_flags(environment_metrics_show_multiple_dashboards: false) + end + + let(:dashboard_params) { { format: :json } } + + it_behaves_like '200 response' context 'when the dashboard could not be provided' do before do allow(YAML).to receive(:safe_load).and_return({}) end - it 'returns an error response' do - get :metrics_dashboard, params: environment_params(format: :json) + it_behaves_like 'error response', :unprocessable_entity + end + + context 'when a dashboard param is specified' do + let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } } + + it_behaves_like '200 response' + end + end + + context 'when multiple dashboards is enabled' do + let(:dashboard_params) { { format: :json } } + + it_behaves_like '200 response', contains_all_dashboards: true + it_behaves_like 'has all dashboards' + + context 'when a dashboard could not be provided' do + before do + allow(YAML).to receive(:safe_load).and_return({}) + end + + it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true + it_behaves_like 'has all dashboards' + end + + context 'when a dashboard param is specified' do + let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } } + + context 'when the dashboard is available' do + let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') } + let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } } + let(:project) { create(:project, :custom_repo, files: dashboard_file) } + let(:environment) { create(:environment, name: 'production', project: project) } + + it_behaves_like '200 response', contains_all_dashboards: true + it_behaves_like 'has all dashboards' + end - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response.keys).to contain_exactly('message', 'status', 'http_status') + context 'when the dashboard does not exist' do + it_behaves_like 'error response', :not_found, contains_all_dashboards: true + it_behaves_like 'has all dashboards' end end end diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml index c2d3d3d8aca..638ecbcc11f 100644 --- a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml +++ b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml @@ -2,7 +2,7 @@ dashboard: 'Test Dashboard' priority: 1 panel_groups: - group: Group A - priority: 10 + priority: 1 panels: - title: "Super Chart A1" type: "area-chart" @@ -23,7 +23,7 @@ panel_groups: label: Legend Label unit: unit - group: Group B - priority: 1 + priority: 10 panels: - title: "Super Chart B" type: "area-chart" diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb new file mode 100644 index 00000000000..e88eb140b35 --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:project) { build(:project) } + set(:environment) { build(:environment, project: project) } + let(:system_dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH} + + describe '.find' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:service_call) { described_class.find(project, nil, environment, dashboard_path) } + + it_behaves_like 'misconfigured dashboard service response', :not_found + + context 'when the dashboard exists' do + let(:project) { project_with_dashboard(dashboard_path) } + + it_behaves_like 'valid dashboard service response' + end + + context 'when the dashboard is configured incorrectly' do + let(:project) { project_with_dashboard(dashboard_path, {}) } + + it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity + end + + context 'when the system dashboard is specified' do + let(:dashboard_path) { system_dashboard_path } + + it_behaves_like 'valid dashboard service response' + end + + context 'when no dashboard is specified' do + let(:service_call) { described_class.find(project, nil, environment) } + + it_behaves_like 'valid dashboard service response' + end + end + + describe '.find_all_paths' do + let(:all_dashboard_paths) { described_class.find_all_paths(project) } + let(:system_dashboard) { { path: system_dashboard_path, default: true } } + + it 'includes only the system dashboard by default' do + expect(all_dashboard_paths).to eq([system_dashboard]) + end + + context 'when the project contains dashboards' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:project) { project_with_dashboard(dashboard_path) } + + it 'includes system and project dashboards' do + project_dashboard = { path: dashboard_path, default: false } + + expect(all_dashboard_paths).to contain_exactly(system_dashboard, project_dashboard) + end + end + end +end diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb index ee7c93fce8d..be3c1095bd7 100644 --- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb @@ -4,12 +4,12 @@ require 'spec_helper' describe Gitlab::Metrics::Dashboard::Processor do let(:project) { build(:project) } - let(:environment) { build(:environment) } + let(:environment) { build(:environment, project: project) } let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') } describe 'process' do let(:process_params) { [project, environment, dashboard_yml] } - let(:dashboard) { described_class.new(*process_params).process } + let(:dashboard) { described_class.new(*process_params).process(insert_project_metrics: true) } context 'when dashboard config corresponds to common metrics' do let!(:common_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') } @@ -35,9 +35,9 @@ describe Gitlab::Metrics::Dashboard::Processor do it 'orders groups by priority and panels by weight' do expected_metrics_order = [ - 'metric_a2', # group priority 10, panel weight 2 - 'metric_a1', # group priority 10, panel weight 1 - 'metric_b', # group priority 1, panel weight 1 + 'metric_b', # group priority 10, panel weight 1 + 'metric_a2', # group priority 1, panel weight 2 + 'metric_a1', # group priority 1, panel weight 1 project_business_metric.id, # group priority 0, panel weight nil (0) project_response_metric.id, # group priority -5, panel weight nil (0) project_system_metric.id, # group priority -10, panel weight nil (0) @@ -46,6 +46,17 @@ describe Gitlab::Metrics::Dashboard::Processor do expect(actual_metrics_order).to eq expected_metrics_order end + + context 'when the dashboard should not include project metrics' do + let(:dashboard) { described_class.new(*process_params).process(insert_project_metrics: false) } + + it 'includes only dashboard metrics' do + metrics = all_metrics.map { |m| m[:id] } + + expect(metrics.length).to be(3) + expect(metrics).to eq %w(metric_b metric_a2 metric_a1) + end + end end shared_examples_for 'errors with message' do |expected_message| diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb new file mode 100644 index 00000000000..162beb0268a --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:user) { build(:user) } + set(:project) { build(:project) } + set(:environment) { build(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + describe 'get_dashboard' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] } + let(:service_call) { described_class.new(*service_params).get_dashboard } + + context 'when the dashboard does not exist' do + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + context 'when the dashboard exists' do + let(:project) { project_with_dashboard(dashboard_path) } + + it_behaves_like 'valid dashboard service response' + + it 'caches the unprocessed dashboard for subsequent calls' do + expect_any_instance_of(described_class) + .to receive(:get_raw_dashboard) + .once + .and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + + context 'and the dashboard is then deleted' do + it 'does not return the previously cached dashboard' do + described_class.new(*service_params).get_dashboard + + delete_project_dashboard(project, user, dashboard_path) + + expect_any_instance_of(described_class) + .to receive(:get_raw_dashboard) + .once + .and_call_original + + described_class.new(*service_params).get_dashboard + end + end + end + + context 'when the dashboard is configured incorrectly' do + let(:project) { project_with_dashboard(dashboard_path, {}) } + + it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity + end + end +end diff --git a/spec/lib/gitlab/metrics/dashboard/service_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_spec.rb deleted file mode 100644 index e66c356bf49..00000000000 --- a/spec/lib/gitlab/metrics/dashboard/service_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Metrics::Dashboard::Service, :use_clean_rails_memory_store_caching do - let(:project) { build(:project) } - let(:environment) { build(:environment) } - - describe 'get_dashboard' do - let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/dashboard.json')) } - - it 'returns a json representation of the environment dashboard' do - result = described_class.new(project, environment).get_dashboard - - expect(result.keys).to contain_exactly(:dashboard, :status) - expect(result[:status]).to eq(:success) - - expect(JSON::Validator.fully_validate(dashboard_schema, result[:dashboard])).to be_empty - end - - it 'caches the dashboard for subsequent calls' do - expect(YAML).to receive(:safe_load).once.and_call_original - - described_class.new(project, environment).get_dashboard - described_class.new(project, environment).get_dashboard - end - - context 'when the dashboard is configured incorrectly' do - before do - allow(YAML).to receive(:safe_load).and_return({}) - end - - it 'returns an appropriate message and status code' do - result = described_class.new(project, environment).get_dashboard - - expect(result.keys).to contain_exactly(:message, :http_status, :status) - expect(result[:status]).to eq(:error) - expect(result[:http_status]).to eq(:unprocessable_entity) - end - end - end -end diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb new file mode 100644 index 00000000000..e71ce2481a3 --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:project) { build(:project) } + set(:environment) { build(:environment, project: project) } + + describe 'get_dashboard' do + let(:dashboard_path) { described_class::SYSTEM_DASHBOARD_PATH } + let(:service_params) { [project, nil, { environment: environment, dashboard_path: dashboard_path }] } + let(:service_call) { described_class.new(*service_params).get_dashboard } + + it_behaves_like 'valid dashboard service response' + + it 'caches the unprocessed dashboard for subsequent calls' do + expect(YAML).to receive(:safe_load).once.and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + + context 'when called with a non-system dashboard' do + let(:dashboard_path) { 'garbage/dashboard/path' } + + # We want to alwaus return the system dashboard. + it_behaves_like 'valid dashboard service response' + end + end +end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 43ec1125087..a6c3d5756aa 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1637,6 +1637,7 @@ describe Repository do :has_visible_content?, :issue_template_names, :merge_request_template_names, + :metrics_dashboard_paths, :xcode_project? ]) diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb new file mode 100644 index 00000000000..1f36b0e217c --- /dev/null +++ b/spec/support/helpers/metrics_dashboard_helpers.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module MetricsDashboardHelpers + def project_with_dashboard(dashboard_path, dashboard_yml = nil) + dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') + + create(:project, :custom_repo, files: { dashboard_path => dashboard_yml }) + end + + def delete_project_dashboard(project, user, dashboard_path) + project.repository.delete_file( + user, + dashboard_path, + branch_name: 'master', + message: 'Delete dashboard' + ) + + project.repository.refresh_method_caches([:metrics_dashboard]) + end + + shared_examples_for 'misconfigured dashboard service response' do |status_code| + it 'returns an appropriate message and status code' do + result = service_call + + expect(result.keys).to contain_exactly(:message, :http_status, :status) + expect(result[:status]).to eq(:error) + expect(result[:http_status]).to eq(status_code) + end + end + + shared_examples_for 'valid dashboard service response' do + let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/dashboard.json')) } + + it 'returns a json representation of the dashboard' do + result = service_call + + expect(result.keys).to contain_exactly(:dashboard, :status) + expect(result[:status]).to eq(:success) + + expect(JSON::Validator.fully_validate(dashboard_schema, result[:dashboard])).to be_empty + end + end +end -- cgit v1.2.1 From 3e3bead22bddc65963319e38926bae4c2c3a0eab Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Wed, 10 Apr 2019 12:33:30 -0500 Subject: Make time window parameters available in the query string This commit adds the frontend functionality to add the parameters via the `pushState` api, preventing the need for a page reload. --- spec/javascripts/monitoring/dashboard_spec.js | 37 ++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 16dc0084a10..5c28840d3a4 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; -import { timeWindows } from '~/monitoring/constants'; +import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; import axios from '~/lib/utils/axios_utils'; import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; @@ -248,6 +248,41 @@ describe('Dashboard', () => { done(); }); }); + + it('shows a specific time window selected from the url params', done => { + spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']); + + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + }); + + setTimeout(() => { + const selectedTimeWindow = component.$el.querySelector( + '.js-time-window-dropdown [active="true"]', + ); + + expect(selectedTimeWindow.textContent.trim()).toEqual('30 minutes'); + done(); + }); + }); + + it('defaults to the eight hours time window for non valid url parameters', done => { + spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([ + '', + ]); + + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + }); + + Vue.nextTick(() => { + expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours); + + done(); + }); + }); }); describe('when the window resizes', () => { -- cgit v1.2.1 From 6627efd6b48e70a572149865bd4196e6f711f9de Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 1 May 2019 15:23:11 -0300 Subject: Allow Sentry client-side DSN to be passed on gitlab.yml --- spec/support/shared_examples/application_setting_examples.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb index d8f7ba1185e..421303c97be 100644 --- a/spec/support/shared_examples/application_setting_examples.rb +++ b/spec/support/shared_examples/application_setting_examples.rb @@ -260,6 +260,7 @@ RSpec.shared_examples 'application settings examples' do allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) allow(Gitlab.config.sentry).to receive(:dsn).and_return(nil) + allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(nil) expect(setting.sentry_enabled).to eq true expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' @@ -277,12 +278,13 @@ RSpec.shared_examples 'application settings examples' do allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) allow(Gitlab.config.sentry).to receive(:dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42') + allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43') expect(setting).not_to receive(:read_attribute) expect(setting.sentry_enabled).to eq true expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' expect(setting.clientside_sentry_enabled).to eq true - expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' + expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43' end end end -- cgit v1.2.1 From ecc2527032f4e8d6caa66d51fcea22437dd88ab8 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Wed, 1 May 2019 16:13:08 -0500 Subject: Add close_select2 method to select2_helper **Why?** This is needed to create a feature test for: https://gitlab.com/gitlab-org/gitlab-ee/issues/11194 --- spec/support/helpers/select2_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb index f4f0415985c..87672c8896d 100644 --- a/spec/support/helpers/select2_helper.rb +++ b/spec/support/helpers/select2_helper.rb @@ -35,6 +35,10 @@ module Select2Helper execute_script("$('#{selector}').select2('open');") end + def close_select2(selector) + execute_script("$('#{selector}').select2('close');") + end + def scroll_select2_to_bottom(selector) evaluate_script "$('#{selector}').scrollTop($('#{selector}')[0].scrollHeight); $('#{selector}');" end -- cgit v1.2.1 From 5faa98f481e0f5e0ccb1758c34a104f523ab21d2 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 24 Apr 2019 17:50:00 +0700 Subject: Session stored globally per request - This can be accessed with Session.current and is restored after. - Data can be stored under a key with NamespacedSessionStore --- spec/lib/gitlab/namespaced_session_store_spec.rb | 22 +++++++++++++++++++ spec/lib/gitlab/session_spec.rb | 27 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 spec/lib/gitlab/namespaced_session_store_spec.rb create mode 100644 spec/lib/gitlab/session_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/namespaced_session_store_spec.rb b/spec/lib/gitlab/namespaced_session_store_spec.rb new file mode 100644 index 00000000000..c0af2ede32a --- /dev/null +++ b/spec/lib/gitlab/namespaced_session_store_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::NamespacedSessionStore do + let(:key) { :some_key } + subject { described_class.new(key) } + + it 'stores data under the specified key' do + Gitlab::Session.with_session({}) do + subject[:new_data] = 123 + + expect(Thread.current[:session_storage][key]).to eq(new_data: 123) + end + end + + it 'retrieves data from the given key' do + Thread.current[:session_storage] = { key => { existing_data: 123 } } + + expect(subject[:existing_data]).to eq 123 + end +end diff --git a/spec/lib/gitlab/session_spec.rb b/spec/lib/gitlab/session_spec.rb new file mode 100644 index 00000000000..8db73f0ec7b --- /dev/null +++ b/spec/lib/gitlab/session_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Session do + it 'uses the current thread as a data store' do + Thread.current[:session_storage] = { a: :b } + + expect(described_class.current).to eq(a: :b) + ensure + Thread.current[:session_storage] = nil + end + + describe '#with_session' do + it 'sets session hash' do + described_class.with_session(one: 1) do + expect(described_class.current).to eq(one: 1) + end + end + + it 'restores current store after' do + described_class.with_session(two: 2) { } + + expect(described_class.current).to eq nil + end + end +end -- cgit v1.2.1 From 96750fac4c4060d0a9e321039cab3dcf52a31105 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 2 May 2019 07:01:14 +0000 Subject: Add opentracing integration for graphql Extends existing graphql's tracer with opentracing measurements. Because it also adds Tracing::Graphql class (for opentracing), it also renames Graphql::Tracing class to Graphql::GenericTracing to minimize confusion with similar class names. --- spec/lib/gitlab/graphql/generic_tracing_spec.rb | 67 +++++++++++++++++++++++++ spec/lib/gitlab/graphql/tracing_spec.rb | 33 ------------ 2 files changed, 67 insertions(+), 33 deletions(-) create mode 100644 spec/lib/gitlab/graphql/generic_tracing_spec.rb delete mode 100644 spec/lib/gitlab/graphql/tracing_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/generic_tracing_spec.rb b/spec/lib/gitlab/graphql/generic_tracing_spec.rb new file mode 100644 index 00000000000..ae92dcc40af --- /dev/null +++ b/spec/lib/gitlab/graphql/generic_tracing_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::GenericTracing do + let(:graphql_duration_seconds_histogram) { double('Gitlab::Metrics::NullMetric') } + + it 'updates graphql histogram with expected labels' do + query = 'query { users { id } }' + tracer = described_class.new + + allow(tracer) + .to receive(:graphql_duration_seconds) + .and_return(graphql_duration_seconds_histogram) + + expect_metric('graphql.lex', 'lex') + expect_metric('graphql.parse', 'parse') + expect_metric('graphql.validate', 'validate') + expect_metric('graphql.analyze', 'analyze_multiplex') + expect_metric('graphql.execute', 'execute_query_lazy') + expect_metric('graphql.execute', 'execute_multiplex') + + GitlabSchema.execute(query, context: { tracers: [tracer] }) + end + + context "when labkit tracing is enabled" do + before do + expect(Labkit::Tracing).to receive(:enabled?).and_return(true) + end + + it 'yields with labkit tracing' do + expected_tags = { + 'component' => 'web', + 'span.kind' => 'server', + 'platform_key' => 'pkey', + 'key' => 'key' + } + + expect(Labkit::Tracing) + .to receive(:with_tracing) + .with(operation_name: "pkey.key", tags: expected_tags) + .and_yield + + expect { |b| described_class.new.platform_trace('pkey', 'key', nil, &b) }.to yield_control + end + end + + context "when labkit tracing is disabled" do + before do + expect(Labkit::Tracing).to receive(:enabled?).and_return(false) + end + + it 'yields without measurement' do + expect(Labkit::Tracing).not_to receive(:with_tracing) + + expect { |b| described_class.new.platform_trace('pkey', 'key', nil, &b) }.to yield_control + end + end + + private + + def expect_metric(platform_key, key) + expect(graphql_duration_seconds_histogram) + .to receive(:observe) + .with({ platform_key: platform_key, key: key }, be > 0.0) + end +end diff --git a/spec/lib/gitlab/graphql/tracing_spec.rb b/spec/lib/gitlab/graphql/tracing_spec.rb deleted file mode 100644 index 7300a9a572e..00000000000 --- a/spec/lib/gitlab/graphql/tracing_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Graphql::Tracing do - let(:graphql_duration_seconds_histogram) { double('Gitlab::Metrics::NullMetric') } - - it 'updates graphql histogram with expected labels' do - query = 'query { users { id } }' - tracer = described_class.new - - allow(tracer) - .to receive(:graphql_duration_seconds) - .and_return(graphql_duration_seconds_histogram) - - expect_metric('graphql.lex', 'lex') - expect_metric('graphql.parse', 'parse') - expect_metric('graphql.validate', 'validate') - expect_metric('graphql.analyze', 'analyze_multiplex') - expect_metric('graphql.execute', 'execute_query_lazy') - expect_metric('graphql.execute', 'execute_multiplex') - - GitlabSchema.execute(query, context: { tracers: [tracer] }) - end - - private - - def expect_metric(platform_key, key) - expect(graphql_duration_seconds_histogram) - .to receive(:observe) - .with({ platform_key: platform_key, key: key }, be > 0.0) - end -end -- cgit v1.2.1 From 2fcf80de3f153c928245446460daf2bf6bdfa7f1 Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Tue, 30 Apr 2019 13:10:39 +0100 Subject: Strip whitespace for PipelineSchedule#cron --- spec/models/ci/pipeline_schedule_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 81913f4a3b6..1bfc14d2839 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -35,6 +35,15 @@ describe Ci::PipelineSchedule do expect(pipeline_schedule).not_to be_valid end end + + context 'when cron contains trailing whitespaces' do + it 'strips the attribute' do + pipeline_schedule = build(:ci_pipeline_schedule, cron: ' 0 0 * * * ') + + expect(pipeline_schedule).to be_valid + expect(pipeline_schedule.cron).to eq('0 0 * * *') + end + end end describe '#set_next_run_at' do -- cgit v1.2.1 From c1892f6c9000cacafae4f6c8992ba6c1128c8c95 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 2 May 2019 11:13:42 +0100 Subject: Remove the `comment_personal_snippet` permission This is now entirely handled by `create_note`: 1. Project snippets prevent `create_note`. 2. Uploads already only support routing for personal snippets. This simplifies some policies and access checks, too! --- spec/policies/personal_snippet_policy_spec.rb | 31 +++++++++++---------------- spec/routing/uploads_routing_spec.rb | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 spec/routing/uploads_routing_spec.rb (limited to 'spec') diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb index a38e0dbd797..097000ceb6a 100644 --- a/spec/policies/personal_snippet_policy_spec.rb +++ b/spec/policies/personal_snippet_policy_spec.rb @@ -14,13 +14,6 @@ describe PersonalSnippetPolicy do ] end - let(:comment_permissions) do - [ - :comment_personal_snippet, - :create_note - ] - end - def permissions(user) described_class.new(user, snippet) end @@ -33,7 +26,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_disallowed(*comment_permissions) + is_expected.to be_disallowed(:create_note) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -44,7 +37,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(*comment_permissions) + is_expected.to be_allowed(:create_note) is_expected.to be_allowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -55,7 +48,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(*comment_permissions) + is_expected.to be_allowed(:create_note) is_expected.to be_allowed(:award_emoji) is_expected.to be_allowed(*author_permissions) end @@ -70,7 +63,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(*comment_permissions) + is_expected.to be_disallowed(:create_note) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -81,7 +74,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(*comment_permissions) + is_expected.to be_allowed(:create_note) is_expected.to be_allowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -92,7 +85,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(*comment_permissions) + is_expected.to be_disallowed(:create_note) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -103,7 +96,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(*comment_permissions) + is_expected.to be_allowed(:create_note) is_expected.to be_allowed(:award_emoji) is_expected.to be_allowed(*author_permissions) end @@ -118,7 +111,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(*comment_permissions) + is_expected.to be_disallowed(:create_note) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -129,7 +122,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(*comment_permissions) + is_expected.to be_disallowed(:create_note) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -140,7 +133,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(:create_note) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -151,7 +144,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_disallowed(:read_personal_snippet) - is_expected.to be_disallowed(*comment_permissions) + is_expected.to be_disallowed(:create_note) is_expected.to be_disallowed(:award_emoji) is_expected.to be_disallowed(*author_permissions) end @@ -162,7 +155,7 @@ describe PersonalSnippetPolicy do it do is_expected.to be_allowed(:read_personal_snippet) - is_expected.to be_allowed(*comment_permissions) + is_expected.to be_allowed(:create_note) is_expected.to be_allowed(:award_emoji) is_expected.to be_allowed(*author_permissions) end diff --git a/spec/routing/uploads_routing_spec.rb b/spec/routing/uploads_routing_spec.rb new file mode 100644 index 00000000000..6a041ffdd6c --- /dev/null +++ b/spec/routing/uploads_routing_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Uploads', 'routing' do + it 'allows creating uploads for personal snippets' do + expect(post('/uploads/personal_snippet?id=1')).to route_to( + controller: 'uploads', + action: 'create', + model: 'personal_snippet', + id: '1' + ) + end + + it 'does not allow creating uploads for other models' do + UploadsController::MODEL_CLASSES.keys.compact.each do |model| + next if model == 'personal_snippet' + + expect(post("/uploads/#{model}?id=1")).not_to be_routable + end + end +end -- cgit v1.2.1 From bf229a6c632709ef7c69a7033eadfd74f5ddcc97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Alc=C3=A1ntara?= Date: Thu, 2 May 2019 14:17:03 +0000 Subject: Uninstall application confirm modal component - Vue confirmation modal implementation - CSS tweaks for modal default height --- spec/frontend/clusters/clusters_bundle_spec.js | 82 +++++------ .../clusters/components/application_row_spec.js | 158 +++++++++++++++------ .../uninstall_application_button_spec.js | 32 +++++ ...ninstall_application_confirmation_modal_spec.js | 47 ++++++ .../services/application_state_machine_spec.js | 54 +++++-- spec/frontend/clusters/services/mock_data.js | 7 + .../clusters/stores/clusters_store_spec.js | 14 ++ 7 files changed, 291 insertions(+), 103 deletions(-) create mode 100644 spec/frontend/clusters/components/uninstall_application_button_spec.js create mode 100644 spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js (limited to 'spec') diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index a61103397eb..73897107f67 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -1,12 +1,12 @@ import Clusters from '~/clusters/clusters_bundle'; -import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX } from '~/clusters/constants'; +import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, APPLICATIONS } from '~/clusters/constants'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import { loadHTMLFixture } from 'helpers/fixtures'; import { setTestTimeout } from 'helpers/timeout'; import $ from 'jquery'; -const { INSTALLING, INSTALLABLE, INSTALLED } = APPLICATION_STATUS; +const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS; describe('Clusters', () => { setTestTimeout(1000); @@ -212,73 +212,61 @@ describe('Clusters', () => { }); describe('installApplication', () => { - it('tries to install helm', () => { + it.each(APPLICATIONS)('tries to install %s', applicationId => { jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - cluster.store.state.applications.helm.status = INSTALLABLE; + cluster.store.state.applications[applicationId].status = INSTALLABLE; - cluster.installApplication({ id: 'helm' }); + cluster.installApplication({ id: applicationId }); - expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined); + expect(cluster.store.state.applications[applicationId].status).toEqual(INSTALLING); + expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith(applicationId, undefined); }); - it('tries to install ingress', () => { - jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - - cluster.store.state.applications.ingress.status = INSTALLABLE; - - cluster.installApplication({ id: 'ingress' }); - - expect(cluster.store.state.applications.ingress.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); - }); + it('sets error request status when the request fails', () => { + jest + .spyOn(cluster.service, 'installApplication') + .mockRejectedValueOnce(new Error('STUBBED ERROR')); - it('tries to install runner', () => { - jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + cluster.store.state.applications.helm.status = INSTALLABLE; - cluster.store.state.applications.runner.status = INSTALLABLE; + const promise = cluster.installApplication({ id: 'helm' }); - cluster.installApplication({ id: 'runner' }); + return promise.then(() => { + expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE); + expect(cluster.store.state.applications.helm.installFailed).toBe(true); - expect(cluster.store.state.applications.runner.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.runner.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); + expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); + }); }); + }); - it('tries to install jupyter', () => { - jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + describe('uninstallApplication', () => { + it.each(APPLICATIONS)('tries to uninstall %s', applicationId => { + jest.spyOn(cluster.service, 'uninstallApplication').mockResolvedValueOnce(); - cluster.installApplication({ - id: 'jupyter', - params: { hostname: cluster.store.state.applications.jupyter.hostname }, - }); + cluster.store.state.applications[applicationId].status = INSTALLED; - cluster.store.state.applications.jupyter.status = INSTALLABLE; - expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { - hostname: cluster.store.state.applications.jupyter.hostname, - }); + cluster.uninstallApplication({ id: applicationId }); + + expect(cluster.store.state.applications[applicationId].status).toEqual(UNINSTALLING); + expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null); + expect(cluster.service.uninstallApplication).toHaveBeenCalledWith(applicationId); }); - it('sets error request status when the request fails', () => { + it('sets error request status when the uninstall request fails', () => { jest - .spyOn(cluster.service, 'installApplication') + .spyOn(cluster.service, 'uninstallApplication') .mockRejectedValueOnce(new Error('STUBBED ERROR')); - cluster.store.state.applications.helm.status = INSTALLABLE; + cluster.store.state.applications.helm.status = INSTALLED; - const promise = cluster.installApplication({ id: 'helm' }); - - expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalled(); + const promise = cluster.uninstallApplication({ id: 'helm' }); return promise.then(() => { - expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE); - expect(cluster.store.state.applications.helm.installFailed).toBe(true); + expect(cluster.store.state.applications.helm.status).toEqual(INSTALLED); + expect(cluster.store.state.applications.helm.uninstallFailed).toBe(true); expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); }); diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js index 17273b7d5b1..7c781b72355 100644 --- a/spec/frontend/clusters/components/application_row_spec.js +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -1,7 +1,10 @@ import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; import eventHub from '~/clusters/event_hub'; import { APPLICATION_STATUS } from '~/clusters/constants'; import applicationRow from '~/clusters/components/application_row.vue'; +import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue'; + import mountComponent from 'helpers/vue_mount_component_helper'; import { DEFAULT_APPLICATION_STATE } from '../services/mock_data'; @@ -194,11 +197,52 @@ describe('Application Row', () => { ...DEFAULT_APPLICATION_STATE, installed: true, uninstallable: true, + status: APPLICATION_STATUS.NOT_INSTALLABLE, }); const uninstallButton = vm.$el.querySelector('.js-cluster-application-uninstall-button'); expect(uninstallButton).toBeTruthy(); }); + + it('displays a success toast message if application uninstall was successful', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + title: 'GitLab Runner', + uninstallSuccessful: false, + }); + + vm.$toast = { show: jest.fn() }; + vm.uninstallSuccessful = true; + + return vm.$nextTick(() => { + expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner uninstalled successfully.'); + }); + }); + }); + + describe('when confirmation modal triggers confirm event', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallowMount(ApplicationRow, { + propsData: { + ...DEFAULT_APPLICATION_STATE, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('triggers uninstallApplication event', () => { + jest.spyOn(eventHub, '$emit'); + wrapper.find(UninstallApplicationConfirmationModal).vm.$emit('confirm'); + + expect(eventHub.$emit).toHaveBeenCalledWith('uninstallApplication', { + id: DEFAULT_APPLICATION_STATE.id, + }); + }); }); describe('Upgrade button', () => { @@ -304,7 +348,7 @@ describe('Application Row', () => { vm.$toast = { show: jest.fn() }; vm.updateSuccessful = true; - vm.$nextTick(() => { + return vm.$nextTick(() => { expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.'); }); }); @@ -360,60 +404,88 @@ describe('Application Row', () => { }); describe('Error block', () => { - it('does not show error block when there is no error', () => { - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: null, - }); - const generalErrorMessage = vm.$el.querySelector( - '.js-cluster-application-general-error-message', - ); + describe('when nothing fails', () => { + it('does not show error block', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + }); + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); - expect(generalErrorMessage).toBeNull(); + expect(generalErrorMessage).toBeNull(); + }); }); - it('shows status reason when install fails', () => { + describe('when install or uninstall fails', () => { const statusReason = 'We broke it 0.0'; - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.ERROR, - statusReason, - installFailed: true, + const requestReason = 'We broke the request 0.0'; + + beforeEach(() => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.ERROR, + statusReason, + requestReason, + installFailed: true, + }); }); - const generalErrorMessage = vm.$el.querySelector( - '.js-cluster-application-general-error-message', - ); - const statusErrorMessage = vm.$el.querySelector( - '.js-cluster-application-status-error-message', - ); - expect(generalErrorMessage.textContent.trim()).toEqual( - `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, - ); + it('shows status reason if it is available', () => { + const statusErrorMessage = vm.$el.querySelector( + '.js-cluster-application-status-error-message', + ); + + expect(statusErrorMessage.textContent.trim()).toEqual(statusReason); + }); + + it('shows request reason if it is available', () => { + const requestErrorMessage = vm.$el.querySelector( + '.js-cluster-application-request-error-message', + ); + + expect(requestErrorMessage.textContent.trim()).toEqual(requestReason); + }); + }); + + describe('when install fails', () => { + beforeEach(() => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.ERROR, + installFailed: true, + }); + }); - expect(statusErrorMessage.textContent.trim()).toEqual(statusReason); + it('shows a general message indicating the installation failed', () => { + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); + + expect(generalErrorMessage.textContent.trim()).toEqual( + `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, + ); + }); }); - it('shows request reason when REQUEST_FAILURE', () => { - const requestReason = 'We broke thre request 0.0'; - vm = mountComponent(ApplicationRow, { - ...DEFAULT_APPLICATION_STATE, - status: APPLICATION_STATUS.INSTALLABLE, - installFailed: true, - requestReason, + describe('when uninstall fails', () => { + beforeEach(() => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.ERROR, + uninstallFailed: true, + }); }); - const generalErrorMessage = vm.$el.querySelector( - '.js-cluster-application-general-error-message', - ); - const requestErrorMessage = vm.$el.querySelector( - '.js-cluster-application-request-error-message', - ); - expect(generalErrorMessage.textContent.trim()).toEqual( - `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, - ); + it('shows a general message indicating the uninstalling failed', () => { + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); - expect(requestErrorMessage.textContent.trim()).toEqual(requestReason); + expect(generalErrorMessage.textContent.trim()).toEqual( + `Something went wrong while uninstalling ${DEFAULT_APPLICATION_STATE.title}`, + ); + }); }); }); }); diff --git a/spec/frontend/clusters/components/uninstall_application_button_spec.js b/spec/frontend/clusters/components/uninstall_application_button_spec.js new file mode 100644 index 00000000000..9f9397d4d41 --- /dev/null +++ b/spec/frontend/clusters/components/uninstall_application_button_spec.js @@ -0,0 +1,32 @@ +import { shallowMount } from '@vue/test-utils'; +import UninstallApplicationButton from '~/clusters/components/uninstall_application_button.vue'; +import LoadingButton from '~/vue_shared/components/loading_button.vue'; +import { APPLICATION_STATUS } from '~/clusters/constants'; + +const { INSTALLED, UPDATING, UNINSTALLING } = APPLICATION_STATUS; + +describe('UninstallApplicationButton', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(UninstallApplicationButton, { + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe.each` + status | loading | disabled | label + ${INSTALLED} | ${false} | ${false} | ${'Uninstall'} + ${UPDATING} | ${false} | ${true} | ${'Uninstall'} + ${UNINSTALLING} | ${true} | ${true} | ${'Uninstalling'} + `('when app status is $status', ({ loading, disabled, status, label }) => { + it(`renders a loading=${loading}, disabled=${disabled} button with label="${label}"`, () => { + createComponent({ status }); + expect(wrapper.find(LoadingButton).props()).toMatchObject({ loading, disabled, label }); + }); + }); +}); diff --git a/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js new file mode 100644 index 00000000000..6a7126b45cd --- /dev/null +++ b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; +import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue'; +import { GlModal } from '@gitlab/ui'; +import { INGRESS } from '~/clusters/constants'; + +describe('UninstallApplicationConfirmationModal', () => { + let wrapper; + const appTitle = 'Ingress'; + + const createComponent = (props = {}) => { + wrapper = shallowMount(UninstallApplicationConfirmationModal, { + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + beforeEach(() => { + createComponent({ application: INGRESS, applicationTitle: appTitle }); + }); + + it(`renders a modal with a title "Uninstall ${appTitle}"`, () => { + expect(wrapper.find(GlModal).attributes('title')).toEqual(`Uninstall ${appTitle}`); + }); + + it(`renders a modal with an ok button labeled "Uninstall ${appTitle}"`, () => { + expect(wrapper.find(GlModal).attributes('ok-title')).toEqual(`Uninstall ${appTitle}`); + }); + + it('triggers confirm event when ok button is clicked', () => { + wrapper.find(GlModal).vm.$emit('ok'); + + expect(wrapper.emitted('confirm')).toBeTruthy(); + }); + + it('displays a warning text indicating the app will be uninstalled', () => { + expect(wrapper.text()).toContain(`You are about to uninstall ${appTitle} from your cluster.`); + }); + + it('displays a custom warning text depending on the application', () => { + expect(wrapper.text()).toContain( + `The associated load balancer and IP will be deleted and cannot be restored.`, + ); + }); +}); diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js index e74b7910572..e057e2ac955 100644 --- a/spec/frontend/clusters/services/application_state_machine_spec.js +++ b/spec/frontend/clusters/services/application_state_machine_spec.js @@ -1,5 +1,10 @@ import transitionApplicationState from '~/clusters/services/application_state_machine'; -import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT } from '~/clusters/constants'; +import { + APPLICATION_STATUS, + UNINSTALL_EVENT, + UPDATE_EVENT, + INSTALL_EVENT, +} from '~/clusters/constants'; const { NO_STATUS, @@ -12,6 +17,8 @@ const { UPDATING, UPDATED, UPDATE_ERRORED, + UNINSTALLING, + UNINSTALL_ERRORED, } = APPLICATION_STATUS; const NO_EFFECTS = 'no effects'; @@ -21,16 +28,18 @@ describe('applicationStateMachine', () => { describe(`current state is ${NO_STATUS}`, () => { it.each` - expectedState | event | effects - ${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS} - ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS} - ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS} - ${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS} - ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} - ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }} - ${UPDATING} | ${UPDATING} | ${NO_EFFECTS} - ${INSTALLED} | ${UPDATED} | ${NO_EFFECTS} - ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} + expectedState | event | effects + ${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS} + ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS} + ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS} + ${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS} + ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} + ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }} + ${UPDATING} | ${UPDATING} | ${NO_EFFECTS} + ${INSTALLED} | ${UPDATED} | ${NO_EFFECTS} + ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} + ${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS} + ${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }} `(`transitions to $expectedState on $event event and applies $effects`, data => { const { expectedState, event, effects } = data; const currentAppState = { @@ -99,8 +108,9 @@ describe('applicationStateMachine', () => { describe(`current state is ${INSTALLED}`, () => { it.each` - expectedState | event | effects - ${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }} + expectedState | event | effects + ${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }} + ${UNINSTALLING} | ${UNINSTALL_EVENT} | ${{ uninstallFailed: false, uninstallSuccessful: false }} `(`transitions to $expectedState on $event event and applies $effects`, data => { const { expectedState, event, effects } = data; const currentAppState = { @@ -131,4 +141,22 @@ describe('applicationStateMachine', () => { }); }); }); + + describe(`current state is ${UNINSTALLING}`, () => { + it.each` + expectedState | event | effects + ${INSTALLABLE} | ${INSTALLABLE} | ${{ uninstallSuccessful: true }} + ${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }} + `(`transitions to $expectedState on $event event and applies $effects`, data => { + const { expectedState, event, effects } = data; + const currentAppState = { + status: UNINSTALLING, + }; + + expect(transitionApplicationState(currentAppState, event)).toEqual({ + status: expectedState, + ...effects, + }); + }); + }); }); diff --git a/spec/frontend/clusters/services/mock_data.js b/spec/frontend/clusters/services/mock_data.js index 1e896af1c7d..41ad398e924 100644 --- a/spec/frontend/clusters/services/mock_data.js +++ b/spec/frontend/clusters/services/mock_data.js @@ -11,6 +11,7 @@ const CLUSTERS_MOCK_DATA = { name: 'helm', status: APPLICATION_STATUS.INSTALLABLE, status_reason: null, + can_uninstall: false, }, { name: 'ingress', @@ -18,32 +19,38 @@ const CLUSTERS_MOCK_DATA = { status_reason: 'Cannot connect', external_ip: null, external_hostname: null, + can_uninstall: false, }, { name: 'runner', status: APPLICATION_STATUS.INSTALLING, status_reason: null, + can_uninstall: false, }, { name: 'prometheus', status: APPLICATION_STATUS.ERROR, status_reason: 'Cannot connect', + can_uninstall: false, }, { name: 'jupyter', status: APPLICATION_STATUS.INSTALLING, status_reason: 'Cannot connect', + can_uninstall: false, }, { name: 'knative', status: APPLICATION_STATUS.INSTALLING, status_reason: 'Cannot connect', + can_uninstall: false, }, { name: 'cert_manager', status: APPLICATION_STATUS.ERROR, status_reason: 'Cannot connect', email: 'test@example.com', + can_uninstall: false, }, ], }, diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index a20e0439555..aa926bb36d7 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -63,6 +63,8 @@ describe('Clusters Store', () => { installed: false, installFailed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, ingress: { title: 'Ingress', @@ -74,6 +76,8 @@ describe('Clusters Store', () => { installed: false, installFailed: true, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, runner: { title: 'GitLab Runner', @@ -89,6 +93,8 @@ describe('Clusters Store', () => { updateFailed: false, updateSuccessful: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, prometheus: { title: 'Prometheus', @@ -98,6 +104,8 @@ describe('Clusters Store', () => { installed: false, installFailed: true, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, jupyter: { title: 'JupyterHub', @@ -108,6 +116,8 @@ describe('Clusters Store', () => { installed: false, installFailed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, knative: { title: 'Knative', @@ -121,6 +131,8 @@ describe('Clusters Store', () => { installed: false, installFailed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, cert_manager: { title: 'Cert-Manager', @@ -131,6 +143,8 @@ describe('Clusters Store', () => { email: mockResponseData.applications[6].email, installed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, }, }); -- cgit v1.2.1 From ee7d2c1b2f91a299179cbb0b36e1b870b6c36903 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 24 Apr 2019 16:51:48 +0300 Subject: Remove unused projects.ci_id column And remove Gitlab::Ci::Trace#deprecated_path as it relies on ci_id Signed-off-by: Dmitriy Zaporozhets --- .../shared_examples/ci_trace_shared_examples.rb | 39 ---------------------- 1 file changed, 39 deletions(-) (limited to 'spec') diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb index c603421d748..db935bcb388 100644 --- a/spec/support/shared_examples/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/ci_trace_shared_examples.rb @@ -329,14 +329,6 @@ shared_examples_for 'trace with disabled live trace feature' do it_behaves_like 'read successfully with IO' end - context 'when current_path (with project_ci_id) exists' do - before do - expect(trace).to receive(:deprecated_path) { expand_fixture_path('trace/sample_trace') } - end - - it_behaves_like 'read successfully with IO' - end - context 'when db trace exists' do before do build.send(:write_attribute, :trace, "data") @@ -396,37 +388,6 @@ shared_examples_for 'trace with disabled live trace feature' do end end - context 'deprecated path' do - let(:path) { trace.send(:deprecated_path) } - - context 'with valid ci_id' do - before do - build.project.update(ci_id: 1000) - - FileUtils.mkdir_p(File.dirname(path)) - - File.open(path, "w") do |file| - file.write("data") - end - end - - it "trace exist" do - expect(trace.exist?).to be(true) - end - - it "can be erased" do - trace.erase! - expect(trace.exist?).to be(false) - end - end - - context 'without valid ci_id' do - it "does not return deprecated path" do - expect(path).to be_nil - end - end - end - context 'stored in database' do before do build.send(:write_attribute, :trace, "data") -- cgit v1.2.1 From d9b383cc3681c4ae6fe0dbef20a3d1089e48e139 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Thu, 2 May 2019 16:04:15 +0000 Subject: Add packages_size to ProjectStatistics This new field will allow to keep track of the storage used by the packages features, it provides also aggregation at namespace level. --- spec/helpers/storage_helper_spec.rb | 24 ++++++++++++++++++++++++ spec/models/namespace_spec.rb | 12 +++++++----- spec/models/project_statistics_spec.rb | 30 ++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index 03df9deafa1..50c74a7c2f9 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -18,4 +18,28 @@ describe StorageHelper do expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB") end end + + describe "#storage_counters_details" do + let(:namespace) { create :namespace } + let(:project) do + create(:project, + namespace: namespace, + statistics: build(:project_statistics, + repository_size: 10.kilobytes, + lfs_objects_size: 20.gigabytes, + build_artifacts_size: 30.megabytes)) + end + + let(:message) { '10 KB repositories, 30 MB build artifacts, 20 GB LFS' } + + it 'works on ProjectStatistics' do + expect(helper.storage_counters_details(project.statistics)).to eq(message) + end + + it 'works on Namespace.with_statistics' do + namespace_stats = Namespace.with_statistics.find(project.namespace.id) + + expect(helper.storage_counters_details(namespace_stats)).to eq(message) + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index dd5edca5059..bfde367c47f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -146,20 +146,20 @@ describe Namespace do create(:project, namespace: namespace, statistics: build(:project_statistics, - storage_size: 606, repository_size: 101, lfs_objects_size: 202, - build_artifacts_size: 303)) + build_artifacts_size: 303, + packages_size: 404)) end let(:project2) do create(:project, namespace: namespace, statistics: build(:project_statistics, - storage_size: 60, repository_size: 10, lfs_objects_size: 20, - build_artifacts_size: 30)) + build_artifacts_size: 30, + packages_size: 40)) end it "sums all project storage counters in the namespace" do @@ -167,10 +167,11 @@ describe Namespace do project2 statistics = described_class.with_statistics.find(namespace.id) - expect(statistics.storage_size).to eq 666 + expect(statistics.storage_size).to eq 1110 expect(statistics.repository_size).to eq 111 expect(statistics.lfs_objects_size).to eq 222 expect(statistics.build_artifacts_size).to eq 333 + expect(statistics.packages_size).to eq 444 end it "correctly handles namespaces without projects" do @@ -180,6 +181,7 @@ describe Namespace do expect(statistics.repository_size).to eq 0 expect(statistics.lfs_objects_size).to eq 0 expect(statistics.build_artifacts_size).to eq 0 + expect(statistics.packages_size).to eq 0 end end diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index c670b6aac56..738398a06f9 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -124,16 +124,30 @@ describe ProjectStatistics do end describe '.increment_statistic' do - it 'increases the statistic by that amount' do - expect { described_class.increment_statistic(project.id, :build_artifacts_size, 13) } - .to change { statistics.reload.build_artifacts_size } - .by(13) + shared_examples 'a statistic that increases storage_size' do + it 'increases the statistic by that amount' do + expect { described_class.increment_statistic(project.id, stat, 13) } + .to change { statistics.reload.send(stat) || 0 } + .by(13) + end + + it 'increases also storage size by that amount' do + expect { described_class.increment_statistic(project.id, stat, 20) } + .to change { statistics.reload.storage_size } + .by(20) + end end - it 'increases also storage size by that amount' do - expect { described_class.increment_statistic(project.id, :build_artifacts_size, 20) } - .to change { statistics.reload.storage_size } - .by(20) + context 'when adjusting :build_artifacts_size' do + let(:stat) { :build_artifacts_size } + + it_behaves_like 'a statistic that increases storage_size' + end + + context 'when adjusting :packages_size' do + let(:stat) { :packages_size } + + it_behaves_like 'a statistic that increases storage_size' end context 'when the amount is 0' do -- cgit v1.2.1 From 1b8646804d323d1a919c0150511168f8b6ef41d1 Mon Sep 17 00:00:00 2001 From: Jason Goodman Date: Thu, 2 May 2019 16:07:26 +0000 Subject: Update deployment chat message notification Include link to user and commit title. Rearrange text --- spec/lib/gitlab/data_builder/deployment_spec.rb | 3 +++ .../chat_message/deployment_message_spec.rb | 13 ++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb index b89a44e178b..0a6e2302b09 100644 --- a/spec/lib/gitlab/data_builder/deployment_spec.rb +++ b/spec/lib/gitlab/data_builder/deployment_spec.rb @@ -19,6 +19,7 @@ describe Gitlab::DataBuilder::Deployment do deployment = create(:deployment, status: :failed, environment: environment, sha: commit.sha, project: project) deployable = deployment.deployable expected_deployable_url = Gitlab::Routing.url_helpers.project_job_url(deployable.project, deployable) + expected_user_url = Gitlab::Routing.url_helpers.user_url(deployment.user) expected_commit_url = Gitlab::UrlBuilder.build(commit) data = described_class.build(deployment) @@ -30,7 +31,9 @@ describe Gitlab::DataBuilder::Deployment do expect(data[:project]).to eq(project.hook_attrs) expect(data[:short_sha]).to eq(deployment.short_sha) expect(data[:user]).to eq(deployment.user.hook_attrs) + expect(data[:user_url]).to eq(expected_user_url) expect(data[:commit_url]).to eq(expected_commit_url) + expect(data[:commit_title]).to eq(commit.title) end end end diff --git a/spec/models/project_services/chat_message/deployment_message_spec.rb b/spec/models/project_services/chat_message/deployment_message_spec.rb index 86565ce8b01..42c1689db3d 100644 --- a/spec/models/project_services/chat_message/deployment_message_spec.rb +++ b/spec/models/project_services/chat_message/deployment_message_spec.rb @@ -89,8 +89,10 @@ describe ChatMessage::DeploymentMessage do name: "Jane Person", username: "jane" }, + user_url: "user_url", short_sha: "12345678", - commit_url: "commit_url" + commit_url: "commit_url", + commit_title: "commit title text" }.merge(params) end @@ -104,12 +106,13 @@ describe ChatMessage::DeploymentMessage do deployment = create(:deployment, :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha) job_url = Gitlab::Routing.url_helpers.project_job_url(project, ci_build) commit_url = Gitlab::UrlBuilder.build(deployment.commit) + user_url = Gitlab::Routing.url_helpers.user_url(user) data = Gitlab::DataBuilder::Deployment.build(deployment) message = described_class.new(data) expect(message.attachments).to eq([{ - text: "[myspace/myproject](#{project.web_url})\n[Job ##{ci_build.id}](#{job_url}), SHA [#{deployment.short_sha}](#{commit_url}), by John Smith (smith)", + text: "[myspace/myproject](#{project.web_url}) with job [##{ci_build.id}](#{job_url}) by [John Smith (smith)](#{user_url})\n[#{deployment.short_sha}](#{commit_url}): #{commit.title}", color: "good" }]) end @@ -120,7 +123,7 @@ describe ChatMessage::DeploymentMessage do message = described_class.new(data) expect(message.attachments).to eq([{ - text: "[project_path_with_namespace](project_web_url)\n[Job #3](deployable_url), SHA [12345678](commit_url), by Jane Person (jane)", + text: "[project_path_with_namespace](project_web_url) with job [#3](deployable_url) by [Jane Person (jane)](user_url)\n[12345678](commit_url): commit title text", color: "danger" }]) end @@ -131,7 +134,7 @@ describe ChatMessage::DeploymentMessage do message = described_class.new(data) expect(message.attachments).to eq([{ - text: "[project_path_with_namespace](project_web_url)\n[Job #3](deployable_url), SHA [12345678](commit_url), by Jane Person (jane)", + text: "[project_path_with_namespace](project_web_url) with job [#3](deployable_url) by [Jane Person (jane)](user_url)\n[12345678](commit_url): commit title text", color: "warning" }]) end @@ -142,7 +145,7 @@ describe ChatMessage::DeploymentMessage do message = described_class.new(data) expect(message.attachments).to eq([{ - text: "[project_path_with_namespace](project_web_url)\n[Job #3](deployable_url), SHA [12345678](commit_url), by Jane Person (jane)", + text: "[project_path_with_namespace](project_web_url) with job [#3](deployable_url) by [Jane Person (jane)](user_url)\n[12345678](commit_url): commit title text", color: "#334455" }]) end -- cgit v1.2.1 From 49cb4b3dfcb88403ca7c7e866d94a9fbb08be442 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Thu, 2 May 2019 17:30:07 +0000 Subject: Add support for two-step Gitaly Rebase RPC The new two-step Gitaly `Rebase` RPC yields the rebase commit SHA to the client before proceeding with the rebase. This avoids an issue where the rebase commit SHA was returned when the RPC had fully completed, and in some cases this would be after the Rails `post_receive` worker services had already run. In these situations, the merge request did not yet have its rebase_commit_sha attribute set introducing the possibility for bugs (such as previous approvals being reset). https://gitlab.com/gitlab-org/gitlab-ee/issues/5966 --- spec/models/repository_spec.rb | 85 ++++++++++++++++++++++ .../services/merge_requests/rebase_service_spec.rb | 18 ++++- 2 files changed, 102 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index a6c3d5756aa..9ff0f355fd4 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1451,6 +1451,91 @@ describe Repository do end end + describe '#rebase' do + let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) } + + shared_examples_for 'a method that can rebase successfully' do + it 'returns the rebase commit sha' do + rebase_commit_sha = repository.rebase(user, merge_request) + head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha + + expect(rebase_commit_sha).to eq(head_sha) + end + + it 'sets the `rebase_commit_sha` for the given merge request' do + rebase_commit_sha = repository.rebase(user, merge_request) + + expect(rebase_commit_sha).not_to be_nil + expect(merge_request.rebase_commit_sha).to eq(rebase_commit_sha) + end + end + + context 'when two_step_rebase feature is enabled' do + before do + stub_feature_flags(two_step_rebase: true) + end + + it_behaves_like 'a method that can rebase successfully' + + it 'executes the new Gitaly RPC' do + expect_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rebase) + expect_any_instance_of(Gitlab::GitalyClient::OperationService).not_to receive(:user_rebase) + + repository.rebase(user, merge_request) + end + + describe 'rolling back the `rebase_commit_sha`' do + let(:new_sha) { Digest::SHA1.hexdigest('foo') } + + it 'does not rollback when there are no errors' do + second_response = double(pre_receive_error: nil, git_error: nil) + mock_gitaly(second_response) + + repository.rebase(user, merge_request) + + expect(merge_request.reload.rebase_commit_sha).to eq(new_sha) + end + + it 'does rollback when an error is encountered in the second step' do + second_response = double(pre_receive_error: 'my_error', git_error: nil) + mock_gitaly(second_response) + + expect do + repository.rebase(user, merge_request) + end.to raise_error(Gitlab::Git::PreReceiveError) + + expect(merge_request.reload.rebase_commit_sha).to be_nil + end + + def mock_gitaly(second_response) + responses = [ + double(rebase_sha: new_sha).as_null_object, + second_response + ] + + expect_any_instance_of( + Gitaly::OperationService::Stub + ).to receive(:user_rebase_confirmable).and_return(responses.each) + end + end + end + + context 'when two_step_rebase feature is disabled' do + before do + stub_feature_flags(two_step_rebase: false) + end + + it_behaves_like 'a method that can rebase successfully' + + it 'executes the deprecated Gitaly RPC' do + expect_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:user_rebase) + expect_any_instance_of(Gitlab::GitalyClient::OperationService).not_to receive(:rebase) + + repository.rebase(user, merge_request) + end + end + end + describe '#revert' do let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') } let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index 0d9523a4c2c..a443e4588d9 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -73,7 +73,7 @@ describe MergeRequests::RebaseService do end context 'valid params' do - describe 'successful rebase' do + shared_examples_for 'a service that can execute a successful rebase' do before do service.execute(merge_request) end @@ -99,6 +99,22 @@ describe MergeRequests::RebaseService do end end + context 'when the two_step_rebase feature is enabled' do + before do + stub_feature_flags(two_step_rebase: true) + end + + it_behaves_like 'a service that can execute a successful rebase' + end + + context 'when the two_step_rebase feature is disabled' do + before do + stub_feature_flags(two_step_rebase: false) + end + + it_behaves_like 'a service that can execute a successful rebase' + end + context 'fork' do describe 'successful fork rebase' do let(:forked_project) do -- cgit v1.2.1 From 5432f5480f334a0bd15ed06568e0c82f0dd54e45 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Thu, 2 May 2019 18:27:35 +0000 Subject: Adds a way to start multiple manual jobs in stage - Adds an endpoint on PipelinesController - Adds a service that iterates over every build in a stage and plays it. - Includes 'play_manual' details on EntitySerializer - Builds a new Stage state: PlayManual. An stage can take this status if it has manual builds or an skipped, scheduled or manual status - Includes FE modifications and specs --- .../controllers/projects/stages_controller_spec.rb | 72 ++++++++++++++++++++ spec/features/projects/pipelines/pipeline_spec.rb | 14 ++++ .../pipelines/graph/action_component_spec.js | 11 +++ .../pipelines/graph/stage_column_component_spec.js | 49 ++++++++++++++ spec/lib/gitlab/ci/status/stage/factory_spec.rb | 18 ++++- .../lib/gitlab/ci/status/stage/play_manual_spec.rb | 74 ++++++++++++++++++++ spec/models/ci/legacy_stage_spec.rb | 2 + spec/models/ci/pipeline_spec.rb | 32 +++++++++ spec/models/ci/stage_spec.rb | 2 + spec/serializers/pipeline_serializer_spec.rb | 2 +- spec/serializers/stage_entity_spec.rb | 28 ++++++++ spec/serializers/stage_serializer_spec.rb | 31 +++++++++ spec/services/ci/play_manual_stage_service_spec.rb | 79 ++++++++++++++++++++++ .../shared_examples/ci/stage_shared_examples.rb | 27 ++++++++ 14 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 spec/controllers/projects/stages_controller_spec.rb create mode 100644 spec/lib/gitlab/ci/status/stage/play_manual_spec.rb create mode 100644 spec/serializers/stage_serializer_spec.rb create mode 100644 spec/services/ci/play_manual_stage_service_spec.rb create mode 100644 spec/support/shared_examples/ci/stage_shared_examples.rb (limited to 'spec') diff --git a/spec/controllers/projects/stages_controller_spec.rb b/spec/controllers/projects/stages_controller_spec.rb new file mode 100644 index 00000000000..a91e3523fd7 --- /dev/null +++ b/spec/controllers/projects/stages_controller_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::StagesController do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + before do + sign_in(user) + end + + describe 'POST #play_manual.json' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:stage_name) { 'test' } + + before do + create_manual_build(pipeline, 'test', 'rspec 1/2') + create_manual_build(pipeline, 'test', 'rspec 2/2') + + pipeline.reload + end + + context 'when user does not have access' do + it 'returns not authorized' do + play_manual_stage! + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when user has access' do + before do + project.add_maintainer(user) + end + + context 'when the stage does not exists' do + let(:stage_name) { 'deploy' } + + it 'fails to play all manual' do + play_manual_stage! + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when the stage exists' do + it 'starts all manual jobs' do + expect(pipeline.builds.manual.count).to eq(2) + + play_manual_stage! + + expect(response).to have_gitlab_http_status(:ok) + expect(pipeline.builds.manual.count).to eq(0) + end + end + end + + def play_manual_stage! + post :play_manual, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id, + stage_name: stage_name + }, format: :json + end + + def create_manual_build(pipeline, stage, name) + create(:ci_build, :manual, pipeline: pipeline, stage: stage, name: name) + end + end +end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 3825468cf71..a1115b514d3 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -236,6 +236,20 @@ describe 'Pipeline', :js do end end + context 'when the pipeline has manual stage' do + before do + create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'CentOS') + create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'Debian') + create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'OpenSUDE') + + visit_pipeline + end + + it 'displays play all button' do + expect(page).to have_selector('.js-stage-action') + end + end + context 'page tabs' do before do visit_pipeline diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index 95717d659b8..321497b35b5 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -66,5 +66,16 @@ describe('pipeline graph action component', () => { done(); }, 0); }); + + it('renders a loading icon while waiting for request', done => { + component.$el.click(); + + component.$nextTick(() => { + expect(component.$el.querySelector('.js-action-icon-loading')).not.toBeNull(); + setTimeout(() => { + done(); + }); + }); + }); }); }); diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js index 3240e8e4c1b..5183f8dd2d6 100644 --- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js +++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js @@ -70,4 +70,53 @@ describe('stage column component', () => { ); }); }); + + describe('with action', () => { + it('renders action button', () => { + component = mountComponent(StageColumnComponent, { + groups: [ + { + id: 4259, + name: '', + status: { + icon: 'status_success', + label: 'success', + tooltip: '', + }, + }, + ], + title: 'test', + hasTriggeredBy: false, + action: { + icon: 'play', + title: 'Play all', + path: 'action', + }, + }); + + expect(component.$el.querySelector('.js-stage-action')).not.toBeNull(); + }); + }); + + describe('without action', () => { + it('does not render action button', () => { + component = mountComponent(StageColumnComponent, { + groups: [ + { + id: 4259, + name: '', + status: { + icon: 'status_success', + label: 'success', + tooltip: '', + }, + }, + ], + title: 'test', + hasTriggeredBy: false, + }); + + expect(component.$el.querySelector('.js-stage-action')).toBeNull(); + }); + }); }); diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index dee4f4efd1b..4b299170c87 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Stage::Factory do end context 'when stage has a core status' do - HasStatus::AVAILABLE_STATUSES.each do |core_status| + (HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status| context "when core status is #{core_status}" do before do create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status) @@ -64,4 +64,20 @@ describe Gitlab::Ci::Status::Stage::Factory do expect(status.details_path).to include "pipelines/#{pipeline.id}##{stage.name}" end end + + context 'when stage has manual builds' do + (HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status| + context "when status is #{core_status}" do + before do + create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status) + create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status) + create(:ci_build, pipeline: pipeline, stage: 'build', status: :manual) + end + + it 'fabricates a play manual status' do + expect(status).to be_a(Gitlab::Ci::Status::Stage::PlayManual) + end + end + end + end end diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb new file mode 100644 index 00000000000..b0113b00ef0 --- /dev/null +++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Status::Stage::PlayManual do + let(:stage) { double('stage') } + let(:play_manual) { described_class.new(stage) } + + describe '#action_icon' do + subject { play_manual.action_icon } + + it { is_expected.to eq('play') } + end + + describe '#action_button_title' do + subject { play_manual.action_button_title } + + it { is_expected.to eq('Play all manual') } + end + + describe '#action_title' do + subject { play_manual.action_title } + + it { is_expected.to eq('Play all manual') } + end + + describe '#action_path' do + let(:stage) { create(:ci_stage_entity, status: 'manual') } + let(:pipeline) { stage.pipeline } + let(:play_manual) { stage.detailed_status(create(:user)) } + + subject { play_manual.action_path } + + it { is_expected.to eq("/#{pipeline.project.full_path}/pipelines/#{pipeline.id}/stages/#{stage.name}/play_manual") } + end + + describe '#action_method' do + subject { play_manual.action_method } + + it { is_expected.to eq(:post) } + end + + describe '.matches?' do + let(:user) { double('user') } + + subject { described_class.matches?(stage, user) } + + context 'when stage is skipped' do + let(:stage) { create(:ci_stage_entity, status: :skipped) } + + it { is_expected.to be_truthy } + end + + context 'when stage is manual' do + let(:stage) { create(:ci_stage_entity, status: :manual) } + + it { is_expected.to be_truthy } + end + + context 'when stage is scheduled' do + let(:stage) { create(:ci_stage_entity, status: :scheduled) } + + it { is_expected.to be_truthy } + end + + context 'when stage is success' do + let(:stage) { create(:ci_stage_entity, status: :success) } + + context 'and does not have manual builds' do + it { is_expected.to be_falsy } + end + end + end +end diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb index be0307518eb..bb812cc0533 100644 --- a/spec/models/ci/legacy_stage_spec.rb +++ b/spec/models/ci/legacy_stage_spec.rb @@ -272,4 +272,6 @@ describe Ci::LegacyStage do def create_job(type, status: 'success', stage: stage_name, **opts) create(type, pipeline: pipeline, stage: stage, status: status, **opts) end + + it_behaves_like 'manual playable stage', :ci_stage end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 9d0cd654f13..af455a72f50 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2942,4 +2942,36 @@ describe Ci::Pipeline, :mailer do end end end + + describe '#find_stage_by_name' do + let(:pipeline) { create(:ci_pipeline) } + let(:stage_name) { 'test' } + + let(:stage) do + create(:ci_stage_entity, + pipeline: pipeline, + project: pipeline.project, + name: 'test') + end + + before do + create_list(:ci_build, 2, pipeline: pipeline, stage: stage.name) + end + + subject { pipeline.find_stage_by_name!(stage_name) } + + context 'when stage exists' do + it { is_expected.to eq(stage) } + end + + context 'when stage does not exist' do + let(:stage_name) { 'build' } + + it 'raises an ActiveRecord exception' do + expect do + subject + end.to raise_exception(ActiveRecord::RecordNotFound) + end + end + end end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 661958390e2..85cd32fb03a 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -285,4 +285,6 @@ describe Ci::Stage, :models do end end end + + it_behaves_like 'manual playable stage', :ci_stage_entity end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index d9023036534..54e6abc2d3a 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -156,8 +156,8 @@ describe PipelineSerializer do it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expected_queries = Gitlab.ee? ? 38 : 31 + expect(recorded.count).to be_within(2).of(expected_queries) expect(recorded.cached_count).to eq(0) end diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb index 2034c7891ef..6b1185d1283 100644 --- a/spec/serializers/stage_entity_spec.rb +++ b/spec/serializers/stage_entity_spec.rb @@ -48,6 +48,10 @@ describe StageEntity do expect(subject[:title]).to eq 'test: passed' end + it 'does not contain play_details info' do + expect(subject[:status][:action]).not_to be_present + end + context 'when the jobs should be grouped' do let(:entity) { described_class.new(stage, request: request, grouped: true) } @@ -66,5 +70,29 @@ describe StageEntity do end end end + + context 'with a skipped stage ' do + let(:stage) { create(:ci_stage_entity, status: 'skipped') } + + it 'contains play_all_manual' do + expect(subject[:status][:action]).to be_present + end + end + + context 'with a scheduled stage ' do + let(:stage) { create(:ci_stage_entity, status: 'scheduled') } + + it 'contains play_all_manual' do + expect(subject[:status][:action]).to be_present + end + end + + context 'with a manual stage ' do + let(:stage) { create(:ci_stage_entity, status: 'manual') } + + it 'contains play_all_manual' do + expect(subject[:status][:action]).to be_present + end + end end end diff --git a/spec/serializers/stage_serializer_spec.rb b/spec/serializers/stage_serializer_spec.rb new file mode 100644 index 00000000000..aae17cfbcb9 --- /dev/null +++ b/spec/serializers/stage_serializer_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe StageSerializer do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:resource) { create(:ci_stage_entity) } + + let(:serializer) do + described_class.new(current_user: user, project: project) + end + + subject { serializer.represent(resource) } + + describe '#represent' do + context 'with a single entity' do + it 'serializes the stage object' do + expect(subject[:name]).to eq(resource.name) + end + end + + context 'with an array of entities' do + let(:resource) { create_list(:ci_stage_entity, 2) } + + it 'serializes the array of pipelines' do + expect(subject).not_to be_empty + end + end + end +end diff --git a/spec/services/ci/play_manual_stage_service_spec.rb b/spec/services/ci/play_manual_stage_service_spec.rb new file mode 100644 index 00000000000..5d812745c7f --- /dev/null +++ b/spec/services/ci/play_manual_stage_service_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::PlayManualStageService, '#execute' do + let(:current_user) { create(:user) } + let(:pipeline) { create(:ci_pipeline, user: current_user) } + let(:project) { pipeline.project } + let(:service) { described_class.new(project, current_user, pipeline: pipeline) } + let(:stage_status) { 'manual' } + + let(:stage) do + create(:ci_stage_entity, + pipeline: pipeline, + project: project, + name: 'test') + end + + before do + project.add_maintainer(current_user) + create_builds_for_stage(status: stage_status) + end + + context 'when pipeline has manual builds' do + before do + service.execute(stage) + end + + it 'starts manual builds from pipeline' do + expect(pipeline.builds.manual.count).to eq(0) + end + + it 'updates manual builds' do + pipeline.builds.each do |build| + expect(build.user).to eq(current_user) + end + end + end + + context 'when pipeline has no manual builds' do + let(:stage_status) { 'failed' } + + before do + service.execute(stage) + end + + it 'does not update the builds' do + expect(pipeline.builds.failed.count).to eq(3) + end + end + + context 'when user does not have permission on a specific build' do + before do + allow_any_instance_of(Ci::Build).to receive(:play) + .and_raise(Gitlab::Access::AccessDeniedError) + + service.execute(stage) + end + + it 'logs the error' do + expect(Gitlab::AppLogger).to receive(:error) + .exactly(stage.builds.manual.count) + + service.execute(stage) + end + end + + def create_builds_for_stage(options) + options.merge!({ + when: 'manual', + pipeline: pipeline, + stage: stage.name, + stage_id: stage.id, + user: pipeline.user + }) + + create_list(:ci_build, 3, options) + end +end diff --git a/spec/support/shared_examples/ci/stage_shared_examples.rb b/spec/support/shared_examples/ci/stage_shared_examples.rb new file mode 100644 index 00000000000..925974ed11e --- /dev/null +++ b/spec/support/shared_examples/ci/stage_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +shared_examples 'manual playable stage' do |stage_type| + let(:stage) { build(stage_type, status: status) } + + describe '#manual_playable?' do + subject { stage.manual_playable? } + + context 'when is manual' do + let(:status) { 'manual' } + + it { is_expected.to be_truthy } + end + + context 'when is scheduled' do + let(:status) { 'scheduled' } + + it { is_expected.to be_truthy } + end + + context 'when is skipped' do + let(:status) { 'skipped' } + + it { is_expected.to be_truthy } + end + end +end -- cgit v1.2.1 From d8bddb16624f34600069bb5d3540960b25176381 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Wed, 10 Apr 2019 11:39:45 +0800 Subject: Validate MR branch names Prevents refspec as branch name, which would bypass branch protection when used in conjunction with rebase. HEAD seems to be a special case with lots of occurrence, so it is considered valid for now. Another special case is `refs/head/*`, which can be imported. --- spec/features/issuables/issuable_list_spec.rb | 2 +- spec/lib/gitlab/bitbucket_import/importer_spec.rb | 1 + spec/lib/gitlab/git_ref_validator_spec.rb | 92 ++++++++++++++++------- spec/models/merge_request_spec.rb | 36 +++++++++ 4 files changed, 103 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index 7b6e9cd66b2..225b858742d 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -76,7 +76,7 @@ describe 'issuable list' do create(:issue, project: project, author: user) else create(:merge_request, source_project: project, source_branch: generate(:branch)) - source_branch = FFaker::Name.name + source_branch = FFaker::Lorem.characters(8) pipeline = create(:ci_empty_pipeline, project: project, ref: source_branch, status: %w(running failed success).sample, sha: 'any') create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: source_branch, head_pipeline: pipeline) end diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index e1a2bae5fe8..fbe87aaefa1 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -5,6 +5,7 @@ describe Gitlab::BitbucketImport::Importer do before do stub_omniauth_provider('bitbucket') + stub_feature_flags(stricter_mr_branch_name: false) end let(:statuses) do diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb index 3ab04a1c46d..6fc41cd64f9 100644 --- a/spec/lib/gitlab/git_ref_validator_spec.rb +++ b/spec/lib/gitlab/git_ref_validator_spec.rb @@ -1,31 +1,69 @@ require 'spec_helper' describe Gitlab::GitRefValidator do - it { expect(described_class.validate('feature/new')).to be_truthy } - it { expect(described_class.validate('implement_@all')).to be_truthy } - it { expect(described_class.validate('my_new_feature')).to be_truthy } - it { expect(described_class.validate('my-branch')).to be_truthy } - it { expect(described_class.validate('#1')).to be_truthy } - it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy } - it { expect(described_class.validate('feature/~new/')).to be_falsey } - it { expect(described_class.validate('feature/^new/')).to be_falsey } - it { expect(described_class.validate('feature/:new/')).to be_falsey } - it { expect(described_class.validate('feature/?new/')).to be_falsey } - it { expect(described_class.validate('feature/*new/')).to be_falsey } - it { expect(described_class.validate('feature/[new/')).to be_falsey } - it { expect(described_class.validate('feature/new/')).to be_falsey } - it { expect(described_class.validate('feature/new.')).to be_falsey } - it { expect(described_class.validate('feature\@{')).to be_falsey } - it { expect(described_class.validate('feature\new')).to be_falsey } - it { expect(described_class.validate('feature//new')).to be_falsey } - it { expect(described_class.validate('feature new')).to be_falsey } - it { expect(described_class.validate('refs/heads/')).to be_falsey } - it { expect(described_class.validate('refs/remotes/')).to be_falsey } - it { expect(described_class.validate('refs/heads/feature')).to be_falsey } - it { expect(described_class.validate('refs/remotes/origin')).to be_falsey } - it { expect(described_class.validate('-')).to be_falsey } - it { expect(described_class.validate('-branch')).to be_falsey } - it { expect(described_class.validate('.tag')).to be_falsey } - it { expect(described_class.validate('my branch')).to be_falsey } - it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey } + using RSpec::Parameterized::TableSyntax + + context '.validate' do + it { expect(described_class.validate('feature/new')).to be_truthy } + it { expect(described_class.validate('implement_@all')).to be_truthy } + it { expect(described_class.validate('my_new_feature')).to be_truthy } + it { expect(described_class.validate('my-branch')).to be_truthy } + it { expect(described_class.validate('#1')).to be_truthy } + it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy } + it { expect(described_class.validate('feature/~new/')).to be_falsey } + it { expect(described_class.validate('feature/^new/')).to be_falsey } + it { expect(described_class.validate('feature/:new/')).to be_falsey } + it { expect(described_class.validate('feature/?new/')).to be_falsey } + it { expect(described_class.validate('feature/*new/')).to be_falsey } + it { expect(described_class.validate('feature/[new/')).to be_falsey } + it { expect(described_class.validate('feature/new/')).to be_falsey } + it { expect(described_class.validate('feature/new.')).to be_falsey } + it { expect(described_class.validate('feature\@{')).to be_falsey } + it { expect(described_class.validate('feature\new')).to be_falsey } + it { expect(described_class.validate('feature//new')).to be_falsey } + it { expect(described_class.validate('feature new')).to be_falsey } + it { expect(described_class.validate('refs/heads/')).to be_falsey } + it { expect(described_class.validate('refs/remotes/')).to be_falsey } + it { expect(described_class.validate('refs/heads/feature')).to be_falsey } + it { expect(described_class.validate('refs/remotes/origin')).to be_falsey } + it { expect(described_class.validate('-')).to be_falsey } + it { expect(described_class.validate('-branch')).to be_falsey } + it { expect(described_class.validate('+foo:bar')).to be_falsey } + it { expect(described_class.validate('foo:bar')).to be_falsey } + it { expect(described_class.validate('.tag')).to be_falsey } + it { expect(described_class.validate('my branch')).to be_falsey } + it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey } + end + + context '.validate_merge_request_branch' do + it { expect(described_class.validate_merge_request_branch('HEAD')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('feature/new')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('implement_@all')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('my_new_feature')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('my-branch')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('#1')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('feature/refs/heads/foo')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('feature/~new/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature/^new/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature/:new/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature/?new/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature/*new/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature/[new/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature/new/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature/new.')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature\@{')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature\new')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature//new')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('feature new')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('refs/heads/master')).to be_truthy } + it { expect(described_class.validate_merge_request_branch('refs/heads/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('refs/remotes/')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('-')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('-branch')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('+foo:bar')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('foo:bar')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('.tag')).to be_falsey } + it { expect(described_class.validate_merge_request_branch('my branch')).to be_falsey } + it { expect(described_class.validate_merge_request_branch("\xA0\u0000\xB0")).to be_falsey } + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6f34ef9c1bc..2b78e1e361e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -150,6 +150,42 @@ describe MergeRequest do end end + context 'for branch' do + before do + stub_feature_flags(stricter_mr_branch_name: false) + end + + using RSpec::Parameterized::TableSyntax + + where(:branch_name, :valid) do + 'foo' | true + 'foo:bar' | false + '+foo:bar' | false + 'foo bar' | false + '-foo' | false + 'HEAD' | true + 'refs/heads/master' | true + end + + with_them do + it "validates source_branch" do + subject = build(:merge_request, source_branch: branch_name, target_branch: 'master') + + subject.valid? + + expect(subject.errors.added?(:source_branch)).to eq(!valid) + end + + it "validates target_branch" do + subject = build(:merge_request, source_branch: 'master', target_branch: branch_name) + + subject.valid? + + expect(subject.errors.added?(:target_branch)).to eq(!valid) + end + end + end + context 'for forks' do let(:project) { create(:project) } let(:fork1) { fork_project(project) } -- cgit v1.2.1 From 76469b508234c0528cbb5afb22e2dd776c11d56b Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 22 Apr 2019 14:57:08 +0900 Subject: Use full ref when creating MR pipeline in specs Continuation of 426488b7218e85ce69868ae4628801af2322b74a --- spec/services/ci/create_pipeline_service_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 866d709d446..0f5554fa577 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -923,7 +923,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end @@ -954,7 +954,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end @@ -983,7 +983,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end -- cgit v1.2.1 From c7e8f5c613754a7221d6b2f0b0e154b75c55dd84 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Fri, 3 May 2019 03:01:57 +0800 Subject: Refactor spec to not use truthy or falsey --- spec/lib/gitlab/git_ref_validator_spec.rb | 116 +++++++++++++++--------------- 1 file changed, 58 insertions(+), 58 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb index 6fc41cd64f9..b63389af29f 100644 --- a/spec/lib/gitlab/git_ref_validator_spec.rb +++ b/spec/lib/gitlab/git_ref_validator_spec.rb @@ -4,66 +4,66 @@ describe Gitlab::GitRefValidator do using RSpec::Parameterized::TableSyntax context '.validate' do - it { expect(described_class.validate('feature/new')).to be_truthy } - it { expect(described_class.validate('implement_@all')).to be_truthy } - it { expect(described_class.validate('my_new_feature')).to be_truthy } - it { expect(described_class.validate('my-branch')).to be_truthy } - it { expect(described_class.validate('#1')).to be_truthy } - it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy } - it { expect(described_class.validate('feature/~new/')).to be_falsey } - it { expect(described_class.validate('feature/^new/')).to be_falsey } - it { expect(described_class.validate('feature/:new/')).to be_falsey } - it { expect(described_class.validate('feature/?new/')).to be_falsey } - it { expect(described_class.validate('feature/*new/')).to be_falsey } - it { expect(described_class.validate('feature/[new/')).to be_falsey } - it { expect(described_class.validate('feature/new/')).to be_falsey } - it { expect(described_class.validate('feature/new.')).to be_falsey } - it { expect(described_class.validate('feature\@{')).to be_falsey } - it { expect(described_class.validate('feature\new')).to be_falsey } - it { expect(described_class.validate('feature//new')).to be_falsey } - it { expect(described_class.validate('feature new')).to be_falsey } - it { expect(described_class.validate('refs/heads/')).to be_falsey } - it { expect(described_class.validate('refs/remotes/')).to be_falsey } - it { expect(described_class.validate('refs/heads/feature')).to be_falsey } - it { expect(described_class.validate('refs/remotes/origin')).to be_falsey } - it { expect(described_class.validate('-')).to be_falsey } - it { expect(described_class.validate('-branch')).to be_falsey } - it { expect(described_class.validate('+foo:bar')).to be_falsey } - it { expect(described_class.validate('foo:bar')).to be_falsey } - it { expect(described_class.validate('.tag')).to be_falsey } - it { expect(described_class.validate('my branch')).to be_falsey } - it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey } + it { expect(described_class.validate('feature/new')).to be true } + it { expect(described_class.validate('implement_@all')).to be true } + it { expect(described_class.validate('my_new_feature')).to be true } + it { expect(described_class.validate('my-branch')).to be true } + it { expect(described_class.validate('#1')).to be true } + it { expect(described_class.validate('feature/refs/heads/foo')).to be true } + it { expect(described_class.validate('feature/~new/')).to be false } + it { expect(described_class.validate('feature/^new/')).to be false } + it { expect(described_class.validate('feature/:new/')).to be false } + it { expect(described_class.validate('feature/?new/')).to be false } + it { expect(described_class.validate('feature/*new/')).to be false } + it { expect(described_class.validate('feature/[new/')).to be false } + it { expect(described_class.validate('feature/new/')).to be false } + it { expect(described_class.validate('feature/new.')).to be false } + it { expect(described_class.validate('feature\@{')).to be false } + it { expect(described_class.validate('feature\new')).to be false } + it { expect(described_class.validate('feature//new')).to be false } + it { expect(described_class.validate('feature new')).to be false } + it { expect(described_class.validate('refs/heads/')).to be false } + it { expect(described_class.validate('refs/remotes/')).to be false } + it { expect(described_class.validate('refs/heads/feature')).to be false } + it { expect(described_class.validate('refs/remotes/origin')).to be false } + it { expect(described_class.validate('-')).to be false } + it { expect(described_class.validate('-branch')).to be false } + it { expect(described_class.validate('+foo:bar')).to be false } + it { expect(described_class.validate('foo:bar')).to be false } + it { expect(described_class.validate('.tag')).to be false } + it { expect(described_class.validate('my branch')).to be false } + it { expect(described_class.validate("\xA0\u0000\xB0")).to be false } end context '.validate_merge_request_branch' do - it { expect(described_class.validate_merge_request_branch('HEAD')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('feature/new')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('implement_@all')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('my_new_feature')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('my-branch')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('#1')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('feature/refs/heads/foo')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('feature/~new/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature/^new/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature/:new/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature/?new/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature/*new/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature/[new/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature/new/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature/new.')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature\@{')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature\new')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature//new')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('feature new')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('refs/heads/master')).to be_truthy } - it { expect(described_class.validate_merge_request_branch('refs/heads/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('refs/remotes/')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('-')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('-branch')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('+foo:bar')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('foo:bar')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('.tag')).to be_falsey } - it { expect(described_class.validate_merge_request_branch('my branch')).to be_falsey } - it { expect(described_class.validate_merge_request_branch("\xA0\u0000\xB0")).to be_falsey } + it { expect(described_class.validate_merge_request_branch('HEAD')).to be true } + it { expect(described_class.validate_merge_request_branch('feature/new')).to be true } + it { expect(described_class.validate_merge_request_branch('implement_@all')).to be true } + it { expect(described_class.validate_merge_request_branch('my_new_feature')).to be true } + it { expect(described_class.validate_merge_request_branch('my-branch')).to be true } + it { expect(described_class.validate_merge_request_branch('#1')).to be true } + it { expect(described_class.validate_merge_request_branch('feature/refs/heads/foo')).to be true } + it { expect(described_class.validate_merge_request_branch('feature/~new/')).to be false } + it { expect(described_class.validate_merge_request_branch('feature/^new/')).to be false } + it { expect(described_class.validate_merge_request_branch('feature/:new/')).to be false } + it { expect(described_class.validate_merge_request_branch('feature/?new/')).to be false } + it { expect(described_class.validate_merge_request_branch('feature/*new/')).to be false } + it { expect(described_class.validate_merge_request_branch('feature/[new/')).to be false } + it { expect(described_class.validate_merge_request_branch('feature/new/')).to be false } + it { expect(described_class.validate_merge_request_branch('feature/new.')).to be false } + it { expect(described_class.validate_merge_request_branch('feature\@{')).to be false } + it { expect(described_class.validate_merge_request_branch('feature\new')).to be false } + it { expect(described_class.validate_merge_request_branch('feature//new')).to be false } + it { expect(described_class.validate_merge_request_branch('feature new')).to be false } + it { expect(described_class.validate_merge_request_branch('refs/heads/master')).to be true } + it { expect(described_class.validate_merge_request_branch('refs/heads/')).to be false } + it { expect(described_class.validate_merge_request_branch('refs/remotes/')).to be false } + it { expect(described_class.validate_merge_request_branch('-')).to be false } + it { expect(described_class.validate_merge_request_branch('-branch')).to be false } + it { expect(described_class.validate_merge_request_branch('+foo:bar')).to be false } + it { expect(described_class.validate_merge_request_branch('foo:bar')).to be false } + it { expect(described_class.validate_merge_request_branch('.tag')).to be false } + it { expect(described_class.validate_merge_request_branch('my branch')).to be false } + it { expect(described_class.validate_merge_request_branch("\xA0\u0000\xB0")).to be false } end end -- cgit v1.2.1 From 3945e18176db929c758b03510c92b31c6da2bf3b Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Wed, 1 May 2019 08:56:23 -0400 Subject: CE-backport track uninstall button clicks --- .../uninstall_application_confirmation_modal_spec.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js index 6a7126b45cd..04808864fc0 100644 --- a/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js +++ b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js @@ -29,10 +29,19 @@ describe('UninstallApplicationConfirmationModal', () => { expect(wrapper.find(GlModal).attributes('ok-title')).toEqual(`Uninstall ${appTitle}`); }); - it('triggers confirm event when ok button is clicked', () => { - wrapper.find(GlModal).vm.$emit('ok'); + describe('when ok button is clicked', () => { + beforeEach(() => { + jest.spyOn(wrapper.vm, 'trackUninstallButtonClick'); + wrapper.find(GlModal).vm.$emit('ok'); + }); + + it('emits confirm event', () => { + expect(wrapper.emitted('confirm')).toBeTruthy(); + }); - expect(wrapper.emitted('confirm')).toBeTruthy(); + it('calls track uninstall button click mixin', () => { + expect(wrapper.vm.trackUninstallButtonClick).toHaveBeenCalledWith(INGRESS); + }); }); it('displays a warning text indicating the app will be uninstalled', () => { -- cgit v1.2.1 From 5990d42253b2b99dc5a699df81f0e0f976844c49 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 25 Apr 2019 11:44:53 +0200 Subject: Ignore PDF and balsamiq fixture files (cherry picked from commit 1b33828811897b6a3f8a38044cc0839b87098d1f) --- spec/javascripts/fixtures/.gitignore | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore index 2507c8e7263..bed020f5b0a 100644 --- a/spec/javascripts/fixtures/.gitignore +++ b/spec/javascripts/fixtures/.gitignore @@ -1,3 +1,5 @@ *.html.raw *.html *.json +*.pdf +*.bmpr -- cgit v1.2.1 From 1542904541494fdb4ffec29c5ca83c0eac9b8c97 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 25 Apr 2019 11:48:16 +0200 Subject: Generate EE frontend fixtures into ee/ directory (cherry picked from commit d4ce3c720ecb52f2a9153133bbd47fa358c5d7b6) --- spec/support/helpers/javascript_fixtures_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 9cae8f934db..494398dc4de 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -15,7 +15,7 @@ module JavaScriptFixturesHelpers end def fixture_root_path - 'spec/javascripts/fixtures' + (Gitlab.ee? ? 'ee/' : '') + 'spec/javascripts/fixtures' end # Public: Removes all fixture files from given directory -- cgit v1.2.1 From 6e3165f0b4bcbb12d88b653df7f6b66349b7b375 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 25 Apr 2019 11:48:54 +0200 Subject: Load EE fixtures from ee/ directory in Karma (cherry picked from commit 7cde57bf7367bce130a2e7f149c8dafd362de3f8) --- spec/javascripts/test_constants.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js index 24b5512b053..77c206585fe 100644 --- a/spec/javascripts/test_constants.js +++ b/spec/javascripts/test_constants.js @@ -1,4 +1,6 @@ -export const FIXTURES_PATH = '/base/spec/javascripts/fixtures'; +export const FIXTURES_PATH = `/base/${ + process.env.IS_GITLAB_EE ? 'ee/' : '' +}spec/javascripts/fixtures`; export const TEST_HOST = 'http://test.host'; export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`; -- cgit v1.2.1 From fbac16b2de4b715a85dfd2b79768079d12229bc1 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 25 Apr 2019 11:49:06 +0200 Subject: Load EE fixtures from ee/ directory in Jest (cherry picked from commit 2917a28a885922a03f2026ddbb2680bc2b6e5d50) --- spec/frontend/environment.js | 7 ++++++- spec/frontend/helpers/fixtures.js | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index 34df8019a2e..9612162ad0c 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -24,8 +24,9 @@ class CustomEnvironment extends JSDOMEnvironment { }); const { testEnvironmentOptions } = config; + const { IS_EE } = testEnvironmentOptions; this.global.gon = { - ee: testEnvironmentOptions.IS_EE, + ee: IS_EE, }; this.rejectedPromises = []; @@ -33,6 +34,10 @@ class CustomEnvironment extends JSDOMEnvironment { this.global.promiseRejectionHandler = error => { this.rejectedPromises.push(error); }; + + this.global.fixturesBasePath = `${process.cwd()}/${ + IS_EE ? 'ee/' : '' + }spec/javascripts/fixtures`; } async teardown() { diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js index f0351aa31c6..b77bcd6266e 100644 --- a/spec/frontend/helpers/fixtures.js +++ b/spec/frontend/helpers/fixtures.js @@ -3,10 +3,8 @@ import path from 'path'; import { ErrorWithStack } from 'jest-util'; -const fixturesBasePath = path.join(process.cwd(), 'spec', 'javascripts', 'fixtures'); - export function getFixture(relativePath) { - const absolutePath = path.join(fixturesBasePath, relativePath); + const absolutePath = path.join(global.fixturesBasePath, relativePath); if (!fs.existsSync(absolutePath)) { throw new ErrorWithStack( `Fixture file ${relativePath} does not exist. -- cgit v1.2.1 From d25239ee0b56a1f7c00d805949d0d0009037ee54 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 2 May 2019 21:41:05 +0000 Subject: Use git_garbage_collect_worker to run pack_refs PackRefs is not an expensive gitaly call - we want to call it more often (than as part of full `gc`) because it helps to keep number of refs files small - too many refs file may be a problem for deployments with slow storage. --- spec/lib/gitlab/gitaly_client/ref_service_spec.rb | 11 +++++++++++ spec/services/projects/housekeeping_service_spec.rb | 3 +++ spec/workers/git_garbage_collect_worker_spec.rb | 13 +++++++++++++ 3 files changed, 27 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 0dab39575b9..0bb6e582159 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -165,4 +165,15 @@ describe Gitlab::GitalyClient::RefService do client.delete_refs(except_with_prefixes: prefixes) end end + + describe '#pack_refs' do + it 'sends a pack_refs message' do + expect_any_instance_of(Gitaly::RefService::Stub) + .to receive(:pack_refs) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(:pack_refs_response)) + + client.pack_refs + end + end end diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 368cf123c3e..f651db70cbd 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -81,6 +81,9 @@ describe Projects::HousekeepingService do # At push 10, 20, ... (except those above) expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid) .exactly(16).times + # At push 6, 12, 18, ... (except those above) + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :pack_refs, :the_lease_key, :the_uuid) + .exactly(27).times 201.times do subject.increment! diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 2459638c3e6..cc1c23bb9e7 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -115,6 +115,19 @@ describe GitGarbageCollectWorker do end end + context "pack_refs" do + before do + expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid) + end + + it "calls Gitaly" do + expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:pack_refs) + .and_return(nil) + + subject.perform(project.id, :pack_refs, lease_key, lease_uuid) + end + end + context "repack_incremental" do before do expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid) -- cgit v1.2.1 From 89132bbdd63bbd033c43422500a972af6d94a4d0 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Fri, 3 May 2019 01:05:53 +0000 Subject: Add gitlab-managed option to clusters form When this option is enabled, GitLab will create namespaces and service accounts as usual. When disabled, GitLab wont create any project specific kubernetes resources Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/56557 --- .../controllers/groups/clusters_controller_spec.rb | 27 +++++++++++++++++ .../projects/clusters_controller_spec.rb | 35 ++++++++++++++++++++-- spec/factories/clusters/clusters.rb | 2 +- .../prerequisite/kubernetes_namespace_spec.rb | 6 ++++ spec/models/clusters/cluster_spec.rb | 18 +++++++++++ spec/models/clusters/platforms/kubernetes_spec.rb | 12 ++++++++ spec/requests/api/project_clusters_spec.rb | 2 ++ spec/services/clusters/refresh_service_spec.rb | 6 ++++ spec/workers/cluster_configure_worker_spec.rb | 10 +++++++ 9 files changed, 114 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index e5180ec5c5c..7349cb7094c 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -189,6 +189,7 @@ describe Groups::ClustersController do { cluster: { name: 'new-cluster', + managed: '1', provider_gcp_attributes: { gcp_project_id: 'gcp-project-12345', legacy_abac: legacy_abac_param @@ -218,6 +219,7 @@ describe Groups::ClustersController do expect(cluster).to be_gcp expect(cluster).to be_kubernetes expect(cluster.provider_gcp).to be_legacy_abac + expect(cluster).to be_managed end context 'when legacy_abac param is false' do @@ -278,6 +280,7 @@ describe Groups::ClustersController do { cluster: { name: 'new-cluster', + managed: '1', platform_kubernetes_attributes: { api_url: 'http://my-url', token: 'test' @@ -303,6 +306,7 @@ describe Groups::ClustersController do expect(response).to redirect_to(group_cluster_path(group, cluster)) expect(cluster).to be_user expect(cluster).to be_kubernetes + expect(cluster).to be_managed end end @@ -334,6 +338,29 @@ describe Groups::ClustersController do expect(cluster).to be_platform_kubernetes_rbac end end + + context 'when creates a user-managed cluster' do + let(:params) do + { + cluster: { + name: 'new-cluster', + managed: '0', + platform_kubernetes_attributes: { + api_url: 'http://my-url', + token: 'test', + authorization_type: 'rbac' + } + } + } + end + + it 'creates a new user-managed cluster' do + go + + cluster = group.clusters.first + expect(cluster.managed?).to be_falsy + end + end end describe 'security' do diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index d94c18ddc02..8d37bd82d21 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -165,6 +165,7 @@ describe Projects::ClustersController do { cluster: { name: 'new-cluster', + managed: '1', provider_gcp_attributes: { gcp_project_id: 'gcp-project-12345', legacy_abac: legacy_abac_param @@ -191,6 +192,7 @@ describe Projects::ClustersController do expect(project.clusters.first).to be_gcp expect(project.clusters.first).to be_kubernetes expect(project.clusters.first.provider_gcp).to be_legacy_abac + expect(project.clusters.first.managed?).to be_truthy end context 'when legacy_abac param is false' do @@ -251,6 +253,7 @@ describe Projects::ClustersController do { cluster: { name: 'new-cluster', + managed: '1', platform_kubernetes_attributes: { api_url: 'http://my-url', token: 'test', @@ -302,9 +305,35 @@ describe Projects::ClustersController do expect(response).to redirect_to(project_cluster_path(project, project.clusters.first)) - expect(project.clusters.first).to be_user - expect(project.clusters.first).to be_kubernetes - expect(project.clusters.first).to be_platform_kubernetes_rbac + cluster = project.clusters.first + + expect(cluster).to be_user + expect(cluster).to be_kubernetes + expect(cluster).to be_platform_kubernetes_rbac + end + end + + context 'when creates a user-managed cluster' do + let(:params) do + { + cluster: { + name: 'new-cluster', + managed: '0', + platform_kubernetes_attributes: { + api_url: 'http://my-url', + token: 'test', + namespace: 'aaa', + authorization_type: 'rbac' + } + } + } + end + + it 'creates a new user-managed cluster' do + go + cluster = project.clusters.first + + expect(cluster.managed?).to be_falsy end end end diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb index 97405ec7c58..6eb0194b710 100644 --- a/spec/factories/clusters/clusters.rb +++ b/spec/factories/clusters/clusters.rb @@ -65,7 +65,7 @@ FactoryBot.define do domain 'example.com' end - trait :user_managed do + trait :not_managed do managed false end end diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index e8332b14627..5387863bd07 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -28,6 +28,12 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do it { is_expected.to be_truthy } + context 'and the cluster is not managed' do + let(:cluster) { create(:cluster, :not_managed, projects: [build.project]) } + + it { is_expected.to be_falsey } + end + context 'and a namespace is already created for this project' do let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: build.project) } diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 894ef3fb956..e1506c06044 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -95,6 +95,24 @@ describe Clusters::Cluster do it { is_expected.to contain_exactly(cluster) } end + describe '.managed' do + subject do + described_class.managed + end + + context 'cluster is not managed' do + let!(:cluster) { create(:cluster, :not_managed) } + + it { is_expected.not_to include(cluster) } + end + + context 'cluster is managed' do + let!(:cluster) { create(:cluster) } + + it { is_expected.to include(cluster) } + end + end + describe '.missing_kubernetes_namespace' do let!(:cluster) { create(:cluster, :provided_by_gcp, :project) } let(:project) { cluster.project } diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 0281dd2c303..e35d14f2282 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -331,6 +331,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching { key: 'KUBE_TOKEN', value: kubernetes.token, public: false } ) end + + context 'the cluster is not managed' do + let!(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: kubernetes) } + + it_behaves_like 'setting variables' + + it 'sets KUBE_TOKEN' do + expect(subject).to include( + { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } + ) + end + end end context 'kubernetes namespace exists for the project' do diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 94e6ca2c07c..5357be3cdee 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -189,6 +189,7 @@ describe API::ProjectClusters do { name: 'test-cluster', domain: 'domain.example.com', + managed: false, platform_kubernetes_attributes: platform_kubernetes_attributes } end @@ -220,6 +221,7 @@ describe API::ProjectClusters do expect(cluster_result.project).to eq(project) expect(cluster_result.name).to eq('test-cluster') expect(cluster_result.domain).to eq('domain.example.com') + expect(cluster_result.managed).to be_falsy expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.namespace).to eq(namespace) diff --git a/spec/services/clusters/refresh_service_spec.rb b/spec/services/clusters/refresh_service_spec.rb index 9e442ebf4e9..94c35228955 100644 --- a/spec/services/clusters/refresh_service_spec.rb +++ b/spec/services/clusters/refresh_service_spec.rb @@ -121,5 +121,11 @@ describe Clusters::RefreshService do end end end + + context 'cluster is not managed' do + let!(:cluster) { create(:cluster, :project, :not_managed, projects: [project]) } + + include_examples 'does not create a kubernetes namespace' + end end end diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb index bdb8e0e9c84..daf014ac574 100644 --- a/spec/workers/cluster_configure_worker_spec.rb +++ b/spec/workers/cluster_configure_worker_spec.rb @@ -68,6 +68,16 @@ describe ClusterConfigureWorker, '#perform' do it_behaves_like 'configured cluster' end + context 'when cluster is not managed' do + let(:cluster) { create(:cluster, :not_managed) } + + it 'does not configure the cluster' do + expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster) + + 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) -- cgit v1.2.1 From 364d948b8e5f6f8bb0a2c44b50b00f0ef1eb6a1f Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Sat, 27 Apr 2019 00:09:20 +0800 Subject: Fix false positives in filtered search specs Assertions were made based on elements found so the specs still pass even if the elements don't exist --- .../issues/filtered_search/dropdown_hint_spec.rb | 34 +++++++++++----------- spec/support/helpers/filtered_search_helpers.rb | 17 +++++------ 2 files changed, 24 insertions(+), 27 deletions(-) (limited to 'spec') diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 096756f19cc..1f4e9e79179 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -80,7 +80,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-author', visible: true) - expect_tokens([{ name: 'author' }]) + expect_tokens([{ name: 'Author' }]) expect_filtered_search_input_empty end @@ -89,7 +89,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-assignee', visible: true) - expect_tokens([{ name: 'assignee' }]) + expect_tokens([{ name: 'Assignee' }]) expect_filtered_search_input_empty end @@ -98,7 +98,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-milestone', visible: true) - expect_tokens([{ name: 'milestone' }]) + expect_tokens([{ name: 'Milestone' }]) expect_filtered_search_input_empty end @@ -107,7 +107,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-label', visible: true) - expect_tokens([{ name: 'label' }]) + expect_tokens([{ name: 'Label' }]) expect_filtered_search_input_empty end @@ -116,7 +116,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-my-reaction', visible: true) - expect_tokens([{ name: 'my-reaction' }]) + expect_tokens([{ name: 'My-reaction' }]) expect_filtered_search_input_empty end @@ -125,7 +125,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-confidential', visible: true) - expect_tokens([{ name: 'confidential' }]) + expect_tokens([{ name: 'Confidential' }]) expect_filtered_search_input_empty end end @@ -137,7 +137,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-author', visible: true) - expect_tokens([{ name: 'author' }]) + expect_tokens([{ name: 'Author' }]) expect_filtered_search_input_empty end @@ -147,7 +147,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-assignee', visible: true) - expect_tokens([{ name: 'assignee' }]) + expect_tokens([{ name: 'Assignee' }]) expect_filtered_search_input_empty end @@ -157,7 +157,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-milestone', visible: true) - expect_tokens([{ name: 'milestone' }]) + expect_tokens([{ name: 'Milestone' }]) expect_filtered_search_input_empty end @@ -167,7 +167,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-label', visible: true) - expect_tokens([{ name: 'label' }]) + expect_tokens([{ name: 'Label' }]) expect_filtered_search_input_empty end @@ -177,7 +177,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-my-reaction', visible: true) - expect_tokens([{ name: 'my-reaction' }]) + expect_tokens([{ name: 'My-reaction' }]) expect_filtered_search_input_empty end end @@ -189,7 +189,7 @@ describe 'Dropdown hint', :js do filtered_search.send_keys(:backspace) click_hint('author') - expect_tokens([{ name: 'author' }]) + expect_tokens([{ name: 'Author' }]) expect_filtered_search_input_empty end @@ -199,7 +199,7 @@ describe 'Dropdown hint', :js do filtered_search.send_keys(:backspace) click_hint('assignee') - expect_tokens([{ name: 'assignee' }]) + expect_tokens([{ name: 'Assignee' }]) expect_filtered_search_input_empty end @@ -209,7 +209,7 @@ describe 'Dropdown hint', :js do filtered_search.send_keys(:backspace) click_hint('milestone') - expect_tokens([{ name: 'milestone' }]) + expect_tokens([{ name: 'Milestone' }]) expect_filtered_search_input_empty end @@ -219,7 +219,7 @@ describe 'Dropdown hint', :js do filtered_search.send_keys(:backspace) click_hint('label') - expect_tokens([{ name: 'label' }]) + expect_tokens([{ name: 'Label' }]) expect_filtered_search_input_empty end @@ -229,7 +229,7 @@ describe 'Dropdown hint', :js do filtered_search.send_keys(:backspace) click_hint('my-reaction') - expect_tokens([{ name: 'my-reaction' }]) + expect_tokens([{ name: 'My-reaction' }]) expect_filtered_search_input_empty end end @@ -247,7 +247,7 @@ describe 'Dropdown hint', :js do expect(page).to have_css(js_dropdown_hint, visible: false) expect(page).to have_css('#js-dropdown-wip', visible: true) - expect_tokens([{ name: 'wip' }]) + expect_tokens([{ name: 'WIP' }]) expect_filtered_search_input_empty end end diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 03057a102c5..34ef185ea27 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -78,20 +78,17 @@ module FilteredSearchHelpers # .tokens-container to make sure the correct names and values are rendered def expect_tokens(tokens) page.within '.filtered-search-box .tokens-container' do - page.all(:css, '.tokens-container li .selectable').each_with_index do |el, index| - token_name = tokens[index][:name] - token_value = tokens[index][:value] - token_emoji = tokens[index][:emoji_name] + token_elements = page.all(:css, 'li.filtered-search-token') - expect(el.find('.name')).to have_content(token_name) + tokens.each_with_index do |token, index| + el = token_elements[index] - if token_value - expect(el.find('.value')).to have_content(token_value) - end + expect(el.find('.name')).to have_content(token[:name]) + expect(el.find('.value')).to have_content(token[:value]) if token[:value].present? # gl-emoji content is blank when the emoji unicode is not supported - if token_emoji - selector = %(gl-emoji[data-name="#{token_emoji}"]) + if token[:emoji_name].present? + selector = %(gl-emoji[data-name="#{token[:emoji_name]}"]) expect(el.find('.value')).to have_css(selector) end end -- cgit v1.2.1 From 0595c6d5cd45fbf34f8779be544df72f83647915 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 3 May 2019 04:02:15 +0000 Subject: Whitelist query limits for internal API When creating a merge request for push options, there may be over 100 queries that are run to create a merge request. Even after we reduce the number of queries by disabling the Sidekiq jobs, it appears we still hover near this limit. Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/11450 --- spec/requests/api/internal_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 88c19448373..bae0302f3ff 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -959,7 +959,9 @@ describe API::Internal do it 'creates a new merge request' do expect do - post api('/internal/post_receive'), params: valid_params + Sidekiq::Testing.fake! do + post api('/internal/post_receive'), params: valid_params + end end.to change { MergeRequest.count }.by(1) end -- cgit v1.2.1 From b4bc68211bbf87015f31590a30dd04bfde63ad8e Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Fri, 3 May 2019 14:49:45 +0800 Subject: Show full Rspec backtrace only in CI --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fbc5fcea7b9..9266bee34d6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -53,7 +53,7 @@ RSpec.configure do |config| config.display_try_failure_messages = true config.infer_spec_type_from_file_location! - config.full_backtrace = true + config.full_backtrace = !!ENV['CI'] config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| location = metadata[:location] -- cgit v1.2.1 From 9d24d4a8fdd299a1e84f2e549fb58ee526a2f0f9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 3 May 2019 08:33:15 +0100 Subject: Impove the performance of expanding full diff Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/58597 --- .../diffs/components/diff_content_spec.js | 14 ++++ spec/javascripts/diffs/mock_data/diff_file.js | 1 + spec/javascripts/diffs/store/actions_spec.js | 92 ++++++++++++++++----- spec/javascripts/diffs/store/mutations_spec.js | 94 ++++++++++++++++++++++ 4 files changed, 181 insertions(+), 20 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js index bc9288e4150..124bdeea45d 100644 --- a/spec/javascripts/diffs/components/diff_content_spec.js +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -29,6 +29,10 @@ describe('DiffContent', () => { }); }); + afterEach(() => { + vm.$destroy(); + }); + describe('text based files', () => { it('should render diff inline view', done => { vm.$store.state.diffs.diffViewType = 'inline'; @@ -49,6 +53,16 @@ describe('DiffContent', () => { done(); }); }); + + it('renders rendering more lines loading icon', done => { + vm.diffFile.renderingLines = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.loading-container')).not.toBe(null); + + done(); + }); + }); }); describe('empty files', () => { diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js index 32af9ea8ddd..27428197c1c 100644 --- a/spec/javascripts/diffs/mock_data/diff_file.js +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -240,4 +240,5 @@ export default { }, ], discussions: [], + renderingLines: false, }; diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index bca99caa920..c82dcadd2f1 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -36,6 +36,7 @@ import actions, { fetchFullDiff, toggleFullDiff, setFileCollapsed, + setExpandedDiffLines, } from '~/diffs/store/actions'; import eventHub from '~/notes/event_hub'; import * as types from '~/diffs/store/mutation_types'; @@ -879,9 +880,9 @@ describe('DiffsStoreActions', () => { it('commits REQUEST_FULL_DIFF', done => { testAction( receiveFullDiffSucess, - { filePath: 'test', data: 'test' }, + { filePath: 'test' }, {}, - [{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test', data: 'test' } }], + [{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test' } }], [], done, ); @@ -903,11 +904,8 @@ describe('DiffsStoreActions', () => { describe('fetchFullDiff', () => { let mock; - let scrollToElementSpy; beforeEach(() => { - scrollToElementSpy = spyOnDependency(actions, 'scrollToElement').and.stub(); - mock = new MockAdapter(axios); }); @@ -921,28 +919,23 @@ describe('DiffsStoreActions', () => { }); it('dispatches receiveFullDiffSucess', done => { + const file = { + context_lines_path: `${gl.TEST_HOST}/context`, + file_path: 'test', + file_hash: 'test', + }; testAction( fetchFullDiff, - { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' }, + file, null, [], - [{ type: 'receiveFullDiffSucess', payload: { filePath: 'test', data: ['test'] } }], + [ + { type: 'receiveFullDiffSucess', payload: { filePath: 'test' } }, + { type: 'setExpandedDiffLines', payload: { file, data: ['test'] } }, + ], done, ); }); - - it('scrolls to element', done => { - fetchFullDiff( - { dispatch() {} }, - { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' }, - ) - .then(() => { - expect(scrollToElementSpy).toHaveBeenCalledWith('#test'); - - done(); - }) - .catch(done.fail); - }); }); describe('error', () => { @@ -999,4 +992,63 @@ describe('DiffsStoreActions', () => { ); }); }); + + describe('setExpandedDiffLines', () => { + beforeEach(() => { + spyOnDependency(actions, 'idleCallback').and.callFake(cb => { + cb({ timeRemaining: () => 50 }); + }); + }); + + it('commits SET_CURRENT_VIEW_DIFF_FILE_LINES when lines less than MAX_RENDERING_DIFF_LINES', done => { + spyOnDependency(actions, 'convertExpandLines').and.callFake(() => ['test']); + + testAction( + setExpandedDiffLines, + { file: { file_path: 'path' }, data: [] }, + { diffViewType: 'inline' }, + [ + { + type: 'SET_HIDDEN_VIEW_DIFF_FILE_LINES', + payload: { filePath: 'path', lines: ['test'] }, + }, + { + type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES', + payload: { filePath: 'path', lines: ['test'] }, + }, + ], + [], + done, + ); + }); + + it('commits ADD_CURRENT_VIEW_DIFF_FILE_LINES when lines more than MAX_RENDERING_DIFF_LINES', done => { + const lines = new Array(501).fill().map((_, i) => `line-${i}`); + spyOnDependency(actions, 'convertExpandLines').and.callFake(() => lines); + + testAction( + setExpandedDiffLines, + { file: { file_path: 'path' }, data: [] }, + { diffViewType: 'inline' }, + [ + { + type: 'SET_HIDDEN_VIEW_DIFF_FILE_LINES', + payload: { filePath: 'path', lines }, + }, + { + type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES', + payload: { filePath: 'path', lines: lines.slice(0, 200) }, + }, + { type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' }, + ...new Array(301).fill().map((_, i) => ({ + type: 'ADD_CURRENT_VIEW_DIFF_FILE_LINES', + payload: { filePath: 'path', line: `line-${i + 200}` }, + })), + { type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' }, + ], + [], + done, + ); + }); + }); }); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 5556a994a14..fa193e1d3b9 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -756,4 +756,98 @@ describe('DiffsStoreMutations', () => { expect(state.diffFiles[0].viewer.collapsed).toBe(true); }); }); + + describe('SET_HIDDEN_VIEW_DIFF_FILE_LINES', () => { + [ + { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' }, + { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' }, + ].forEach(({ current, hidden, diffViewType }) => { + it(`sets the ${hidden} lines when diff view is ${diffViewType}`, () => { + const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] }; + const state = { + diffFiles: [file], + diffViewType, + }; + + mutations[types.SET_HIDDEN_VIEW_DIFF_FILE_LINES](state, { + filePath: 'test', + lines: ['test'], + }); + + expect(file[`${current}_diff_lines`]).toEqual([]); + expect(file[`${hidden}_diff_lines`]).toEqual(['test']); + }); + }); + }); + + describe('SET_CURRENT_VIEW_DIFF_FILE_LINES', () => { + [ + { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' }, + { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' }, + ].forEach(({ current, hidden, diffViewType }) => { + it(`sets the ${current} lines when diff view is ${diffViewType}`, () => { + const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] }; + const state = { + diffFiles: [file], + diffViewType, + }; + + mutations[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { + filePath: 'test', + lines: ['test'], + }); + + expect(file[`${current}_diff_lines`]).toEqual(['test']); + expect(file[`${hidden}_diff_lines`]).toEqual([]); + }); + }); + }); + + describe('ADD_CURRENT_VIEW_DIFF_FILE_LINES', () => { + [ + { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' }, + { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' }, + ].forEach(({ current, hidden, diffViewType }) => { + it(`pushes to ${current} lines when diff view is ${diffViewType}`, () => { + const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] }; + const state = { + diffFiles: [file], + diffViewType, + }; + + mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, { + filePath: 'test', + line: 'test', + }); + + expect(file[`${current}_diff_lines`]).toEqual(['test']); + expect(file[`${hidden}_diff_lines`]).toEqual([]); + + mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, { + filePath: 'test', + line: 'test2', + }); + + expect(file[`${current}_diff_lines`]).toEqual(['test', 'test2']); + expect(file[`${hidden}_diff_lines`]).toEqual([]); + }); + }); + }); + + describe('TOGGLE_DIFF_FILE_RENDERING_MORE', () => { + it('toggles renderingLines on file', () => { + const file = { file_path: 'test', renderingLines: false }; + const state = { + diffFiles: [file], + }; + + mutations[types.TOGGLE_DIFF_FILE_RENDERING_MORE](state, 'test'); + + expect(file.renderingLines).toBe(true); + + mutations[types.TOGGLE_DIFF_FILE_RENDERING_MORE](state, 'test'); + + expect(file.renderingLines).toBe(false); + }); + }); }); -- cgit v1.2.1 From b0fbf001dab134b6638411f0be209bc0d1460519 Mon Sep 17 00:00:00 2001 From: Patrick Derichs Date: Fri, 3 May 2019 15:09:20 +0200 Subject: Fix url redaction for issue links Add changelog entry Add missing href to all redactor specs and removed href assignment Remove obsolete spec If original_content is given, it should be used for link content --- spec/lib/banzai/redactor_spec.rb | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'spec') diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb index aaeec953e4b..718649e0e10 100644 --- a/spec/lib/banzai/redactor_spec.rb +++ b/spec/lib/banzai/redactor_spec.rb @@ -13,10 +13,10 @@ describe Banzai::Redactor do it 'redacts an array of documents' do doc1 = Nokogiri::HTML - .fragment('foo') + .fragment('foo') doc2 = Nokogiri::HTML - .fragment('bar') + .fragment('bar') redacted_data = redactor.redact([doc1, doc2]) @@ -27,7 +27,7 @@ describe Banzai::Redactor do end it 'replaces redacted reference with inner HTML' do - doc = Nokogiri::HTML.fragment("foo") + doc = Nokogiri::HTML.fragment("foo") redactor.redact([doc]) expect(doc.to_html).to eq('foo') end @@ -35,20 +35,24 @@ describe Banzai::Redactor do context 'when data-original attribute provided' do let(:original_content) { 'foo' } it 'replaces redacted reference with original content' do - doc = Nokogiri::HTML.fragment("bar") + doc = Nokogiri::HTML.fragment("bar") redactor.redact([doc]) expect(doc.to_html).to eq(original_content) end - end - - it 'returns tag with original href if it is originally a link reference' do - href = 'http://localhost:3000' - doc = Nokogiri::HTML - .fragment("#{href}") - redactor.redact([doc]) + it 'does not replace redacted reference with original content if href is given' do + html = "Marge" + doc = Nokogiri::HTML.fragment(html) + redactor.redact([doc]) + expect(doc.to_html).to eq('Marge') + end - expect(doc.to_html).to eq('http://localhost:3000') + it 'uses the original content as the link content if given' do + html = "Marge" + doc = Nokogiri::HTML.fragment(html) + redactor.redact([doc]) + expect(doc.to_html).to eq('Homer') + end end end @@ -61,7 +65,7 @@ describe Banzai::Redactor do end it 'redacts an issue attached' do - doc = Nokogiri::HTML.fragment("foo") + doc = Nokogiri::HTML.fragment("foo") redactor.redact([doc]) @@ -69,7 +73,7 @@ describe Banzai::Redactor do end it 'redacts an external issue' do - doc = Nokogiri::HTML.fragment("foo") + doc = Nokogiri::HTML.fragment("foo") redactor.redact([doc]) -- cgit v1.2.1 From 241ba4be7989547b3bc3f9a1a20b8dee7a4e9a0c Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Fri, 3 May 2019 13:29:20 +0000 Subject: Allow guests users to access project releases This is step one of resolving https://gitlab.com/gitlab-org/gitlab-ce/issues/56838. Here is what changed: - Revert the security fix from bdee9e8412d. - Do not leak repository information (tag name, commit) to guests in API responses. - Do not include links to source code in API responses for users that do not have download_code access. - Show Releases in sidebar for guests. - Do not display links to source code under Assets for users that do not have download_code access. GET ':id/releases/:tag_name' still do not allow guests to access releases. This is to prevent guessing tag existence. --- .../api/schemas/public_api/v4/release.json | 35 ++++++++++++---- .../public_api/v4/release/release_for_guest.json | 22 ++++++++++ .../public_api/v4/release/releases_for_guest.json | 4 ++ .../schemas/public_api/v4/release/tag_release.json | 12 ++++++ .../api/schemas/public_api/v4/releases.json | 4 ++ spec/fixtures/api/schemas/public_api/v4/tag.json | 2 +- spec/models/release_spec.rb | 5 +++ spec/policies/project_policy_spec.rb | 4 +- spec/requests/api/releases_spec.rb | 49 ++++++++++++++++++---- spec/requests/api/tags_spec.rb | 2 +- .../policies/project_policy_shared_context.rb | 3 +- 11 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/release/tag_release.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/releases.json (limited to 'spec') diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json index 6612c2a9911..6ea0781c1ed 100644 --- a/spec/fixtures/api/schemas/public_api/v4/release.json +++ b/spec/fixtures/api/schemas/public_api/v4/release.json @@ -1,12 +1,33 @@ { "type": "object", - "required" : [ - "tag_name", - "description" - ], - "properties" : { - "tag_name": { "type": ["string", "null"] }, - "description": { "type": "string" } + "required": ["name", "tag_name", "commit"], + "properties": { + "name": { "type": "string" }, + "tag_name": { "type": "string" }, + "description": { "type": "string" }, + "description_html": { "type": "string" }, + "created_at": { "type": "date" }, + "commit": { + "oneOf": [{ "type": "null" }, { "$ref": "commit/basic.json" }] + }, + "author": { + "oneOf": [{ "type": "null" }, { "$ref": "user/basic.json" }] + }, + "assets": { + "required": ["count", "links", "sources"], + "properties": { + "count": { "type": "integer" }, + "links": { "$ref": "../../release/links.json" }, + "sources": { + "type": "array", + "items": { + "format": "zip", + "url": "string" + } + } + }, + "additionalProperties": false + } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json new file mode 100644 index 00000000000..e78398ad1d5 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "description_html": { "type": "string" }, + "created_at": { "type": "date" }, + "author": { + "oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }] + }, + "assets": { + "required": ["count", "links"], + "properties": { + "count": { "type": "integer" }, + "links": { "$ref": "../../../release/links.json" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json new file mode 100644 index 00000000000..c13966b28e9 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "release_for_guest.json" } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json b/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json new file mode 100644 index 00000000000..6612c2a9911 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required" : [ + "tag_name", + "description" + ], + "properties" : { + "tag_name": { "type": ["string", "null"] }, + "description": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/releases.json b/spec/fixtures/api/schemas/public_api/v4/releases.json new file mode 100644 index 00000000000..e26215707fe --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/releases.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "release.json" } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/tag.json b/spec/fixtures/api/schemas/public_api/v4/tag.json index 10d4edb7ffb..5713ea1f526 100644 --- a/spec/fixtures/api/schemas/public_api/v4/tag.json +++ b/spec/fixtures/api/schemas/public_api/v4/tag.json @@ -14,7 +14,7 @@ "release": { "oneOf": [ { "type": "null" }, - { "$ref": "release.json" } + { "$ref": "release/tag_release.json" } ] } }, diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 0b19a4f8efc..7c106ce6b85 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -49,6 +49,11 @@ RSpec.describe Release do it 'counts the link as an asset' do is_expected.to eq(1 + Releases::Source::FORMATS.count) end + + it "excludes sources count when asked" do + assets_count = release.assets_count(except: [:sources]) + expect(assets_count).to eq(1) + end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 42f8bf3137b..8075fcade5f 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -17,7 +17,7 @@ describe ProjectPolicy do read_project_for_iids read_issue_iid read_label read_milestone read_project_snippet read_project_member read_note create_project create_issue create_note upload_file create_merge_request_in - award_emoji + award_emoji read_release ] end @@ -26,7 +26,7 @@ describe ProjectPolicy do download_code fork_project create_project_snippet update_issue admin_issue admin_label admin_list read_commit_status read_build read_container_image read_pipeline read_environment read_deployment - read_merge_request download_wiki_code read_sentry_issue read_release + read_merge_request download_wiki_code read_sentry_issue ] end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 71ec091c42c..8603fa2a73d 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -52,7 +52,7 @@ describe API::Releases do it 'matches response schema' do get api("/projects/#{project.id}/releases", maintainer) - expect(response).to match_response_schema('releases') + expect(response).to match_response_schema('public_api/v4/releases') end end @@ -69,10 +69,25 @@ describe API::Releases do end context 'when user is a guest' do - it 'responds 403 Forbidden' do + let!(:release) do + create(:release, + project: project, + tag: 'v0.1', + author: maintainer, + created_at: 2.days.ago) + end + + it 'responds 200 OK' do get api("/projects/#{project.id}/releases", guest) - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:ok) + end + + it "does not expose tag, commit and source code" do + get api("/projects/#{project.id}/releases", guest) + + expect(response).to match_response_schema('public_api/v4/release/releases_for_guest') + expect(json_response[0]['assets']['count']).to eq(release.links.count) end context 'when project is public' do @@ -83,6 +98,13 @@ describe API::Releases do expect(response).to have_gitlab_http_status(:ok) end + + it "exposes tag, commit and source code" do + get api("/projects/#{project.id}/releases", guest) + + expect(response).to match_response_schema('public_api/v4/releases') + expect(json_response[0]['assets']['count']).to eq(release.links.count + release.sources.count) + end end end @@ -135,7 +157,7 @@ describe API::Releases do it 'matches response schema' do get api("/projects/#{project.id}/releases/v0.1", maintainer) - expect(response).to match_response_schema('release') + expect(response).to match_response_schema('public_api/v4/release') end it 'contains source information as assets' do @@ -225,6 +247,17 @@ describe API::Releases do expect(response).to have_gitlab_http_status(:ok) end + + it "exposes tag and commit" do + create(:release, + project: project, + tag: 'v0.1', + author: maintainer, + created_at: 2.days.ago) + get api("/projects/#{project.id}/releases/v0.1", guest) + + expect(response).to match_response_schema('public_api/v4/release') + end end end end @@ -306,7 +339,7 @@ describe API::Releases do it 'matches response schema' do post api("/projects/#{project.id}/releases", maintainer), params: params - expect(response).to match_response_schema('release') + expect(response).to match_response_schema('public_api/v4/release') end it 'does not create a new tag' do @@ -378,7 +411,7 @@ describe API::Releases do it 'matches response schema' do post api("/projects/#{project.id}/releases", maintainer), params: params - expect(response).to match_response_schema('release') + expect(response).to match_response_schema('public_api/v4/release') end end @@ -532,7 +565,7 @@ describe API::Releases do it 'matches response schema' do put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params - expect(response).to match_response_schema('release') + expect(response).to match_response_schema('public_api/v4/release') end context 'when user tries to update sha' do @@ -624,7 +657,7 @@ describe API::Releases do it 'matches response schema' do delete api("/projects/#{project.id}/releases/v0.1", maintainer) - expect(response).to match_response_schema('release') + expect(response).to match_response_schema('public_api/v4/release') end context 'when there are no corresponding releases' do diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index fffe878ddbd..d898319e709 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -378,7 +378,7 @@ describe API::Tags do post api(route, user), params: { description: description } expect(response).to have_gitlab_http_status(201) - expect(response).to match_response_schema('public_api/v4/release') + expect(response).to match_response_schema('public_api/v4/release/tag_release') expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(description) end diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb index ee5cfcd850d..54d9f5b15f2 100644 --- a/spec/support/shared_context/policies/project_policy_shared_context.rb +++ b/spec/support/shared_context/policies/project_policy_shared_context.rb @@ -24,8 +24,7 @@ RSpec.shared_context 'ProjectPolicy context' do download_code fork_project create_project_snippet update_issue admin_issue admin_label admin_list read_commit_status read_build read_container_image read_pipeline read_environment read_deployment - read_merge_request download_wiki_code read_sentry_issue read_release - read_prometheus + read_merge_request download_wiki_code read_sentry_issue read_prometheus ] end -- cgit v1.2.1 From 1fb499de278ead55384b13f659a0acfc3680e37f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 29 Apr 2019 12:22:25 +0100 Subject: Add leave link to access_granted email Allows users to leave a project/group that they have been added to. Add function to leave a namespace by url param If the `leave` param is present on a project/group show page, click the leave link. --- spec/features/groups/members/leave_group_spec.rb | 26 ++++++++++++++++++++-- ...group_member_cannot_leave_group_project_spec.rb | 9 +++++++- .../projects/members/member_leaves_project_spec.rb | 13 ++++++++++- spec/mailers/notify_spec.rb | 4 ++++ 4 files changed, 48 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index 7a91c64d7db..439803f9255 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -21,6 +21,20 @@ describe 'Groups > Members > Leave group' do expect(group.users).not_to include(user) end + it 'guest leaves the group by url param', :js do + group.add_guest(user) + group.add_owner(other_user) + + visit group_path(group, leave: 1) + + page.accept_confirm + + expect(find('.flash-notice')).to have_content "You left the \"#{group.full_name}\" group" + expect(page).to have_content left_group_message(group) + expect(current_path).to eq(dashboard_groups_path) + expect(group.users).not_to include(user) + end + it 'guest leaves the group as last member' do group.add_guest(user) @@ -32,7 +46,7 @@ describe 'Groups > Members > Leave group' do expect(group.users).not_to include(user) end - it 'owner leaves the group if they is not the last owner' do + it 'owner leaves the group if they are not the last owner' do group.add_owner(user) group.add_owner(other_user) @@ -44,7 +58,7 @@ describe 'Groups > Members > Leave group' do expect(group.users).not_to include(user) end - it 'owner can not leave the group if they is a last owner' do + it 'owner can not leave the group if they are the last owner' do group.add_owner(user) visit group_path(group) @@ -56,6 +70,14 @@ describe 'Groups > Members > Leave group' do expect(find(:css, '.project-members-page li', text: user.name)).not_to have_selector(:css, 'a.btn-remove') end + it 'owner can not leave the group by url param if they are the last owner', :js do + group.add_owner(user) + + visit group_path(group, leave: 1) + + expect(find('.flash-alert')).to have_content 'You do not have permission to leave this group' + end + def left_group_message(group) "You left the \"#{group.name}\"" end diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb index 0ab29660189..a645b917568 100644 --- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb @@ -8,10 +8,17 @@ describe 'Projects > Members > Group member cannot leave group project' do before do group.add_developer(user) sign_in(user) - visit project_path(project) end it 'user does not see a "Leave project" link' do + visit project_path(project) + expect(page).not_to have_content 'Leave project' end + + it 'renders a flash message if attempting to leave by url', :js do + visit project_path(project, leave: 1) + + expect(find('.flash-alert')).to have_content 'You do not have permission to leave this project' + end end diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 94b29de4686..bd2ef9c07c4 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -7,13 +7,24 @@ describe 'Projects > Members > Member leaves project' do before do project.add_developer(user) sign_in(user) - visit project_path(project) end it 'user leaves project' do + visit project_path(project) + click_link 'Leave project' expect(current_path).to eq(dashboard_projects_path) expect(project.users.exists?(user.id)).to be_falsey end + + it 'user leaves project by url param', :js do + visit project_path(project, leave: 1) + + page.accept_confirm + + expect(find('.flash-notice')).to have_content "You left the \"#{project.full_name}\" project" + expect(current_path).to eq(dashboard_projects_path) + expect(project.users.exists?(user.id)).to be_falsey + end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index fee1d701e3a..8f348b1b053 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -701,6 +701,8 @@ describe Notify do is_expected.to have_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.human_access + is_expected.to have_body_text 'leave the project' + is_expected.to have_body_text project_url(project, leave: 1) end end @@ -1144,6 +1146,8 @@ describe Notify do is_expected.to have_body_text group.name is_expected.to have_body_text group.web_url is_expected.to have_body_text group_member.human_access + is_expected.to have_body_text 'leave the group' + is_expected.to have_body_text group_url(group, leave: 1) end end -- cgit v1.2.1 From 5e1e9eb29968111f8ea3538e0cb1e34fab41af7d Mon Sep 17 00:00:00 2001 From: mfluharty Date: Thu, 14 Mar 2019 14:38:57 -0600 Subject: Separate tests for triggerer component --- .../pipelines/pipeline_triggerer_spec.js | 54 ++++++++++++++++++++++ spec/javascripts/pipelines/pipeline_url_spec.js | 48 ------------------- .../pipelines/pipelines_table_row_spec.js | 4 +- 3 files changed, 56 insertions(+), 50 deletions(-) create mode 100644 spec/javascripts/pipelines/pipeline_triggerer_spec.js (limited to 'spec') diff --git a/spec/javascripts/pipelines/pipeline_triggerer_spec.js b/spec/javascripts/pipelines/pipeline_triggerer_spec.js new file mode 100644 index 00000000000..8cf290f2663 --- /dev/null +++ b/spec/javascripts/pipelines/pipeline_triggerer_spec.js @@ -0,0 +1,54 @@ +import { mount } from '@vue/test-utils'; +import pipelineTriggerer from '~/pipelines/components/pipeline_triggerer.vue'; + +describe('Pipelines Triggerer', () => { + let wrapper; + + const mockData = { + pipeline: { + user: { + name: 'foo', + avatar_url: '/avatar', + path: '/path', + }, + }, + }; + + const createComponent = () => { + wrapper = mount(pipelineTriggerer, { + propsData: mockData, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should render a table cell', () => { + expect(wrapper.contains('.table-section')).toBe(true); + }); + + it('should render triggerer information when triggerer is provided', () => { + const link = wrapper.find('.js-pipeline-url-user'); + + expect(link.attributes('href')).toEqual(mockData.pipeline.user.path); + expect(link.find('.js-user-avatar-image-toolip').text()).toEqual(mockData.pipeline.user.name); + expect(link.find('img.avatar').attributes('src')).toEqual( + `${mockData.pipeline.user.avatar_url}?width=26`, + ); + }); + + it('should render "API" when no triggerer is provided', () => { + wrapper.setProps({ + pipeline: { + user: null, + }, + }); + + expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API'); + }); +}); diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index faad49a78b0..aa196af2f33 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -42,54 +42,6 @@ describe('Pipeline Url Component', () => { expect(component.$el.querySelector('.js-pipeline-url-link span').textContent).toEqual('#1'); }); - it('should render user information when a user is provided', () => { - const mockData = { - pipeline: { - id: 1, - path: 'foo', - flags: {}, - user: { - web_url: '/', - name: 'foo', - avatar_url: '/', - path: '/', - }, - }, - autoDevopsHelpPath: 'foo', - }; - - const component = new PipelineUrlComponent({ - propsData: mockData, - }).$mount(); - - const image = component.$el.querySelector('.js-pipeline-url-user img'); - const tooltip = component.$el.querySelector( - '.js-pipeline-url-user .js-user-avatar-image-toolip', - ); - - expect(component.$el.querySelector('.js-pipeline-url-user').getAttribute('href')).toEqual( - mockData.pipeline.user.web_url, - ); - - expect(tooltip.textContent.trim()).toEqual(mockData.pipeline.user.name); - expect(image.getAttribute('src')).toEqual(`${mockData.pipeline.user.avatar_url}?width=20`); - }); - - it('should render "API" when no user is provided', () => { - const component = new PipelineUrlComponent({ - propsData: { - pipeline: { - id: 1, - path: 'foo', - flags: {}, - }, - autoDevopsHelpPath: 'foo', - }, - }).$mount(); - - expect(component.$el.querySelector('.js-pipeline-url-api').textContent).toContain('API'); - }); - it('should render latest, yaml invalid, merge request, and stuck flags when provided', () => { const component = new PipelineUrlComponent({ propsData: { diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js index 234fc705a81..d47504d2f54 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js @@ -80,13 +80,13 @@ describe('Pipelines Table Row', () => { it('should render user information', () => { expect( component.$el - .querySelector('.table-section:nth-child(2) a:nth-child(3)') + .querySelector('.table-section:nth-child(3) .js-pipeline-url-user') .getAttribute('href'), ).toEqual(pipeline.user.path); expect( component.$el - .querySelector('.table-section:nth-child(2) .js-user-avatar-image-toolip') + .querySelector('.table-section:nth-child(3) .js-user-avatar-image-toolip') .textContent.trim(), ).toEqual(pipeline.user.name); }); -- cgit v1.2.1 From 0e033b31248a48bd02ae67aada106f99a2af53f9 Mon Sep 17 00:00:00 2001 From: John Cai Date: Tue, 30 Apr 2019 21:45:19 -0700 Subject: Add client methods for DisconnectGitAlternates rpc DisconnectGitAlternates is a Gitaly RPC that will delete a repository's objects/info/alternates file. This commit adds client support for that RPC. --- spec/lib/gitlab/git/object_pool_spec.rb | 6 ++-- spec/lib/gitlab/git/repository_spec.rb | 39 ++++++++++++++++++++++ .../gitaly_client/repository_service_spec.rb | 30 +++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb index 6511c2b61bf..ebeb7b7b633 100644 --- a/spec/lib/gitlab/git/object_pool_spec.rb +++ b/spec/lib/gitlab/git/object_pool_spec.rb @@ -7,8 +7,6 @@ describe Gitlab::Git::ObjectPool do let(:pool_repository) { create(:pool_repository) } let(:source_repository) { pool_repository.source_project.repository } - let(:source_repository_path) { File.join(TestEnv.repos_path, source_repository.relative_path) } - let(:source_repository_rugged) { Rugged::Repository.new(source_repository_path) } subject { pool_repository.object_pool } @@ -82,6 +80,8 @@ describe Gitlab::Git::ObjectPool do end describe '#fetch' do + let(:source_repository_path) { File.join(TestEnv.repos_path, source_repository.relative_path) } + let(:source_repository_rugged) { Rugged::Repository.new(source_repository_path) } let(:commit_count) { source_repository.commit_count } context "when the object's pool repository exists" do @@ -98,7 +98,7 @@ describe Gitlab::Git::ObjectPool do it "re-creates the object pool's repository" do subject.fetch - expect(subject.repository.exists?).to be(true) + expect(subject.repository.exists?).to be true end it 'does not raise an error' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 0f6aac9b6de..7644d83992f 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2215,4 +2215,43 @@ describe Gitlab::Git::Repository, :seed_helper do line.split("\t").last end end + + describe '#disconnect_alternates' do + let(:project) { create(:project, :repository) } + let(:pool_repository) { create(:pool_repository) } + let(:repository) { project.repository } + let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) } + let(:object_pool) { pool_repository.object_pool } + let(:object_pool_path) { File.join(TestEnv.repos_path, object_pool.repository.relative_path) } + let(:object_pool_rugged) { Rugged::Repository.new(object_pool_path) } + + before do + object_pool.create + end + + it 'does not raise an error when disconnecting a non-linked repository' do + expect { repository.disconnect_alternates }.not_to raise_error + end + + it 'removes the alternates file' do + object_pool.link(repository) + + alternates_file = File.join(repository_path, "objects", "info", "alternates") + expect(File.exist?(alternates_file)).to be_truthy + + repository.disconnect_alternates + + expect(File.exist?(alternates_file)).to be_falsey + end + + it 'can still access objects in the object pool' do + object_pool.link(repository) + new_commit = new_commit_edit_old_file(object_pool_rugged) + expect(repository.commit(new_commit.oid).id).to eq(new_commit.oid) + + repository.disconnect_alternates + + expect(repository.commit(new_commit.oid).id).to eq(new_commit.oid) + end + end end diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index 46ca2340389..09de7ca6afd 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -231,4 +231,34 @@ describe Gitlab::GitalyClient::RepositoryService do client.raw_changes_between('deadbeef', 'deadpork') end end + + describe '#disconnect_alternates' do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) } + let(:pool_repository) { create(:pool_repository) } + let(:object_pool) { pool_repository.object_pool } + let(:object_pool_service) { Gitlab::GitalyClient::ObjectPoolService.new(object_pool) } + + before do + object_pool_service.create(repository) + object_pool_service.link_repository(repository) + end + + it 'deletes the alternates file' do + repository.disconnect_alternates + + alternates_file = File.join(repository_path, "objects", "info", "alternates") + + expect(File.exist?(alternates_file)).to be_falsey + end + + context 'when called twice' do + it "doesn't raise an error" do + repository.disconnect_alternates + + expect { repository.disconnect_alternates }.not_to raise_error + end + end + end end -- cgit v1.2.1 From 0a89c08c055576a192754e9bce368231e2d0d13c Mon Sep 17 00:00:00 2001 From: Nermin Vehabovic Date: Sat, 4 May 2019 16:16:47 +0000 Subject: Resolve "Sort by due date and popularity in both directions" --- spec/features/issuables/sorting_list_spec.rb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'spec') diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb index 0601dd47c03..3a46a4e0167 100644 --- a/spec/features/issuables/sorting_list_spec.rb +++ b/spec/features/issuables/sorting_list_spec.rb @@ -86,26 +86,26 @@ describe 'Sort Issuable List' do expect(last_merge_request).to include(first_created_issuable.title) end end + end - context 'custom sorting' do - let(:issuable_type) { :merge_request } + context 'custom sorting' do + let(:issuable_type) { :merge_request } - it 'supports sorting in asc and desc order' do - visit_merge_requests_with_state(project, 'open') + it 'supports sorting in asc and desc order' do + visit_merge_requests_with_state(project, 'open') - page.within('.issues-other-filters') do - click_button('Created date') - click_link('Last updated') - end + page.within('.issues-other-filters') do + click_button('Created date') + click_link('Last updated') + end - expect(first_merge_request).to include(last_updated_issuable.title) - expect(last_merge_request).to include(first_updated_issuable.title) + expect(first_merge_request).to include(last_updated_issuable.title) + expect(last_merge_request).to include(first_updated_issuable.title) - find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click + find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click - expect(first_merge_request).to include(first_updated_issuable.title) - expect(last_merge_request).to include(last_updated_issuable.title) - end + expect(first_merge_request).to include(first_updated_issuable.title) + expect(last_merge_request).to include(last_updated_issuable.title) end end end -- cgit v1.2.1 From f93b2e02a56a3b1f3041119e8302d43aeafc8284 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 5 May 2019 03:19:14 -0700 Subject: Run rubocop -a on CE files --- spec/initializers/secret_token_spec.rb | 4 ++-- spec/lib/gitlab/path_regex_spec.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb index 6366be30079..726ce07a2d1 100644 --- a/spec/initializers/secret_token_spec.rb +++ b/spec/initializers/secret_token_spec.rb @@ -6,8 +6,8 @@ describe 'create_tokens' do let(:secrets) { ActiveSupport::OrderedOptions.new } - HEX_KEY = /\h{128}/ - RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m + HEX_KEY = /\h{128}/.freeze + RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze before do allow(File).to receive(:write) diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 857862a2abd..84b2e2dc823 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -120,10 +120,10 @@ describe Gitlab::PathRegex do # - Followed by one or more path-parts not starting with `:` or `*` # - Followed by a path-part that includes a wildcard parameter `*` # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw - STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id} - NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*} - ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*} - WILDCARD_SEGMENT = /\*/ + STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}.freeze + NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}.freeze + ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}.freeze + WILDCARD_SEGMENT = /\*/.freeze let(:namespaced_wildcard_routes) do routes_without_format.select do |p| p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}} @@ -144,7 +144,7 @@ describe Gitlab::PathRegex do end.uniq end - STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/} + STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}.freeze let(:group_routes) do routes_without_format.select do |path| path =~ STARTING_WITH_GROUP -- cgit v1.2.1 From d4d2cf7327ed077e063d64dcfe3b8275286c2d06 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 27 Mar 2019 15:46:29 +0700 Subject: Fix merge request pipeline exist method Refactor --- spec/factories/merge_requests.rb | 6 +++--- spec/lib/gitlab/import_export/all_models.yml | 2 +- spec/models/ci/build_spec.rb | 4 ++-- spec/models/ci/pipeline_spec.rb | 2 +- spec/models/concerns/has_ref_spec.rb | 4 ++-- spec/serializers/pipeline_entity_spec.rb | 4 ++-- .../services/merge_requests/create_service_spec.rb | 6 +++--- .../merge_requests/refresh_service_spec.rb | 22 +++++++++++----------- 8 files changed, 25 insertions(+), 25 deletions(-) (limited to 'spec') diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index abf0e6bccb7..e8df5094b83 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -119,7 +119,7 @@ FactoryBot.define do trait :with_legacy_detached_merge_request_pipeline do after(:create) do |merge_request| - merge_request.merge_request_pipelines << create(:ci_pipeline, + merge_request.pipelines_for_merge_request << create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, project: merge_request.source_project, @@ -130,7 +130,7 @@ FactoryBot.define do trait :with_detached_merge_request_pipeline do after(:create) do |merge_request| - merge_request.merge_request_pipelines << create(:ci_pipeline, + merge_request.pipelines_for_merge_request << create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, project: merge_request.source_project, @@ -147,7 +147,7 @@ FactoryBot.define do end after(:create) do |merge_request, evaluator| - merge_request.merge_request_pipelines << create(:ci_pipeline, + merge_request.pipelines_for_merge_request << create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, project: merge_request.source_project, diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 482e9c05da8..2242543daad 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -99,7 +99,7 @@ merge_requests: - timelogs - head_pipeline - latest_merge_request_diff -- merge_request_pipelines +- pipelines_for_merge_request - merge_request_assignees - suggestions - assignees diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 8c73f37bd32..9b489baf163 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2817,7 +2817,7 @@ describe Ci::Build do context 'when ref is merge request' do let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } - let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:pipeline) { merge_request.pipelines_for_merge_request.first } let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) } context 'when ref is protected' do @@ -2875,7 +2875,7 @@ describe Ci::Build do context 'when ref is merge request' do let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } - let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:pipeline) { merge_request.pipelines_for_merge_request.first } let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) } context 'when ref is protected' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index af455a72f50..a0319b3eb0a 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -466,7 +466,7 @@ describe Ci::Pipeline, :mailer do target_branch: 'master') end - let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:pipeline) { merge_request.pipelines_for_merge_request.first } it 'does not return the pipeline' do is_expected.to be_empty diff --git a/spec/models/concerns/has_ref_spec.rb b/spec/models/concerns/has_ref_spec.rb index 6805731fed3..66b25c77430 100644 --- a/spec/models/concerns/has_ref_spec.rb +++ b/spec/models/concerns/has_ref_spec.rb @@ -19,7 +19,7 @@ describe HasRef do context 'when it was triggered by merge request' do let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } - let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:pipeline) { merge_request.pipelines_for_merge_request.first } let(:build) { create(:ci_build, pipeline: pipeline) } it 'returns false' do @@ -68,7 +68,7 @@ describe HasRef do context 'when it is triggered by a merge request' do let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } - let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:pipeline) { merge_request.pipelines_for_merge_request.first } let(:build) { create(:ci_build, tag: false, pipeline: pipeline) } it 'returns nil' do diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index dba7fd91747..47f767ae4ab 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -137,7 +137,7 @@ describe PipelineEntity do context 'when pipeline is detached merge request pipeline' do let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } let(:project) { merge_request.target_project } - let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:pipeline) { merge_request.pipelines_for_merge_request.first } it 'makes detached flag true' do expect(subject[:flags][:detached_merge_request_pipeline]).to be_truthy @@ -185,7 +185,7 @@ describe PipelineEntity do context 'when pipeline is merge request pipeline' do let(:merge_request) { create(:merge_request, :with_merge_request_pipeline, merge_sha: 'abc') } let(:project) { merge_request.target_project } - let(:pipeline) { merge_request.merge_request_pipelines.first } + let(:pipeline) { merge_request.pipelines_for_merge_request.first } it 'makes detached flag false' do expect(subject[:flags][:detached_merge_request_pipeline]).to be_falsy diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index c795176a1e4..ed48f4b1e44 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -195,7 +195,7 @@ describe MergeRequests::CreateService do expect(merge_request).to be_persisted merge_request.reload - expect(merge_request.merge_request_pipelines.count).to eq(1) + expect(merge_request.pipelines_for_merge_request.count).to eq(1) expect(merge_request.actual_head_pipeline).to be_detached_merge_request_pipeline end @@ -247,7 +247,7 @@ describe MergeRequests::CreateService do expect(merge_request).to be_persisted merge_request.reload - expect(merge_request.merge_request_pipelines.count).to eq(0) + expect(merge_request.pipelines_for_merge_request.count).to eq(0) end end @@ -281,7 +281,7 @@ describe MergeRequests::CreateService do expect(merge_request).to be_persisted merge_request.reload - expect(merge_request.merge_request_pipelines.count).to eq(0) + expect(merge_request.pipelines_for_merge_request.count).to eq(0) end end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index d20b2d81763..7258428589f 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -166,8 +166,8 @@ describe MergeRequests::RefreshService do it 'create detached merge request pipeline with commits' do expect { subject } - .to change { @merge_request.merge_request_pipelines.count }.by(1) - .and change { @another_merge_request.merge_request_pipelines.count }.by(0) + .to change { @merge_request.pipelines_for_merge_request.count }.by(1) + .and change { @another_merge_request.pipelines_for_merge_request.count }.by(0) expect(@merge_request.has_commits?).to be_truthy expect(@another_merge_request.has_commits?).to be_falsy @@ -175,13 +175,13 @@ describe MergeRequests::RefreshService do it 'does not create detached merge request pipeline for forked project' do expect { subject } - .not_to change { @fork_merge_request.merge_request_pipelines.count } + .not_to change { @fork_merge_request.pipelines_for_merge_request.count } end it 'create detached merge request pipeline for non-fork merge request' do subject - expect(@merge_request.merge_request_pipelines.first) + expect(@merge_request.pipelines_for_merge_request.first) .to be_detached_merge_request_pipeline end @@ -190,7 +190,7 @@ describe MergeRequests::RefreshService do it 'does not create detached merge request pipeline' do expect { subject } - .not_to change { @merge_request.merge_request_pipelines.count } + .not_to change { @merge_request.pipelines_for_merge_request.count } end end @@ -199,9 +199,9 @@ describe MergeRequests::RefreshService do it 'creates legacy detached merge request pipeline for fork merge request' do expect { subject } - .to change { @fork_merge_request.merge_request_pipelines.count }.by(1) + .to change { @fork_merge_request.pipelines_for_merge_request.count }.by(1) - expect(@fork_merge_request.merge_request_pipelines.first) + expect(@fork_merge_request.pipelines_for_merge_request.first) .to be_legacy_detached_merge_request_pipeline end end @@ -214,7 +214,7 @@ describe MergeRequests::RefreshService do it 'create legacy detached merge request pipeline for non-fork merge request' do subject - expect(@merge_request.merge_request_pipelines.first) + expect(@merge_request.pipelines_for_merge_request.first) .to be_legacy_detached_merge_request_pipeline end end @@ -245,11 +245,11 @@ describe MergeRequests::RefreshService do it 'does not re-create a duplicate detached merge request pipeline' do expect do service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') - end.to change { @merge_request.merge_request_pipelines.count }.by(1) + end.to change { @merge_request.pipelines_for_merge_request.count }.by(1) expect do service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') - end.not_to change { @merge_request.merge_request_pipelines.count } + end.not_to change { @merge_request.pipelines_for_merge_request.count } end end end @@ -266,7 +266,7 @@ describe MergeRequests::RefreshService do it 'does not create a detached merge request pipeline' do expect { subject } - .not_to change { @merge_request.merge_request_pipelines.count } + .not_to change { @merge_request.pipelines_for_merge_request.count } end end end -- cgit v1.2.1 From 8973f32d428ab8961986700700a2bad51fe7d4af Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 25 Mar 2019 14:29:51 +0000 Subject: Remove cleaned up OIDs from database and cache --- .../discussions_diff/highlight_cache_spec.rb | 110 +++++++++------------ spec/lib/gitlab/git/repository_cleaner_spec.rb | 71 +++++++------ .../gitlab/gitaly_client/cleanup_service_spec.rb | 10 +- spec/models/note_diff_file_spec.rb | 27 +++++ spec/services/projects/cleanup_service_spec.rb | 89 ++++++++++++++++- 5 files changed, 204 insertions(+), 103 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb index fe26ebb8796..15ee8c40b55 100644 --- a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb +++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb @@ -3,31 +3,32 @@ require 'spec_helper' describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do + def fake_file(offset) + { + text: 'foo', + type: 'new', + index: 2 + offset, + old_pos: 10 + offset, + new_pos: 11 + offset, + line_code: 'xpto', + rich_text: 'blops' + } + end + + let(:mapping) do + { + 3 => [ + fake_file(0), + fake_file(1) + ], + 4 => [ + fake_file(2) + ] + } + end + describe '#write_multiple' do it 'sets multiple keys serializing content as JSON' do - mapping = { - 3 => [ - { - text: 'foo', - type: 'new', - index: 2, - old_pos: 10, - new_pos: 11, - line_code: 'xpto', - rich_text: 'blops' - }, - { - text: 'foo', - type: 'new', - index: 3, - old_pos: 11, - new_pos: 12, - line_code: 'xpto', - rich_text: 'blips' - } - ] - } - described_class.write_multiple(mapping) mapping.each do |key, value| @@ -41,53 +42,16 @@ describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do describe '#read_multiple' do it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do - mapping = { - 3 => [ - { - text: 'foo', - type: 'new', - index: 2, - old_pos: 11, - new_pos: 12, - line_code: 'xpto', - rich_text: 'blops' - }, - { - text: 'foo', - type: 'new', - index: 3, - old_pos: 10, - new_pos: 11, - line_code: 'xpto', - rich_text: 'blops' - } - ] - } - described_class.write_multiple(mapping) found = described_class.read_multiple(mapping.keys) - expect(found.size).to eq(1) + expect(found.size).to eq(2) expect(found.first.size).to eq(2) expect(found.first).to all(be_a(Gitlab::Diff::Line)) end it 'returns nil when cached key is not found' do - mapping = { - 3 => [ - { - text: 'foo', - type: 'new', - index: 2, - old_pos: 11, - new_pos: 12, - line_code: 'xpto', - rich_text: 'blops' - } - ] - } - described_class.write_multiple(mapping) found = described_class.read_multiple([2, 3]) @@ -95,8 +59,30 @@ describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do expect(found.size).to eq(2) expect(found.first).to eq(nil) - expect(found.second.size).to eq(1) + expect(found.second.size).to eq(2) expect(found.second).to all(be_a(Gitlab::Diff::Line)) end end + + describe '#clear_multiple' do + it 'removes all named keys' do + described_class.write_multiple(mapping) + + described_class.clear_multiple(mapping.keys) + + expect(described_class.read_multiple(mapping.keys)).to all(be_nil) + end + + it 'only removed named keys' do + to_clear, to_leave = mapping.keys + + described_class.write_multiple(mapping) + described_class.clear_multiple([to_clear]) + + cleared, left = described_class.read_multiple([to_clear, to_leave]) + + expect(cleared).to be_nil + expect(left).to all(be_a(Gitlab::Diff::Line)) + end + end end diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb index 6602f22843f..7bba0107e58 100644 --- a/spec/lib/gitlab/git/repository_cleaner_spec.rb +++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb @@ -6,55 +6,62 @@ describe Gitlab::Git::RepositoryCleaner do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:head_sha) { repository.head_commit.id } - let(:object_map_data) { "#{head_sha} #{'0' * 40}" } + let(:object_map_data) { "#{head_sha} #{Gitlab::Git::BLANK_SHA}" } - subject(:cleaner) { described_class.new(repository.raw) } + let(:clean_refs) { %W[refs/environments/1 refs/merge-requests/1 refs/keep-around/#{head_sha}] } + let(:keep_refs) { %w[refs/heads/_keep refs/tags/_keep] } - describe '#apply_bfg_object_map' do - let(:clean_refs) { %W[refs/environments/1 refs/merge-requests/1 refs/keep-around/#{head_sha}] } - let(:keep_refs) { %w[refs/heads/_keep refs/tags/_keep] } + subject(:cleaner) { described_class.new(repository.raw) } + shared_examples_for '#apply_bfg_object_map_stream' do before do (clean_refs + keep_refs).each { |ref| repository.create_ref(head_sha, ref) } end - context 'from StringIO' do - let(:object_map) { StringIO.new(object_map_data) } + it 'removes internal references' do + entries = [] - it 'removes internal references' do - cleaner.apply_bfg_object_map(object_map) + cleaner.apply_bfg_object_map_stream(object_map) do |rsp| + entries.concat(rsp.entries) + end - aggregate_failures do - clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy } - keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy } - end + aggregate_failures do + clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be(false) } + keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be(true) } + + expect(entries).to contain_exactly( + Gitaly::ApplyBfgObjectMapStreamResponse::Entry.new( + type: :COMMIT, + old_oid: head_sha, + new_oid: Gitlab::Git::BLANK_SHA + ) + ) end end + end - context 'from Gitlab::HttpIO' do - let(:url) { 'http://example.com/bfg_object_map.txt' } - let(:tempfile) { Tempfile.new } - let(:object_map) { Gitlab::HttpIO.new(url, object_map_data.size) } + describe '#apply_bfg_object_map_stream (from StringIO)' do + let(:object_map) { StringIO.new(object_map_data) } - around do |example| - tempfile.write(object_map_data) - tempfile.close + include_examples '#apply_bfg_object_map_stream' + end - example.run - ensure - tempfile.unlink - end + describe '#apply_bfg_object_map_stream (from Gitlab::HttpIO)' do + let(:url) { 'http://example.com/bfg_object_map.txt' } + let(:tempfile) { Tempfile.new } + let(:object_map) { Gitlab::HttpIO.new(url, object_map_data.size) } - it 'removes internal references' do - stub_remote_url_200(url, tempfile.path) + around do |example| + tempfile.write(object_map_data) + tempfile.close - cleaner.apply_bfg_object_map(object_map) + stub_remote_url_200(url, tempfile.path) - aggregate_failures do - clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy } - keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy } - end - end + example.run + ensure + tempfile.unlink end + + include_examples '#apply_bfg_object_map_stream' end end diff --git a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb index 369deff732a..c42332dc27b 100644 --- a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb @@ -6,14 +6,14 @@ describe Gitlab::GitalyClient::CleanupService do let(:relative_path) { project.disk_path + '.git' } let(:client) { described_class.new(project.repository) } - describe '#apply_bfg_object_map' do - it 'sends an apply_bfg_object_map message' do + describe '#apply_bfg_object_map_stream' do + it 'sends an apply_bfg_object_map_stream message' do expect_any_instance_of(Gitaly::CleanupService::Stub) - .to receive(:apply_bfg_object_map) + .to receive(:apply_bfg_object_map_stream) .with(kind_of(Enumerator), kind_of(Hash)) - .and_return(double) + .and_return([]) - client.apply_bfg_object_map(StringIO.new) + client.apply_bfg_object_map_stream(StringIO.new) end end end diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb index 99eeac8d778..b15bedd257e 100644 --- a/spec/models/note_diff_file_spec.rb +++ b/spec/models/note_diff_file_spec.rb @@ -10,4 +10,31 @@ describe NoteDiffFile do describe 'validations' do it { is_expected.to validate_presence_of(:diff_note) } end + + describe '.referencing_sha' do + let!(:diff_note) { create(:diff_note_on_commit) } + + let(:note_diff_file) { diff_note.note_diff_file } + let(:project) { diff_note.project } + + it 'finds note diff files by project and sha' do + found = described_class.referencing_sha(diff_note.commit_id, project_id: project.id) + + expect(found).to contain_exactly(note_diff_file) + end + + it 'excludes note diff files with the wrong project' do + other_project = create(:project) + + found = described_class.referencing_sha(diff_note.commit_id, project_id: other_project.id) + + expect(found).to be_empty + end + + it 'excludes note diff files with the wrong sha' do + found = described_class.referencing_sha(Gitlab::Git::BLANK_SHA, project_id: project.id) + + expect(found).to be_empty + end + end end diff --git a/spec/services/projects/cleanup_service_spec.rb b/spec/services/projects/cleanup_service_spec.rb index 29eabc86327..5c246854eb7 100644 --- a/spec/services/projects/cleanup_service_spec.rb +++ b/spec/services/projects/cleanup_service_spec.rb @@ -6,13 +6,13 @@ describe Projects::CleanupService do let(:project) { create(:project, :repository, bfg_object_map: fixture_file_upload('spec/fixtures/bfg_object_map.txt')) } let(:object_map) { project.bfg_object_map } + let(:cleaner) { service.__send__(:repository_cleaner) } + subject(:service) { described_class.new(project) } describe '#execute' do - it 'runs the apply_bfg_object_map gitaly RPC' do - expect_next_instance_of(Gitlab::Git::RepositoryCleaner) do |cleaner| - expect(cleaner).to receive(:apply_bfg_object_map).with(kind_of(IO)) - end + it 'runs the apply_bfg_object_map_stream gitaly RPC' do + expect(cleaner).to receive(:apply_bfg_object_map_stream).with(kind_of(IO)) service.execute end @@ -37,10 +37,91 @@ describe Projects::CleanupService do expect(object_map.exists?).to be_falsy end + context 'with a tainted merge request diff' do + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:diff) { merge_request.merge_request_diff } + let(:entry) { build_entry(diff.commits.first.id) } + + before do + allow(cleaner) + .to receive(:apply_bfg_object_map_stream) + .and_yield(Gitaly::ApplyBfgObjectMapStreamResponse.new(entries: [entry])) + end + + it 'removes the tainted commit from the database' do + service.execute + + expect(MergeRequestDiff.exists?(diff.id)).to be_falsy + end + + it 'ignores non-commit responses from Gitaly' do + entry.type = :UNKNOWN + + service.execute + + expect(MergeRequestDiff.exists?(diff.id)).to be_truthy + end + end + + context 'with a tainted diff note' do + let(:diff_note) { create(:diff_note_on_commit, project: project) } + let(:note_diff_file) { diff_note.note_diff_file } + let(:entry) { build_entry(diff_note.commit_id) } + + let(:highlight_cache) { Gitlab::DiscussionsDiff::HighlightCache } + let(:cache_id) { note_diff_file.id } + + before do + allow(cleaner) + .to receive(:apply_bfg_object_map_stream) + .and_yield(Gitaly::ApplyBfgObjectMapStreamResponse.new(entries: [entry])) + end + + it 'removes the tainted commit from the database' do + service.execute + + expect(NoteDiffFile.exists?(note_diff_file.id)).to be_falsy + end + + it 'removes the highlight cache from redis' do + write_cache(highlight_cache, cache_id, [{}]) + + expect(read_cache(highlight_cache, cache_id)).not_to be_nil + + service.execute + + expect(read_cache(highlight_cache, cache_id)).to be_nil + end + + it 'ignores non-commit responses from Gitaly' do + entry.type = :UNKNOWN + + service.execute + + expect(NoteDiffFile.exists?(note_diff_file.id)).to be_truthy + end + end + it 'raises an error if no object map can be found' do object_map.remove! expect { service.execute }.to raise_error(described_class::NoUploadError) end end + + def build_entry(old_oid) + Gitaly::ApplyBfgObjectMapStreamResponse::Entry.new( + type: :COMMIT, + old_oid: old_oid, + new_oid: Gitlab::Git::BLANK_SHA + ) + end + + def read_cache(cache, key) + cache.read_multiple([key]).first + end + + def write_cache(cache, key, value) + cache.write_multiple(key => value) + end end -- cgit v1.2.1 From 7c2b1cb0c3c598994559180059a87fc44bfda5c8 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 3 May 2019 16:18:06 +0700 Subject: Fix schedule head pipeline update Currently, schedule head pipeline update method which executed after pipeline creation does not take into account of merge reqeust pipelines. We should use dedicated `all_merge_requests` method in this case. --- spec/services/ci/create_pipeline_service_spec.rb | 34 +++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 8a80652b3d8..9a3ac75e418 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -773,7 +773,7 @@ describe Ci::CreatePipelineService do end end - describe 'Merge request pipelines' do + describe 'Pipelines for merge requests' do let(:pipeline) do execute_service(source: source, merge_request: merge_request, @@ -817,12 +817,14 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: Gitlab::Git.ref_name(ref_name), + source_branch: 'feature', target_project: project, target_branch: 'master') end - it 'creates a merge request pipeline' do + let(:ref_name) { merge_request.ref_path } + + it 'creates a detached merge request pipeline' do expect(pipeline).to be_persisted expect(pipeline).to be_merge_request_event expect(pipeline.merge_request).to eq(merge_request) @@ -837,6 +839,13 @@ describe Ci::CreatePipelineService do expect(pipeline.target_sha).to be_nil end + it 'schedules update for the head pipeline of the merge request' do + expect(UpdateHeadPipelineForMergeRequestWorker) + .to receive(:perform_async).with(merge_request.id) + + pipeline + end + context 'when target sha is specified' do let(:target_sha) { merge_request.target_branch_sha } @@ -858,15 +867,16 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: Gitlab::Git.ref_name(ref_name), + source_branch: 'feature', target_project: target_project, target_branch: 'master') end + let(:ref_name) { 'refs/heads/feature' } let!(:project) { fork_project(target_project, nil, repository: true) } let!(:target_project) { create(:project, :repository) } - it 'creates a merge request pipeline in the forked project' do + it 'creates a legacy detached merge request pipeline in the forked project' do expect(pipeline).to be_persisted expect(project.ci_pipelines).to eq([pipeline]) expect(target_project.ci_pipelines).to be_empty @@ -884,7 +894,7 @@ describe Ci::CreatePipelineService do } end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(pipeline).not_to be_persisted expect(pipeline.errors[:base]).to eq(["No stages / jobs for this pipeline."]) end @@ -894,7 +904,7 @@ describe Ci::CreatePipelineService do context 'when merge request is not specified' do let(:merge_request) { nil } - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(pipeline).not_to be_persisted expect(pipeline.errors[:merge_request]).to eq(["can't be blank"]) end @@ -928,7 +938,7 @@ describe Ci::CreatePipelineService do target_branch: 'master') end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(pipeline).not_to be_persisted expect(pipeline.errors[:base]) @@ -939,7 +949,7 @@ describe Ci::CreatePipelineService do context 'when merge request is not specified' do let(:merge_request) { nil } - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(pipeline).not_to be_persisted expect(pipeline.errors[:base]) @@ -968,7 +978,7 @@ describe Ci::CreatePipelineService do target_branch: 'master') end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(pipeline).not_to be_persisted expect(pipeline.errors[:base]) @@ -999,7 +1009,7 @@ describe Ci::CreatePipelineService do target_branch: 'master') end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(pipeline).not_to be_persisted expect(pipeline.errors[:base]) @@ -1028,7 +1038,7 @@ describe Ci::CreatePipelineService do target_branch: 'master') end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(pipeline).not_to be_persisted expect(pipeline.errors[:base]) -- cgit v1.2.1 From e20e062737631e37904b54c73539e6846cd5a1e7 Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Mon, 6 May 2019 12:51:49 +0000 Subject: Fix uploading of LFS tracked file through UI --- spec/services/lfs/file_transformer_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'spec') diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb index 2c6e86069f3..888eea6e91e 100644 --- a/spec/services/lfs/file_transformer_spec.rb +++ b/spec/services/lfs/file_transformer_spec.rb @@ -64,6 +64,25 @@ describe Lfs::FileTransformer do expect(result.encoding).to eq('text') end + context 'when an actual file is passed' do + let(:file) { Tempfile.new(file_path) } + + before do + file.write(file_content) + file.rewind + end + + after do + file.unlink + end + + it "creates an LfsObject with the file's content" do + subject.new_file(file_path, file) + + expect(LfsObject.last.file.read).to eq file_content + end + end + context "when doesn't use LFS" do let(:file_path) { 'other.filetype' } -- cgit v1.2.1 From 85609c117e2b96a786204069669c66d36d971733 Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Mon, 6 May 2019 13:11:42 +0000 Subject: Implement support for CI variables of type file Add env_var and file as supported types for CI variables. Variables of type file expose to users existing gitlab-runner behaviour - save variable value into a temp file and set the path to this file in an ENV var named after the variable key. Resolves https://gitlab.com/gitlab-org/gitlab-ce/issues/46806. --- .../projects/pipeline_schedules_controller_spec.rb | 3 ++- spec/factories/ci/pipeline_schedule_variables.rb | 1 + spec/fixtures/api/schemas/pipeline_schedule.json | 2 +- .../api/schemas/pipeline_schedule_variable.json | 10 ++++++-- spec/models/ci/group_variable_spec.rb | 3 ++- spec/models/ci/pipeline_schedule_variable_spec.rb | 2 +- spec/models/ci/pipeline_variable_spec.rb | 3 ++- spec/models/ci/variable_spec.rb | 3 ++- spec/requests/api/group_variables_spec.rb | 8 ++++-- spec/requests/api/pipeline_schedules_spec.rb | 7 ++++-- spec/requests/api/pipelines_spec.rb | 9 ++++--- spec/requests/api/variables_spec.rb | 8 ++++-- .../controllers/variables_shared_examples.rb | 12 +++++++++ .../models/ci_variable_shared_examples.rb | 29 ++++++++++++++++++++++ 14 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 spec/support/shared_examples/models/ci_variable_shared_examples.rb (limited to 'spec') diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index eb8983a7633..850ef9c92fb 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -91,7 +91,7 @@ describe Projects::PipelineSchedulesController do context 'when variables_attributes has one variable' do let(:schedule) do basic_param.merge({ - variables_attributes: [{ key: 'AAA', secret_value: 'AAA123' }] + variables_attributes: [{ key: 'AAA', secret_value: 'AAA123', variable_type: 'file' }] }) end @@ -105,6 +105,7 @@ describe Projects::PipelineSchedulesController do Ci::PipelineScheduleVariable.last.tap do |v| expect(v.key).to eq("AAA") expect(v.value).to eq("AAA123") + expect(v.variable_type).to eq("file") end end end diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb index 8d29118e310..c85b97fbfc7 100644 --- a/spec/factories/ci/pipeline_schedule_variables.rb +++ b/spec/factories/ci/pipeline_schedule_variables.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' + variable_type 'env_var' pipeline_schedule factory: :ci_pipeline_schedule end diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json index c76c6945117..690c4a7d4e8 100644 --- a/spec/fixtures/api/schemas/pipeline_schedule.json +++ b/spec/fixtures/api/schemas/pipeline_schedule.json @@ -33,7 +33,7 @@ "additionalProperties": false }, "variables": { - "type": ["array", "null"], + "type": "array", "items": { "$ref": "pipeline_schedule_variable.json" } } }, diff --git a/spec/fixtures/api/schemas/pipeline_schedule_variable.json b/spec/fixtures/api/schemas/pipeline_schedule_variable.json index f7ccb2d44a0..022d36cb88c 100644 --- a/spec/fixtures/api/schemas/pipeline_schedule_variable.json +++ b/spec/fixtures/api/schemas/pipeline_schedule_variable.json @@ -1,8 +1,14 @@ { - "type": ["object", "null"], + "type": "object", + "required": [ + "key", + "value", + "variable_type" + ], "properties": { "key": { "type": "string" }, - "value": { "type": "string" } + "value": { "type": "string" }, + "variable_type": { "type": "string" } }, "additionalProperties": false } diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index b3999765e5f..406a69f3bbc 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -5,7 +5,8 @@ require 'spec_helper' describe Ci::GroupVariable do subject { build(:ci_group_variable) } - it { is_expected.to include_module(HasVariable) } + it_behaves_like "CI variable" + it { is_expected.to include_module(Presentable) } it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) } diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb index 3c9379ecb0d..c96a24d5042 100644 --- a/spec/models/ci/pipeline_schedule_variable_spec.rb +++ b/spec/models/ci/pipeline_schedule_variable_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' describe Ci::PipelineScheduleVariable do subject { build(:ci_pipeline_schedule_variable) } - it { is_expected.to include_module(HasVariable) } + it_behaves_like "CI variable" end diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb index 2ecb688299a..e8c7ce088e2 100644 --- a/spec/models/ci/pipeline_variable_spec.rb +++ b/spec/models/ci/pipeline_variable_spec.rb @@ -5,7 +5,8 @@ require 'spec_helper' describe Ci::PipelineVariable do subject { build(:ci_pipeline_variable) } - it { is_expected.to include_module(HasVariable) } + it_behaves_like "CI variable" + it { is_expected.to validate_uniqueness_of(:key).scoped_to(:pipeline_id) } describe '#hook_attrs' do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index d2df6b3344e..a231c7eaed8 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -5,8 +5,9 @@ require 'spec_helper' describe Ci::Variable do subject { build(:ci_variable) } + it_behaves_like "CI variable" + describe 'validations' do - it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Presentable) } it { is_expected.to include_module(Maskable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) } diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index 66b9aae4b58..d50bae3dc47 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -51,6 +51,7 @@ describe API::GroupVariables do expect(response).to have_gitlab_http_status(200) expect(json_response['value']).to eq(variable.value) expect(json_response['protected']).to eq(variable.protected?) + expect(json_response['variable_type']).to eq(variable.variable_type) end it 'responds with 404 Not Found if requesting non-existing variable' do @@ -94,17 +95,19 @@ describe API::GroupVariables do expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy + expect(json_response['variable_type']).to eq('env_var') end it 'creates variable with optional attributes' do expect do - post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' } + post api("/groups/#{group.id}/variables", user), params: { variable_type: 'file', key: 'TEST_VARIABLE_2', value: 'VALUE_2' } end.to change {group.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_falsey + expect(json_response['variable_type']).to eq('file') end it 'does not allow to duplicate variable key' do @@ -145,7 +148,7 @@ describe API::GroupVariables do initial_variable = group.variables.reload.first value_before = initial_variable.value - put api("/groups/#{group.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true } + put api("/groups/#{group.id}/variables/#{variable.key}", user), params: { variable_type: 'file', value: 'VALUE_1_UP', protected: true } updated_variable = group.variables.reload.first @@ -153,6 +156,7 @@ describe API::GroupVariables do expect(value_before).to eq(variable.value) expect(updated_variable.value).to eq('VALUE_1_UP') expect(updated_variable).to be_protected + expect(json_response['variable_type']).to eq('file') end it 'responds with 404 Not Found if requesting non-existing variable' do diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb index 870ef34437f..072bd02f2ac 100644 --- a/spec/requests/api/pipeline_schedules_spec.rb +++ b/spec/requests/api/pipeline_schedules_spec.rb @@ -91,6 +91,7 @@ describe API::PipelineSchedules do let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) } before do + pipeline_schedule.variables << build(:ci_pipeline_schedule_variable) pipeline_schedule.pipelines << build(:ci_pipeline, project: project) end @@ -331,13 +332,14 @@ describe API::PipelineSchedules do it 'creates pipeline_schedule_variable' do expect do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer), - params: params + params: params.merge(variable_type: 'file') end.to change { pipeline_schedule.variables.count }.by(1) expect(response).to have_gitlab_http_status(:created) expect(response).to match_response_schema('pipeline_schedule_variable') expect(json_response['key']).to eq(params[:key]) expect(json_response['value']).to eq(params[:value]) + expect(json_response['variable_type']).to eq('file') end end @@ -389,11 +391,12 @@ describe API::PipelineSchedules do context 'authenticated user with valid permissions' do it 'updates pipeline_schedule_variable' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer), - params: { value: 'updated_value' } + params: { value: 'updated_value', variable_type: 'file' } expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline_schedule_variable') expect(json_response['value']).to eq('updated_value') + expect(json_response['variable_type']).to eq('file') end end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 26158231444..35b3dd219f7 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -294,6 +294,7 @@ describe API::Pipelines do expect(variable.key).to eq(expected_variable['key']) expect(variable.value).to eq(expected_variable['value']) + expect(variable.variable_type).to eq(expected_variable['variable_type']) end end @@ -314,7 +315,7 @@ describe API::Pipelines do end context 'variables given' do - let(:variables) { [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] } + let(:variables) { [{ 'variable_type' => 'file', 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] } it 'creates and returns a new pipeline using the given variables' do expect do @@ -330,7 +331,7 @@ describe API::Pipelines do end describe 'using variables conditions' do - let(:variables) { [{ 'key' => 'STAGING', 'value' => 'true' }] } + let(:variables) { [{ 'variable_type' => 'env_var', 'key' => 'STAGING', 'value' => 'true' }] } before do config = YAML.dump(test: { script: 'test', only: { variables: ['$STAGING'] } }) @@ -467,7 +468,7 @@ describe API::Pipelines do subject expect(response).to have_gitlab_http_status(200) - expect(json_response).to contain_exactly({ "key" => "foo", "value" => "bar" }) + expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" }) end end end @@ -488,7 +489,7 @@ describe API::Pipelines do subject expect(response).to have_gitlab_http_status(200) - expect(json_response).to contain_exactly({ "key" => "foo", "value" => "bar" }) + expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" }) end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 5df6baf0ddf..cc07869a744 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -43,6 +43,7 @@ describe API::Variables do expect(response).to have_gitlab_http_status(200) expect(json_response['value']).to eq(variable.value) expect(json_response['protected']).to eq(variable.protected?) + expect(json_response['variable_type']).to eq('env_var') end it 'responds with 404 Not Found if requesting non-existing variable' do @@ -80,17 +81,19 @@ describe API::Variables do expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy + expect(json_response['variable_type']).to eq('env_var') end it 'creates variable with optional attributes' do expect do - post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' } + post api("/projects/#{project.id}/variables", user), params: { variable_type: 'file', key: 'TEST_VARIABLE_2', value: 'VALUE_2' } end.to change {project.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_falsey + expect(json_response['variable_type']).to eq('file') end it 'does not allow to duplicate variable key' do @@ -125,7 +128,7 @@ describe API::Variables do initial_variable = project.variables.reload.first value_before = initial_variable.value - put api("/projects/#{project.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true } + put api("/projects/#{project.id}/variables/#{variable.key}", user), params: { variable_type: 'file', value: 'VALUE_1_UP', protected: true } updated_variable = project.variables.reload.first @@ -133,6 +136,7 @@ describe API::Variables do expect(value_before).to eq(variable.value) expect(updated_variable.value).to eq('VALUE_1_UP') expect(updated_variable).to be_protected + expect(updated_variable.variable_type).to eq('file') end it 'responds with 404 Not Found if requesting non-existing variable' do diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb index b615a8f54cf..e80722857ec 100644 --- a/spec/support/shared_examples/controllers/variables_shared_examples.rb +++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb @@ -120,4 +120,16 @@ shared_examples 'PATCH #update updates variables' do expect(response).to match_response_schema('variables') end end + + context 'for variables of type file' do + let(:variables_attributes) do + [ + new_variable_attributes.merge(variable_type: 'file') + ] + end + + it 'creates new variable of type file' do + expect { subject }.to change { owner.variables.file.count }.by(1) + end + end end diff --git a/spec/support/shared_examples/models/ci_variable_shared_examples.rb b/spec/support/shared_examples/models/ci_variable_shared_examples.rb new file mode 100644 index 00000000000..f93de8b6ff1 --- /dev/null +++ b/spec/support/shared_examples/models/ci_variable_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +shared_examples_for 'CI variable' do + it { is_expected.to include_module(HasVariable) } + + describe "variable type" do + it 'defines variable types' do + expect(described_class.variable_types).to eq({ "env_var" => 1, "file" => 2 }) + end + + it "defaults variable type to env_var" do + expect(subject.variable_type).to eq("env_var") + end + + it "supports variable type file" do + variable = described_class.new(variable_type: :file) + expect(variable).to be_file + end + end + + it 'strips whitespaces when assigning key' do + subject.key = " SECRET " + expect(subject.key).to eq("SECRET") + end + + it 'can convert to runner variable' do + expect(subject.to_runner_variable.keys).to include(:key, :value, :public, :file) + end +end -- cgit v1.2.1 From 18c07084a6faea04ab446e523fff603843406ac8 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 6 May 2019 13:15:03 +0000 Subject: Destroy repo mirrors instead of disabling them It is important to destroy data related to repo mirrors when they are disabled. Use `_destroy` nested attribute instead of `enabled` for push mirrors. Call `remove_import_data` after saving a project if its pull mirror is disabled. --- .../projects/settings/repository_settings_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 1259ad45791..f7de769cee9 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -217,5 +217,24 @@ describe 'Projects > Settings > Repository settings' do expect(RepositoryCleanupWorker.jobs.count).to eq(1) end end + + context 'with an existing mirror', :js do + let(:mirrored_project) { create(:project, :repository, :remote_mirror) } + + before do + mirrored_project.add_maintainer(user) + + visit project_settings_repository_path(mirrored_project) + end + + it 'delete remote mirrors' do + expect(mirrored_project.remote_mirrors.count).to eq(1) + + find('.js-delete-mirror').click + wait_for_requests + + expect(mirrored_project.remote_mirrors.count).to eq(0) + end + end end end -- cgit v1.2.1 From f80f68d520b98ae60300ecf0758ff241218e9cd0 Mon Sep 17 00:00:00 2001 From: Ken Ding Date: Mon, 6 May 2019 23:30:03 +0930 Subject: 58404 - setup max depth for graphql 58404 - add change log 58404 - add spec 58404 - add more spec to test depth 2 58404 - fix spec 58404 - fix rubocop 58404 - refactor the code by Bob's advice 58404 - revert changes of all_graphql_fields_for 58404 - change text only 58404 - fix rspec according to gitlab's standard 58404 - revert previous spec 58404 - fix rubocop --- spec/graphql/gitlab_schema_spec.rb | 86 +++++++++++++++++-------- spec/requests/api/graphql/gitlab_schema_spec.rb | 40 ++++++++++-- spec/support/helpers/graphql_helpers.rb | 6 ++ 3 files changed, 100 insertions(+), 32 deletions(-) (limited to 'spec') diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index 05f10fb40f0..c138c87c4ac 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe GitlabSchema do + let(:user) { build :user } + it 'uses batch loading' do expect(field_instrumenters).to include(BatchLoader::GraphQL) end @@ -33,43 +35,75 @@ describe GitlabSchema do expect(connection).to eq(Gitlab::Graphql::Connections::KeysetConnection) end - context 'for different types of users' do - it 'returns DEFAULT_MAX_COMPLEXITY for no context' do - expect(GraphQL::Schema) - .to receive(:execute) - .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) + describe '.execute' do + context 'for different types of users' do + context 'when no context' do + it 'returns DEFAULT_MAX_COMPLEXITY' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) - described_class.execute('query') - end + described_class.execute('query') + end + end - it 'returns DEFAULT_MAX_COMPLEXITY for no user' do - expect(GraphQL::Schema) - .to receive(:execute) - .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) + context 'when no user' do + it 'returns DEFAULT_MAX_COMPLEXITY' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) - described_class.execute('query', context: {}) - end + described_class.execute('query', context: {}) + end - it 'returns AUTHENTICATED_COMPLEXITY for a logged in user' do - user = build :user + it 'returns ANONYMOUS_MAX_DEPTH' do + expect(GraphQL::Schema) + .to receive(:execute) + .with('query', hash_including(max_depth: GitlabSchema::ANONYMOUS_MAX_DEPTH)) - expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY)) + described_class.execute('query', context: {}) + end + end - described_class.execute('query', context: { current_user: user }) - end + context 'when a logged in user' do + it 'returns AUTHENTICATED_COMPLEXITY' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY)) - it 'returns ADMIN_COMPLEXITY for an admin user' do - user = build :user, :admin + described_class.execute('query', context: { current_user: user }) + end - expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY)) + it 'returns AUTHENTICATED_MAX_DEPTH' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: GitlabSchema::AUTHENTICATED_MAX_DEPTH)) - described_class.execute('query', context: { current_user: user }) - end + described_class.execute('query', context: { current_user: user }) + end + end + + context 'when an admin user' do + it 'returns ADMIN_COMPLEXITY' do + user = build :user, :admin + + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY)) + + described_class.execute('query', context: { current_user: user }) + end + end + + context 'when max_complexity passed on the query' do + it 'returns what was passed on the query' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: 1234)) + + described_class.execute('query', max_complexity: 1234) + end + end - it 'returns what was passed on the query' do - expect(GraphQL::Schema).to receive(:execute).with('query', { max_complexity: 1234 }) + context 'when max_depth passed on the query' do + it 'returns what was passed on the query' do + expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: 1234)) - described_class.execute('query', max_complexity: 1234) + described_class.execute('query', max_depth: 1234) + end + end end end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index b63b4fb34df..dd518274f82 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -3,15 +3,43 @@ require 'spec_helper' describe 'GitlabSchema configurations' do include GraphqlHelpers - it 'shows an error if complexity is too high' do - project = create(:project, :repository) - query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) + let(:project) { create(:project, :repository) } + let(:query) { graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) } + let(:current_user) { create(:user) } - allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 + describe '#max_complexity' do + context 'when complexity is too high' do + it 'shows an error' do + allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 - post_graphql(query, current_user: nil) + post_graphql(query, current_user: nil) - expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') + expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') + end + end + end + + describe '#max_depth' do + context 'when query depth is too high' do + it 'shows error' do + errors = [{ "message" => "Query has depth of 2, which exceeds max depth of 1" }] + allow(GitlabSchema).to receive(:max_query_depth).and_return 1 + + post_graphql(query) + + expect(graphql_errors).to eq(errors) + end + end + + context 'when query depth is within range' do + it 'has no error' do + allow(GitlabSchema).to receive(:max_query_depth).and_return 5 + + post_graphql(query) + + expect(graphql_errors).to be_nil + end + end end context 'when IntrospectionQuery' do diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index b49d743fb9a..f15944652fd 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -102,6 +102,7 @@ module GraphqlHelpers def all_graphql_fields_for(class_name, parent_types = Set.new) allow_unlimited_graphql_complexity + allow_unlimited_graphql_depth type = GitlabSchema.types[class_name.to_s] return "" unless type @@ -190,4 +191,9 @@ module GraphqlHelpers allow_any_instance_of(GitlabSchema).to receive(:max_complexity).and_return nil allow(GitlabSchema).to receive(:max_query_complexity).with(any_args).and_return nil end + + def allow_unlimited_graphql_depth + allow_any_instance_of(GitlabSchema).to receive(:max_depth).and_return nil + allow(GitlabSchema).to receive(:max_query_depth).with(any_args).and_return nil + end end -- cgit v1.2.1 From 5f22907418397861d9b07cbaeea05ef7264d5605 Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Fri, 12 Apr 2019 17:11:46 +0200 Subject: Update project list specs Add tests to ensure search only executes with a button click or enter, sort by Name, Last updated, Created date and Stars and tests for Visibility filter --- spec/features/dashboard/projects_spec.rb | 13 ++ .../dashboard/user_filters_projects_spec.rb | 226 +++++++++++++++++++++ 2 files changed, 239 insertions(+) (limited to 'spec') diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 9d1c1e3acc7..8b5f645b2b4 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -112,6 +112,14 @@ describe 'Dashboard Projects' do expect(first('.project-row')).to have_content(project_with_most_stars.title) end + + it 'shows tabs to filter by all projects or personal' do + visit dashboard_projects_path + segmented_button = page.find('.filtered-search-nav .button-filter-group') + + expect(segmented_button).to have_content 'All' + expect(segmented_button).to have_content 'Personal' + end end context 'when on Starred projects tab', :js do @@ -134,6 +142,11 @@ describe 'Dashboard Projects' do expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1) expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1) end + + it 'does not show tabs to filter by all projects or personal' do + visit(starred_dashboard_projects_path) + expect(page).not_to have_content '.filtered-search-nav' + end end describe 'with a pipeline', :clean_gitlab_redis_shared_state do diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index cc86114e436..9945bf85997 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -14,6 +14,7 @@ describe 'Dashboard > User filters projects' do describe 'filtering personal projects' do before do + stub_feature_flags(project_list_filter_bar: false) project2.add_developer(user) visit dashboard_projects_path @@ -30,6 +31,7 @@ describe 'Dashboard > User filters projects' do describe 'filtering starred projects', :js do before do + stub_feature_flags(project_list_filter_bar: false) user.toggle_star(project) visit dashboard_projects_path @@ -42,4 +44,228 @@ describe 'Dashboard > User filters projects' do expect(page).not_to have_content('You don\'t have starred projects yet') end end + + describe 'without search bar', :js do + before do + stub_feature_flags(project_list_filter_bar: false) + + project2.add_developer(user) + visit dashboard_projects_path + end + + it 'will autocomplete searches', :js do + expect(page).to have_content 'Victorialand' + expect(page).to have_content 'Treasure' + + fill_in 'project-filter-form-field', with: 'Lord beerus\n' + + expect(page).not_to have_content 'Victorialand' + expect(page).not_to have_content 'Treasure' + end + end + + describe 'with search bar', :js do + before do + stub_feature_flags(project_list_filter_bar: true) + + project2.add_developer(user) + visit dashboard_projects_path + end + + # TODO: move these helpers somewhere more useful + def click_sort_direction + page.find('.filtered-search-block #filtered-search-sorting-dropdown .reverse-sort-btn').click + end + + def select_dropdown_option(selector, label) + dropdown = page.find(selector) + dropdown.click + + dropdown.find('.dropdown-menu a', text: label, match: :first).click + end + + def expect_to_see_projects(sorted_projects) + click_sort_direction + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(sorted_projects) + end + + describe 'Search' do + it 'will execute when i click the search button' do + expect(page).to have_content 'Victorialand' + expect(page).to have_content 'Treasure' + + fill_in 'project-filter-form-field', with: 'Lord vegeta\n' + find('.filtered-search .btn').click + + expect(page).not_to have_content 'Victorialand' + expect(page).not_to have_content 'Treasure' + end + + it 'will execute when i press enter' do + expect(page).to have_content 'Victorialand' + expect(page).to have_content 'Treasure' + + fill_in 'project-filter-form-field', with: 'Lord frieza\n' + find('#project-filter-form-field').native.send_keys :enter + + expect(page).not_to have_content 'Victorialand' + expect(page).not_to have_content 'Treasure' + end + end + + describe 'Filter' do + before do + priv = create(:project, :private, name: 'Private project', namespace: user.namespace) + int = create(:project, :internal, name: 'Internal project', namespace: user.namespace) + + priv.add_maintainer(user) + int.add_maintainer(user) + end + + it 'can filter for only private projects' do + select_dropdown_option '#filtered-search-visibility-dropdown', 'Private' + expect(current_url).to match(/visibility_level=0/) + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(["Private project", "Treasure", "Victorialand"]) + end + + it 'can filter for only internal projects' do + select_dropdown_option '#filtered-search-visibility-dropdown', 'Internal' + expect(current_url).to match(/visibility_level=10/) + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(['Internal project']) + end + + it 'can filter for any project' do + select_dropdown_option '#filtered-search-visibility-dropdown', 'Any' + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(["Internal project", "Private project", "Treasure", "Victorialand"]) + end + end + + describe 'Sorting' do + before do + [ + { name: 'Red ribbon army', created_at: 2.days.ago }, + { name: 'Cell saga', created_at: Time.now }, + { name: 'Frieza saga', created_at: 10.days.ago } + ].each do |item| + proj = create(:project, name: item[:name], namespace: user.namespace, created_at: item[:created_at]) + proj.add_developer(user) + end + + user.toggle_star(project) + user.toggle_star(project2) + user2.toggle_star(project2) + end + + it 'will include sorting direction' do + sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') + expect(sorting_dropdown).to have_css '.reverse-sort-btn' + end + + it 'will have all sorting options', :js do + sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') + sorting_option_labels = ['Last updated', 'Created date', 'Name', 'Stars'] + + sorting_dropdown.click + + sorting_option_labels.each do |label| + expect(sorting_dropdown).to have_content(label) + end + end + + it 'will default to Last updated', :js do + page.find('.filtered-search-block #filtered-search-sorting-dropdown').click + active_sorting_option = page.first('.filtered-search-block #filtered-search-sorting-dropdown .is-active') + + expect(active_sorting_option).to have_content 'Last updated' + end + + context 'Sorting by name' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' + + desc = ['Victorialand', 'Treasure', 'Red ribbon army', 'Frieza saga', 'Cell saga'] + asc = ['Cell saga', 'Frieza saga', 'Red ribbon army', 'Treasure', 'Victorialand'] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' + + [/sort=name_desc/, /sort=name_asc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + + context 'Sorting by Last updated' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' + + desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] + asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' + + [/sort=latest_activity_asc/, /sort=latest_activity_desc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + + context 'Sorting by Created date' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' + + desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] + asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' + + [/sort=created_asc/, /sort=created_desc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + + context 'Sorting by Stars' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' + + desc = ["Red ribbon army", "Cell saga", "Frieza saga", "Victorialand", "Treasure"] + asc = ["Treasure", "Victorialand", "Red ribbon army", "Cell saga", "Frieza saga"] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' + + [/sort=stars_asc/, /sort=stars_desc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + end + end end -- cgit v1.2.1 From 6accad69e214f44ac84f3ceac4159c712aa9af2e Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Fri, 19 Apr 2019 10:36:16 +0200 Subject: Added blank lines to meet style guide Un-nest title variable output Update spec test names Rename sort_value_most_stars -> sort_value_stars_desc Rename sorted_by_stars -> sorted_by_stars_desc Renname sort_value_most_stars_asc -> sort_value_stars_asc Invert feature check, assign feature condition to a variable Inline conditional nav bar rendering Invert conditional label Added follow up task Fix filters returning 0 projects show the wrong view Move click action out of test expectation Use proper variable name for project in before block Rename projects_sort_admin_options_hash Renamed projects_sort_admin_options_has to old_projects_sort_options_hash as its not only used on the admin screen Fix extra whitespace errors Stub project_list_filter_bar in the projects_helper specs Added follow up task for `show_projects?` Removed url test expectations --- spec/features/dashboard/projects_spec.rb | 1 + .../dashboard/user_filters_projects_spec.rb | 93 ++++++++++------------ spec/helpers/projects_helper_spec.rb | 4 + 3 files changed, 47 insertions(+), 51 deletions(-) (limited to 'spec') diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 8b5f645b2b4..d1ed64cce7f 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -145,6 +145,7 @@ describe 'Dashboard Projects' do it 'does not show tabs to filter by all projects or personal' do visit(starred_dashboard_projects_path) + expect(page).not_to have_content '.filtered-search-nav' end end diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index 9945bf85997..e0553086fd7 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -53,7 +53,7 @@ describe 'Dashboard > User filters projects' do visit dashboard_projects_path end - it 'will autocomplete searches', :js do + it 'autocompletes searches upon typing', :js do expect(page).to have_content 'Victorialand' expect(page).to have_content 'Treasure' @@ -85,13 +85,12 @@ describe 'Dashboard > User filters projects' do end def expect_to_see_projects(sorted_projects) - click_sort_direction list = page.all('.projects-list .project-name').map(&:text) expect(list).to match(sorted_projects) end describe 'Search' do - it 'will execute when i click the search button' do + it 'executes when the search button is clicked' do expect(page).to have_content 'Victorialand' expect(page).to have_content 'Treasure' @@ -116,30 +115,37 @@ describe 'Dashboard > User filters projects' do describe 'Filter' do before do - priv = create(:project, :private, name: 'Private project', namespace: user.namespace) - int = create(:project, :internal, name: 'Internal project', namespace: user.namespace) + private_project = create(:project, :private, name: 'Private project', namespace: user.namespace) + internal_project = create(:project, :internal, name: 'Internal project', namespace: user.namespace) - priv.add_maintainer(user) - int.add_maintainer(user) + private_project.add_maintainer(user) + internal_project.add_maintainer(user) end - it 'can filter for only private projects' do + it 'filters private projects only' do select_dropdown_option '#filtered-search-visibility-dropdown', 'Private' + expect(current_url).to match(/visibility_level=0/) + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(["Private project", "Treasure", "Victorialand"]) end - it 'can filter for only internal projects' do + it 'filters internal projects only' do select_dropdown_option '#filtered-search-visibility-dropdown', 'Internal' + expect(current_url).to match(/visibility_level=10/) + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(['Internal project']) end - it 'can filter for any project' do + it 'filters any project' do select_dropdown_option '#filtered-search-visibility-dropdown', 'Any' list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(["Internal project", "Private project", "Treasure", "Victorialand"]) end end @@ -151,8 +157,8 @@ describe 'Dashboard > User filters projects' do { name: 'Cell saga', created_at: Time.now }, { name: 'Frieza saga', created_at: 10.days.ago } ].each do |item| - proj = create(:project, name: item[:name], namespace: user.namespace, created_at: item[:created_at]) - proj.add_developer(user) + project = create(:project, name: item[:name], namespace: user.namespace, created_at: item[:created_at]) + project.add_developer(user) end user.toggle_star(project) @@ -160,12 +166,13 @@ describe 'Dashboard > User filters projects' do user2.toggle_star(project2) end - it 'will include sorting direction' do + it 'includes sorting direction' do sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') + expect(sorting_dropdown).to have_css '.reverse-sort-btn' end - it 'will have all sorting options', :js do + it 'has all sorting options', :js do sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') sorting_option_labels = ['Last updated', 'Created date', 'Name', 'Stars'] @@ -176,7 +183,7 @@ describe 'Dashboard > User filters projects' do end end - it 'will default to Last updated', :js do + it 'defaults to "Last updated"', :js do page.find('.filtered-search-block #filtered-search-sorting-dropdown').click active_sorting_option = page.first('.filtered-search-block #filtered-search-sorting-dropdown .is-active') @@ -184,86 +191,70 @@ describe 'Dashboard > User filters projects' do end context 'Sorting by name' do - it 'will sort the project list' do + it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' desc = ['Victorialand', 'Treasure', 'Red ribbon army', 'Frieza saga', 'Cell saga'] asc = ['Cell saga', 'Frieza saga', 'Red ribbon army', 'Treasure', 'Victorialand'] + click_sort_direction + expect_to_see_projects(desc) - expect_to_see_projects(asc) - end - it 'will update the url query' do - select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' + click_sort_direction - [/sort=name_desc/, /sort=name_asc/].each do |query_param| - click_sort_direction - expect(current_url).to match(query_param) - end + expect_to_see_projects(asc) end end context 'Sorting by Last updated' do - it 'will sort the project list' do + it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + click_sort_direction + expect_to_see_projects(desc) - expect_to_see_projects(asc) - end - it 'will update the url query' do - select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' + click_sort_direction - [/sort=latest_activity_asc/, /sort=latest_activity_desc/].each do |query_param| - click_sort_direction - expect(current_url).to match(query_param) - end + expect_to_see_projects(asc) end end context 'Sorting by Created date' do - it 'will sort the project list' do + it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + click_sort_direction + expect_to_see_projects(desc) - expect_to_see_projects(asc) - end - it 'will update the url query' do - select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' + click_sort_direction - [/sort=created_asc/, /sort=created_desc/].each do |query_param| - click_sort_direction - expect(current_url).to match(query_param) - end + expect_to_see_projects(asc) end end context 'Sorting by Stars' do - it 'will sort the project list' do + it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' desc = ["Red ribbon army", "Cell saga", "Frieza saga", "Victorialand", "Treasure"] asc = ["Treasure", "Victorialand", "Red ribbon army", "Cell saga", "Frieza saga"] + click_sort_direction + expect_to_see_projects(desc) - expect_to_see_projects(asc) - end - it 'will update the url query' do - select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' + click_sort_direction - [/sort=stars_asc/, /sort=stars_desc/].each do |query_param| - click_sort_direction - expect(current_url).to match(query_param) - end + expect_to_see_projects(asc) end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 37c63807c82..554cb861563 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -445,6 +445,10 @@ describe ProjectsHelper do Project.all end + before do + stub_feature_flags(project_list_filter_bar: false) + end + it 'returns true when there are projects' do expect(helper.show_projects?(projects, {})).to eq(true) end -- cgit v1.2.1 From c8a530a319d6e2550f41cbf61203e6b2712dc7e0 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Mon, 6 May 2019 16:24:14 +0000 Subject: Show health graphs on group-level Tweak cluster helper and refactor its specs. --- spec/presenters/group_clusterable_presenter_spec.rb | 6 ++++++ spec/presenters/project_clusterable_presenter_spec.rb | 6 ++++++ spec/services/clusters/applications/create_service_spec.rb | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb index fa77273f6aa..cb623fa1fa4 100644 --- a/spec/presenters/group_clusterable_presenter_spec.rb +++ b/spec/presenters/group_clusterable_presenter_spec.rb @@ -82,4 +82,10 @@ describe GroupClusterablePresenter do it { is_expected.to eq(group_cluster_path(group, cluster)) } end + + describe '#clusters_path' do + subject { presenter.clusters_path } + + it { is_expected.to eq(group_clusters_path(group)) } + end end diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb index 6786a84243f..e5857f75aed 100644 --- a/spec/presenters/project_clusterable_presenter_spec.rb +++ b/spec/presenters/project_clusterable_presenter_spec.rb @@ -82,4 +82,10 @@ describe ProjectClusterablePresenter do it { is_expected.to eq(project_cluster_path(project, cluster)) } end + + describe '#clusters_path' do + subject { presenter.clusters_path } + + it { is_expected.to eq(project_clusters_path(project)) } + end end diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb index 20555873503..bb86a742f0e 100644 --- a/spec/services/clusters/applications/create_service_spec.rb +++ b/spec/services/clusters/applications/create_service_spec.rb @@ -151,8 +151,8 @@ describe Clusters::Applications::CreateService do 'helm' | :application_helm | true | false 'ingress' | :application_ingress | true | true 'runner' | :application_runner | true | true + 'prometheus' | :application_prometheus | true | true 'jupyter' | :application_jupyter | false | true - 'prometheus' | :application_prometheus | false | true end with_them do -- cgit v1.2.1 From 65a7a20ae9b70cdb25835b64466106c796825876 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Apr 2019 00:19:10 +0800 Subject: Try to reduce testing view port and unify it Hoping that we don't hit into Selenium::WebDriver::Error::NoSuchDriverError: invalid session id Again. --- spec/support/capybara.rb | 11 ++++++++--- spec/support/helpers/mobile_helpers.rb | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 18a7a392c12..875a9a76e12 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -17,6 +17,8 @@ JS_CONSOLE_FILTER = Regexp.union([ "Download the Vue Devtools extension" ]) +CAPYBARA_WINDOW_SIZE = [1366, 768].freeze + Capybara.register_driver :chrome do |app| capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( # This enables access to logs with `page.driver.manage.get_log(:browser)` @@ -29,7 +31,7 @@ Capybara.register_driver :chrome do |app| ) options = Selenium::WebDriver::Chrome::Options.new - options.add_argument("window-size=1240,1400") + options.add_argument("window-size=#{CAPYBARA_WINDOW_SIZE.join(',')}") # Chrome won't work properly in a Docker container in sandbox mode options.add_argument("no-sandbox") @@ -78,8 +80,11 @@ RSpec.configure do |config| protocol: 'http') # reset window size between tests - unless session.current_window.size == [1240, 1400] - session.current_window.resize_to(1240, 1400) rescue nil + unless session.current_window.size == CAPYBARA_WINDOW_SIZE + begin + session.current_window.resize_to(*CAPYBARA_WINDOW_SIZE) + rescue # ? + end end end diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb index 9dc1f1de436..4230d315d9b 100644 --- a/spec/support/helpers/mobile_helpers.rb +++ b/spec/support/helpers/mobile_helpers.rb @@ -8,7 +8,7 @@ module MobileHelpers end def restore_window_size - resize_window(1366, 768) + resize_window(*CAPYBARA_WINDOW_SIZE) end def resize_window(width, height) -- cgit v1.2.1 From 52b9797af21ea717518041afca804edd3e4aaa0c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Apr 2019 22:39:44 +0800 Subject: Avoid quick action prompt blocking preview button --- spec/support/helpers/features/notes_helpers.rb | 12 +++++++++++- .../issuable/close_quick_action_shared_examples.rb | 7 +++---- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 89517fde6e2..38f30a14409 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -23,8 +23,18 @@ module Spec def preview_note(text) page.within('.js-main-target-form') do - fill_in('note[note]', with: text) + filled_text = fill_in('note[note]', with: text) + + begin + # Dismiss quick action prompt if it appears + filled_text.parent.send_keys(:escape) + rescue Selenium::WebDriver::Error::ElementNotInteractableError + # It's fine if we can't escape when there's no prompt. + end + click_on('Preview') + + yield if block_given? end end end diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb index e0d0b790a0e..a79a61bc708 100644 --- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true shared_examples 'close quick action' do |issuable_type| + include Spec::Support::Helpers::Features::NotesHelpers + before do project.add_maintainer(maintainer) gitlab_sign_in(maintainer) @@ -76,10 +78,7 @@ shared_examples 'close quick action' do |issuable_type| it 'explains close quick action' do visit public_send("project_#{issuable_type}_path", project, issuable) - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "this is done, close\n/close" - click_on 'Preview' - + preview_note("this is done, close\n/close") do expect(page).not_to have_content '/close' expect(page).to have_content 'this is done, close' expect(page).to have_content "Closes this #{issuable_type.to_s.humanize.downcase}." -- cgit v1.2.1 From 00af4c6d6a8ea2997f57d6e872992b9fc72839d8 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 6 May 2019 11:59:02 +0100 Subject: Allow replying to an individual note in the API If you can do this in the UI, you should be able to do it in the API. If a discussion is not a single note discussion, or it is replyable, you can reply to it. --- spec/requests/api/discussions_spec.rb | 4 ++-- .../shared_examples/requests/api/discussions.rb | 24 ++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb index 35c448d187d..16036297ec7 100644 --- a/spec/requests/api/discussions_spec.rb +++ b/spec/requests/api/discussions_spec.rb @@ -13,7 +13,7 @@ describe API::Discussions do let!(:issue) { create(:issue, project: project, author: user) } let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) } - it_behaves_like 'discussions API', 'projects', 'issues', 'iid' do + it_behaves_like 'discussions API', 'projects', 'issues', 'iid', can_reply_to_invididual_notes: true do let(:parent) { project } let(:noteable) { issue } let(:note) { issue_note } @@ -37,7 +37,7 @@ describe API::Discussions do let!(:diff_note) { create(:diff_note_on_merge_request, noteable: noteable, project: project, author: user) } let(:parent) { project } - it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid' + it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid', can_reply_to_invididual_notes: true it_behaves_like 'diff discussions API', 'projects', 'merge_requests', 'iid' it_behaves_like 'resolvable discussions API', 'projects', 'merge_requests', 'iid' end diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb index eff8e401bad..96f79081d26 100644 --- a/spec/support/shared_examples/requests/api/discussions.rb +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -1,4 +1,4 @@ -shared_examples 'discussions API' do |parent_type, noteable_type, id_name| +shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_invididual_notes: false| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do it "returns an array of discussions" do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) @@ -136,13 +136,25 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| expect(response).to have_gitlab_http_status(400) end - it "returns a 400 bad request error if discussion is individual note" do - note.update_attribute(:type, nil) + context 'when the discussion is an individual note' do + before do + note.update!(type: nil) - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes", user), params: { body: 'hi!' } + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes", user), params: { body: 'hi!' } + end - expect(response).to have_gitlab_http_status(400) + if can_reply_to_invididual_notes + it 'creates a new discussion' do + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['type']).to eq('DiscussionNote') + end + else + it 'returns 400 bad request' do + expect(response).to have_gitlab_http_status(400) + end + end end end -- cgit v1.2.1 From c40bad741f963f0b3bf3868d485e419dbbf7b37c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 6 May 2019 19:45:17 +0000 Subject: Fix issuables state_id nil when importing projects from GitHub Issues and merge requests imported from GitHub are having state_id set to null. This fixes the GitHub project importer and schedule migrations to fix state_id. --- .../bitbucket_server_import/importer_spec.rb | 1 + .../github_import/importer/issue_importer_spec.rb | 2 + .../importer/pull_request_importer_spec.rb | 3 ++ ...edule_sync_issuables_state_id_where_nil_spec.rb | 57 ++++++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb index 1e90a2ef27f..cc09804fd53 100644 --- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb @@ -105,6 +105,7 @@ describe Gitlab::BitbucketServerImport::Importer do expect(merge_request.metrics.merged_by).to eq(project.owner) expect(merge_request.metrics.merged_at).to eq(@merge_event.merge_timestamp) expect(merge_request.merge_commit_sha).to eq('12345678') + expect(merge_request.state_id).to eq(3) end it 'imports comments' do diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb index 7901ae005d9..dab5767ece1 100644 --- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb @@ -98,6 +98,7 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach description: 'This is my issue', milestone_id: milestone.id, state: :opened, + state_id: 1, created_at: created_at, updated_at: updated_at }, @@ -127,6 +128,7 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach description: "*Created by: alice*\n\nThis is my issue", milestone_id: milestone.id, state: :opened, + state_id: 1, created_at: created_at, updated_at: updated_at }, diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index 2e4a7c36fb8..6d614c6527a 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -93,6 +93,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi source_branch: 'github/fork/alice/feature', target_branch: 'master', state: :merged, + state_id: 3, milestone_id: milestone.id, author_id: user.id, assignee_id: user.id, @@ -138,6 +139,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi source_branch: 'github/fork/alice/feature', target_branch: 'master', state: :merged, + state_id: 3, milestone_id: milestone.id, author_id: project.creator_id, assignee_id: user.id, @@ -184,6 +186,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi source_branch: 'master-42', target_branch: 'master', state: :merged, + state_id: 3, milestone_id: milestone.id, author_id: user.id, assignee_id: user.id, diff --git a/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb new file mode 100644 index 00000000000..105c05bb7ca --- /dev/null +++ b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190506135400_schedule_sync_issuables_state_id_where_nil') + +describe ScheduleSyncIssuablesStateIdWhereNil, :migration, :sidekiq do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + let(:issues) { table(:issues) } + let(:migration) { described_class.new } + let(:group) { namespaces.create!(name: 'gitlab', path: 'gitlab') } + let(:project) { projects.create!(namespace_id: group.id) } + + shared_examples 'scheduling migrations' do + before do + Sidekiq::Worker.clear_all + stub_const("#{described_class.name}::BATCH_SIZE", 2) + end + + it 'correctly schedules issuable sync background migration' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(migration).to be_scheduled_delayed_migration(120.seconds, resource_1.id, resource_3.id) + expect(migration).to be_scheduled_delayed_migration(240.seconds, resource_5.id, resource_5.id) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end + end + + describe '#up' do + context 'issues' do + it_behaves_like 'scheduling migrations' do + let(:migration) { described_class::ISSUES_MIGRATION } + let!(:resource_1) { issues.create!(description: 'first', state: 'opened', state_id: nil) } + let!(:resource_2) { issues.create!(description: 'second', state: 'closed', state_id: 2) } + let!(:resource_3) { issues.create!(description: 'third', state: 'closed', state_id: nil) } + let!(:resource_4) { issues.create!(description: 'fourth', state: 'closed', state_id: 2) } + let!(:resource_5) { issues.create!(description: 'fifth', state: 'closed', state_id: nil) } + end + end + + context 'merge requests' do + it_behaves_like 'scheduling migrations' do + let(:migration) { described_class::MERGE_REQUESTS_MIGRATION } + let!(:resource_1) { merge_requests.create!(state: 'opened', state_id: nil, target_project_id: project.id, target_branch: 'feature1', source_branch: 'master') } + let!(:resource_2) { merge_requests.create!(state: 'closed', state_id: 2, target_project_id: project.id, target_branch: 'feature2', source_branch: 'master') } + let!(:resource_3) { merge_requests.create!(state: 'merged', state_id: nil, target_project_id: project.id, target_branch: 'feature3', source_branch: 'master') } + let!(:resource_4) { merge_requests.create!(state: 'locked', state_id: 3, target_project_id: project.id, target_branch: 'feature4', source_branch: 'master') } + let!(:resource_5) { merge_requests.create!(state: 'locked', state_id: nil, target_project_id: project.id, target_branch: 'feature4', source_branch: 'master') } + end + end + end +end -- cgit v1.2.1 From 835c616b12a8712634f5ddbfd8a5e70673a1a663 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 6 May 2019 20:24:23 +0000 Subject: Fix editing issues and MRs with NULL lock_version --- spec/models/issue_spec.rb | 12 ++++++++++++ spec/models/merge_request_spec.rb | 12 ++++++++++++ 2 files changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 0cd69cb4817..0ce4add5669 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -55,6 +55,18 @@ describe Issue do end end + describe 'locking' do + it 'works when an issue has a NULL lock_version' do + issue = create(:issue) + + described_class.where(id: issue.id).update_all('lock_version = NULL') + + issue.update!(lock_version: 0, title: 'locking test') + + expect(issue.reload.title).to eq('locking test') + end + end + describe '#order_by_position_and_priority' do let(:project) { create :project } let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 7457efef013..ec2aef6f815 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -31,6 +31,18 @@ describe MergeRequest do end end + describe 'locking' do + it 'works when a merge request has a NULL lock_version' do + merge_request = create(:merge_request) + + described_class.where(id: merge_request.id).update_all('lock_version = NULL') + + merge_request.update!(lock_version: 0, title: 'locking test') + + expect(merge_request.reload.title).to eq('locking test') + end + end + describe '#squash_in_progress?' do let(:repo_path) do Gitlab::GitalyClient::StorageSettings.allow_disk_access do -- cgit v1.2.1 From 733da6d6a015e8c951dcc02250cfe1fab87789c0 Mon Sep 17 00:00:00 2001 From: James Fargher Date: Wed, 10 Apr 2019 14:13:43 +1200 Subject: Instance level kubernetes clusters admin Instance level clusters were already mostly supported, this change adds admin area controllers for cluster CRUD --- .../admin/clusters/applications_controller_spec.rb | 139 ++++++ spec/controllers/admin/clusters_controller_spec.rb | 540 +++++++++++++++++++++ spec/models/clusters/applications/runner_spec.rb | 18 +- spec/policies/clusters/cluster_policy_spec.rb | 16 + spec/policies/clusters/instance_policy_spec.rb | 23 + spec/presenters/clusters/cluster_presenter_spec.rb | 12 + spec/services/clusters/build_service_spec.rb | 8 + 7 files changed, 753 insertions(+), 3 deletions(-) create mode 100644 spec/controllers/admin/clusters/applications_controller_spec.rb create mode 100644 spec/controllers/admin/clusters_controller_spec.rb create mode 100644 spec/policies/clusters/instance_policy_spec.rb (limited to 'spec') diff --git a/spec/controllers/admin/clusters/applications_controller_spec.rb b/spec/controllers/admin/clusters/applications_controller_spec.rb new file mode 100644 index 00000000000..cf202d88acc --- /dev/null +++ b/spec/controllers/admin/clusters/applications_controller_spec.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Admin::Clusters::ApplicationsController do + include AccessMatchersForController + + def current_application + Clusters::Cluster::APPLICATIONS[application] + end + + shared_examples 'a secure endpoint' do + it { expect { subject }.to be_allowed_for(:admin) } + it { expect { subject }.to be_denied_for(:user) } + it { expect { subject }.to be_denied_for(:external) } + end + + let(:cluster) { create(:cluster, :instance, :provided_by_gcp) } + + describe 'POST create' do + subject do + post :create, params: params + end + + let(:application) { 'helm' } + let(:params) { { application: application, id: cluster.id } } + + describe 'functionality' do + let(:admin) { create(:admin) } + + before do + sign_in(admin) + end + + it 'schedule an application installation' do + expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once + + expect { subject }.to change { current_application.count } + expect(response).to have_http_status(:no_content) + expect(cluster.application_helm).to be_scheduled + end + + context 'when cluster do not exists' do + before do + cluster.destroy! + end + + it 'return 404' do + expect { subject }.not_to change { current_application.count } + expect(response).to have_http_status(:not_found) + end + end + + context 'when application is unknown' do + let(:application) { 'unkwnown-app' } + + it 'return 404' do + is_expected.to have_http_status(:not_found) + end + end + + context 'when application is already installing' do + before do + create(:clusters_applications_helm, :installing, cluster: cluster) + end + + it 'returns 400' do + is_expected.to have_http_status(:bad_request) + end + end + end + + describe 'security' do + before do + allow(ClusterInstallAppWorker).to receive(:perform_async) + end + + it_behaves_like 'a secure endpoint' + end + end + + describe 'PATCH update' do + subject do + patch :update, params: params + end + + let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) } + let(:application_name) { application.name } + let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } } + + describe 'functionality' do + let(:admin) { create(:admin) } + + before do + sign_in(admin) + end + + context "when cluster and app exists" do + it "schedules an application update" do + expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once + + is_expected.to have_http_status(:no_content) + + expect(cluster.application_cert_manager).to be_scheduled + end + end + + context 'when cluster do not exists' do + before do + cluster.destroy! + end + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is unknown' do + let(:application_name) { 'unkwnown-app' } + + it { is_expected.to have_http_status(:not_found) } + end + + context 'when application is already scheduled' do + before do + application.make_scheduled! + end + + it { is_expected.to have_http_status(:bad_request) } + end + end + + describe 'security' do + before do + allow(ClusterPatchAppWorker).to receive(:perform_async) + end + + it_behaves_like 'a secure endpoint' + end + end +end diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb new file mode 100644 index 00000000000..c59fc93ae09 --- /dev/null +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -0,0 +1,540 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Admin::ClustersController do + include AccessMatchersForController + include GoogleApi::CloudPlatformHelpers + + let(:admin) { create(:admin) } + + before do + sign_in(admin) + end + + describe 'GET index' do + def go(params = {}) + get :index, params: params + end + + context 'when feature flag is not enabled' do + before do + stub_feature_flags(instance_clusters: false) + end + + it 'renders 404' do + go + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when feature flag is enabled' do + before do + stub_feature_flags(instance_clusters: true) + end + + describe 'functionality' do + context 'when instance has one or more clusters' do + let!(:enabled_cluster) do + create(:cluster, :provided_by_gcp, :instance) + end + + let!(:disabled_cluster) do + create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance) + end + + it 'lists available clusters' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:index) + expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster]) + end + + context 'when page is specified' do + let(:last_page) { Clusters::Cluster.instance_type.page.total_pages } + + before do + allow(Clusters::Cluster).to receive(:paginates_per).and_return(1) + create_list(:cluster, 2, :provided_by_gcp, :production_environment, :instance) + end + + it 'redirects to the page' do + go(page: last_page) + + expect(response).to have_gitlab_http_status(:ok) + expect(assigns(:clusters).current_page).to eq(last_page) + end + end + end + + context 'when instance does not have a cluster' do + it 'returns an empty state page' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:index, partial: :empty_state) + expect(assigns(:clusters)).to eq([]) + end + end + end + end + + describe 'security' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'GET new' do + def go + get :new + end + + describe 'functionality for new cluster' do + context 'when omniauth has been configured' do + let(:key) { 'secret-key' } + let(:session_key_for_redirect_uri) do + GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key) + end + + before do + allow(SecureRandom).to receive(:hex).and_return(key) + end + + it 'has authorize_url' do + go + + expect(assigns(:authorize_url)).to include(key) + expect(session[session_key_for_redirect_uri]).to eq(new_admin_cluster_path) + end + end + + context 'when omniauth has not configured' do + before do + stub_omniauth_setting(providers: []) + end + + it 'does not have authorize_url' do + go + + expect(assigns(:authorize_url)).to be_nil + end + end + + context 'when access token is valid' do + before do + stub_google_api_validate_token + end + + it 'has new object' do + go + + expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) + end + end + + context 'when access token is expired' do + before do + stub_google_api_expired_token + end + + it { expect(@valid_gcp_token).to be_falsey } + end + + context 'when access token is not stored in session' do + it { expect(@valid_gcp_token).to be_falsey } + end + end + + describe 'functionality for existing cluster' do + it 'has new object' do + go + + expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'POST create for new cluster' do + let(:legacy_abac_param) { 'true' } + let(:params) do + { + cluster: { + name: 'new-cluster', + provider_gcp_attributes: { + gcp_project_id: 'gcp-project-12345', + legacy_abac: legacy_abac_param + } + } + } + end + + def go + post :create_gcp, params: params + end + + describe 'functionality' do + context 'when access token is valid' do + before do + stub_google_api_validate_token + end + + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Providers::Gcp.count } + + cluster = Clusters::Cluster.instance_type.first + + expect(response).to redirect_to(admin_cluster_path(cluster)) + expect(cluster).to be_gcp + expect(cluster).to be_kubernetes + expect(cluster.provider_gcp).to be_legacy_abac + end + + context 'when legacy_abac param is false' do + let(:legacy_abac_param) { 'false' } + + it 'creates a new cluster with legacy_abac_disabled' do + expect(ClusterProvisionWorker).to receive(:perform_async) + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Providers::Gcp.count } + expect(Clusters::Cluster.instance_type.first.provider_gcp).not_to be_legacy_abac + end + end + end + + context 'when access token is expired' do + before do + stub_google_api_expired_token + end + + it { expect(@valid_gcp_token).to be_falsey } + end + + context 'when access token is not stored in session' do + it { expect(@valid_gcp_token).to be_falsey } + end + end + + describe 'security' do + before do + allow_any_instance_of(described_class) + .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) + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_create) do + OpenStruct.new( + self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123', + status: 'RUNNING' + ) + end + + allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil) + end + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'POST create for existing cluster' do + let(:params) do + { + cluster: { + name: 'new-cluster', + platform_kubernetes_attributes: { + api_url: 'http://my-url', + token: 'test' + } + } + } + end + + def go + post :create_user, params: params + end + + describe 'functionality' do + context 'when creates a cluster' do + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Platforms::Kubernetes.count } + + cluster = Clusters::Cluster.instance_type.first + + expect(response).to redirect_to(admin_cluster_path(cluster)) + expect(cluster).to be_user + expect(cluster).to be_kubernetes + end + end + + context 'when creates a RBAC-enabled cluster' do + let(:params) do + { + cluster: { + name: 'new-cluster', + platform_kubernetes_attributes: { + api_url: 'http://my-url', + token: 'test', + authorization_type: 'rbac' + } + } + } + end + + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Platforms::Kubernetes.count } + + cluster = Clusters::Cluster.instance_type.first + + expect(response).to redirect_to(admin_cluster_path(cluster)) + expect(cluster).to be_user + expect(cluster).to be_kubernetes + expect(cluster).to be_platform_kubernetes_rbac + end + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'GET cluster_status' do + let(:cluster) { create(:cluster, :providing_by_gcp, :instance) } + + def go + get :cluster_status, + params: { + id: cluster + }, + format: :json + end + + describe 'functionality' do + it 'responds with matching schema' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('cluster_status') + end + + it 'invokes schedule_status_update on each application' do + expect_any_instance_of(Clusters::Applications::Ingress).to receive(:schedule_status_update) + + go + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'GET show' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + + def go + get :show, + params: { + id: cluster + } + end + + describe 'functionality' do + it 'renders view' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(assigns(:cluster)).to eq(cluster) + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'PUT update' do + def go(format: :html) + put :update, params: params.merge( + id: cluster, + format: format + ) + end + + let(:cluster) { create(:cluster, :provided_by_user, :instance) } + let(:domain) { 'test-domain.com' } + + let(:params) do + { + cluster: { + enabled: false, + name: 'my-new-cluster-name', + base_domain: domain + } + } + end + + it 'updates and redirects back to show page' do + go + + cluster.reload + expect(response).to redirect_to(admin_cluster_path(cluster)) + expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') + expect(cluster.enabled).to be_falsey + expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster.domain).to eq('test-domain.com') + end + + context 'when domain is invalid' do + let(:domain) { 'http://not-a-valid-domain' } + + it 'does not update cluster attributes' do + go + + cluster.reload + expect(response).to render_template(:show) + expect(cluster.name).not_to eq('my-new-cluster-name') + expect(cluster.domain).not_to eq('test-domain.com') + end + end + + context 'when format is json' do + context 'when changing parameters' do + context 'when valid parameters are used' do + let(:params) do + { + cluster: { + enabled: false, + name: 'my-new-cluster-name', + domain: domain + } + } + end + + it 'updates and redirects back to show page' do + go(format: :json) + + cluster.reload + expect(response).to have_http_status(:no_content) + expect(cluster.enabled).to be_falsey + expect(cluster.name).to eq('my-new-cluster-name') + end + end + + context 'when invalid parameters are used' do + let(:params) do + { + cluster: { + enabled: false, + name: '' + } + } + end + + it 'rejects changes' do + go(format: :json) + + expect(response).to have_http_status(:bad_request) + end + end + end + end + + describe 'security' do + set(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'DELETE destroy' do + let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) } + + def go + delete :destroy, + params: { + id: cluster + } + end + + describe 'functionality' do + context 'when cluster is provided by GCP' do + context 'when cluster is created' do + it 'destroys and redirects back to clusters list' do + expect { go } + .to change { Clusters::Cluster.count }.by(-1) + .and change { Clusters::Platforms::Kubernetes.count }.by(-1) + .and change { Clusters::Providers::Gcp.count }.by(-1) + + expect(response).to redirect_to(admin_clusters_path) + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') + end + end + + context 'when cluster is being created' do + let!(:cluster) { create(:cluster, :providing_by_gcp, :production_environment, :instance) } + + it 'destroys and redirects back to clusters list' do + expect { go } + .to change { Clusters::Cluster.count }.by(-1) + .and change { Clusters::Providers::Gcp.count }.by(-1) + + expect(response).to redirect_to(admin_clusters_path) + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') + end + end + end + + context 'when cluster is provided by user' do + let!(:cluster) { create(:cluster, :provided_by_user, :production_environment, :instance) } + + it 'destroys and redirects back to clusters list' do + expect { go } + .to change { Clusters::Cluster.count }.by(-1) + .and change { Clusters::Platforms::Kubernetes.count }.by(-1) + .and change { Clusters::Providers::Gcp.count }.by(0) + + expect(response).to redirect_to(admin_clusters_path) + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') + end + end + end + + describe 'security' do + set(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end +end diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index bdc0cb8ed86..4f0cd0efe9c 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -69,8 +69,8 @@ describe Clusters::Applications::Runner do expect(values).to include('privileged: true') expect(values).to include('image: ubuntu:16.04') expect(values).to include('resources') - expect(values).to match(/runnerToken: '?#{ci_runner.token}/) - expect(values).to match(/gitlabUrl: '?#{Gitlab::Routing.url_helpers.root_url}/) + expect(values).to match(/runnerToken: '?#{Regexp.escape(ci_runner.token)}/) + expect(values).to match(/gitlabUrl: '?#{Regexp.escape(Gitlab::Routing.url_helpers.root_url)}/) end context 'without a runner' do @@ -83,7 +83,7 @@ describe Clusters::Applications::Runner do end it 'uses the new runner token' do - expect(values).to match(/runnerToken: '?#{runner.token}/) + expect(values).to match(/runnerToken: '?#{Regexp.escape(runner.token)}/) end end @@ -114,6 +114,18 @@ describe Clusters::Applications::Runner do expect(runner.groups).to eq [group] end end + + context 'instance cluster' do + let(:cluster) { create(:cluster, :with_installed_helm, :instance) } + + include_examples 'runner creation' + + it 'creates an instance runner' do + subject + + expect(runner).to be_instance_type + end + end end context 'with duplicated values on vendor/runner/values.yaml' do diff --git a/spec/policies/clusters/cluster_policy_spec.rb b/spec/policies/clusters/cluster_policy_spec.rb index b2f0ca1bc30..cc3dde154dc 100644 --- a/spec/policies/clusters/cluster_policy_spec.rb +++ b/spec/policies/clusters/cluster_policy_spec.rb @@ -66,5 +66,21 @@ describe Clusters::ClusterPolicy, :models do it { expect(policy).to be_disallowed :admin_cluster } end end + + context 'instance cluster' do + let(:cluster) { create(:cluster, :instance) } + + context 'when user' do + it { expect(policy).to be_disallowed :update_cluster } + it { expect(policy).to be_disallowed :admin_cluster } + end + + context 'when admin' do + let(:user) { create(:admin) } + + it { expect(policy).to be_allowed :update_cluster } + it { expect(policy).to be_allowed :admin_cluster } + end + end end end diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb new file mode 100644 index 00000000000..ac0f9da5d19 --- /dev/null +++ b/spec/policies/clusters/instance_policy_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::InstancePolicy do + let(:cluster) { create(:cluster, :instance) } + let(:user) { create(:user) } + let(:policy) { described_class.new(user, cluster) } + + describe 'rules' do + context 'when user' do + it { expect(policy).to be_disallowed :update_cluster } + it { expect(policy).to be_disallowed :admin_cluster } + end + + context 'when admin' do + let(:user) { create(:admin) } + + it { expect(policy).to be_allowed :update_cluster } + it { expect(policy).to be_allowed :admin_cluster } + end + end +end diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index a9d786bc872..42701a5f8d1 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -210,6 +210,12 @@ describe Clusters::ClusterPresenter do it { is_expected.to eq('Group cluster') } end + + context 'instance_type cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + + it { is_expected.to eq('Instance cluster') } + end end describe '#show_path' do @@ -227,6 +233,12 @@ describe Clusters::ClusterPresenter do it { is_expected.to eq(group_cluster_path(group, cluster)) } end + + context 'instance_type cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + + it { is_expected.to eq(admin_cluster_path(cluster)) } + end end describe '#read_only_kubernetes_platform_fields?' do diff --git a/spec/services/clusters/build_service_spec.rb b/spec/services/clusters/build_service_spec.rb index da0cb42b3a1..f3e852726f4 100644 --- a/spec/services/clusters/build_service_spec.rb +++ b/spec/services/clusters/build_service_spec.rb @@ -21,5 +21,13 @@ describe Clusters::BuildService do is_expected.to be_group_type end end + + describe 'when cluster subject is an instance' do + let(:cluster_subject) { Clusters::Instance.new } + + it 'sets the cluster_type to instance_type' do + is_expected.to be_instance_type + end + end end end -- cgit v1.2.1 From 8db382b05545fdef0a60bcff65f8c23e8b1ed282 Mon Sep 17 00:00:00 2001 From: James Fargher Date: Thu, 18 Apr 2019 14:45:31 +1200 Subject: Allow projects to use instance level clusters There are two cluster hierarchies one for the deployment platform and one for controllers. The main difference is that deployment platforms do not check user permissions and only return the first match. --- spec/finders/cluster_ancestors_finder_spec.rb | 29 +++++++++++++++++++------- spec/models/clusters/cluster_spec.rb | 9 ++++++++ spec/policies/clusters/instance_policy_spec.rb | 2 ++ 3 files changed, 32 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb index 332086c42e2..750042b6b54 100644 --- a/spec/finders/cluster_ancestors_finder_spec.rb +++ b/spec/finders/cluster_ancestors_finder_spec.rb @@ -8,11 +8,15 @@ describe ClusterAncestorsFinder, '#execute' do let(:user) { create(:user) } let!(:project_cluster) do - create(:cluster, :provided_by_user, cluster_type: :project_type, projects: [project]) + create(:cluster, :provided_by_user, :project, projects: [project]) end let!(:group_cluster) do - create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) + create(:cluster, :provided_by_user, :group, groups: [group]) + end + + let!(:instance_cluster) do + create(:cluster, :provided_by_user, :instance) end subject { described_class.new(clusterable, user).execute } @@ -25,7 +29,7 @@ describe ClusterAncestorsFinder, '#execute' do end it 'returns the project clusters followed by group clusters' do - is_expected.to eq([project_cluster, group_cluster]) + is_expected.to eq([project_cluster, group_cluster, instance_cluster]) end context 'nested groups', :nested_groups do @@ -33,11 +37,11 @@ describe ClusterAncestorsFinder, '#execute' do let(:parent_group) { create(:group) } let!(:parent_group_cluster) do - create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group]) + create(:cluster, :provided_by_user, :group, groups: [parent_group]) end it 'returns the project clusters followed by group clusters ordered ascending the hierarchy' do - is_expected.to eq([project_cluster, group_cluster, parent_group_cluster]) + is_expected.to eq([project_cluster, group_cluster, parent_group_cluster, instance_cluster]) end end end @@ -58,7 +62,7 @@ describe ClusterAncestorsFinder, '#execute' do end it 'returns the list of group clusters' do - is_expected.to eq([group_cluster]) + is_expected.to eq([group_cluster, instance_cluster]) end context 'nested groups', :nested_groups do @@ -66,12 +70,21 @@ describe ClusterAncestorsFinder, '#execute' do let(:parent_group) { create(:group) } let!(:parent_group_cluster) do - create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group]) + create(:cluster, :provided_by_user, :group, groups: [parent_group]) end it 'returns the list of group clusters ordered ascending the hierarchy' do - is_expected.to eq([group_cluster, parent_group_cluster]) + is_expected.to eq([group_cluster, parent_group_cluster, instance_cluster]) end end end + + context 'for an instance' do + let(:clusterable) { Clusters::Instance.new } + let(:user) { create(:admin) } + + it 'returns the list of instance clusters' do + is_expected.to eq([instance_cluster]) + end + end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index e1506c06044..58203da5b22 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -325,6 +325,15 @@ describe Clusters::Cluster do end end + context 'when group and instance have configured kubernetes clusters' do + let(:project) { create(:project, group: group) } + let!(:instance_cluster) { create(:cluster, :provided_by_gcp, :instance) } + + it 'returns clusters in order, descending the hierachy' do + is_expected.to eq([group_cluster, instance_cluster]) + end + end + context 'when sub-group has configured kubernetes cluster', :nested_groups do let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) } let(:sub_group) { sub_group_cluster.group } diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb index ac0f9da5d19..f4652c2ad00 100644 --- a/spec/policies/clusters/instance_policy_spec.rb +++ b/spec/policies/clusters/instance_policy_spec.rb @@ -9,6 +9,7 @@ describe Clusters::InstancePolicy do describe 'rules' do context 'when user' do + it { expect(policy).to be_disallowed :read_cluster } it { expect(policy).to be_disallowed :update_cluster } it { expect(policy).to be_disallowed :admin_cluster } end @@ -16,6 +17,7 @@ describe Clusters::InstancePolicy do context 'when admin' do let(:user) { create(:admin) } + it { expect(policy).to be_allowed :read_cluster } it { expect(policy).to be_allowed :update_cluster } it { expect(policy).to be_allowed :admin_cluster } end -- cgit v1.2.1 From beb66cfcba26d0796644ccce2dfac8c65a808144 Mon Sep 17 00:00:00 2001 From: James Fargher Date: Thu, 2 May 2019 13:07:38 +1200 Subject: Check instance cluster feature at policy level Try to simplify feature flag checks by using policies --- .../admin/clusters/applications_controller_spec.rb | 10 ++++++ .../concerns/enforces_admin_authentication_spec.rb | 38 ++++++++++++++++++++++ spec/policies/clusters/instance_policy_spec.rb | 21 +++++++++--- 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 spec/controllers/concerns/enforces_admin_authentication_spec.rb (limited to 'spec') diff --git a/spec/controllers/admin/clusters/applications_controller_spec.rb b/spec/controllers/admin/clusters/applications_controller_spec.rb index cf202d88acc..76f261e7d3f 100644 --- a/spec/controllers/admin/clusters/applications_controller_spec.rb +++ b/spec/controllers/admin/clusters/applications_controller_spec.rb @@ -13,6 +13,16 @@ describe Admin::Clusters::ApplicationsController do it { expect { subject }.to be_allowed_for(:admin) } it { expect { subject }.to be_denied_for(:user) } it { expect { subject }.to be_denied_for(:external) } + + context 'when instance clusters are disabled' do + before do + stub_feature_flags(instance_clusters: false) + end + + it 'returns 404' do + is_expected.to have_http_status(:not_found) + end + end end let(:cluster) { create(:cluster, :instance, :provided_by_gcp) } diff --git a/spec/controllers/concerns/enforces_admin_authentication_spec.rb b/spec/controllers/concerns/enforces_admin_authentication_spec.rb new file mode 100644 index 00000000000..9025293f9ea --- /dev/null +++ b/spec/controllers/concerns/enforces_admin_authentication_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe EnforcesAdminAuthentication do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + controller(ApplicationController) do + # `described_class` is not available in this context + include EnforcesAdminAuthentication # rubocop:disable RSpec/DescribedClass + + def index + head :ok + end + end + + describe 'authenticate_admin!' do + context 'as an admin' do + let(:user) { create(:admin) } + + it 'renders ok' do + get :index + expect(response).to have_gitlab_http_status(200) + end + end + + context 'as a user' do + it 'renders a 404' do + get :index + expect(response).to have_gitlab_http_status(404) + end + end + end +end diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb index f4652c2ad00..9d755c6d29d 100644 --- a/spec/policies/clusters/instance_policy_spec.rb +++ b/spec/policies/clusters/instance_policy_spec.rb @@ -3,9 +3,8 @@ require 'spec_helper' describe Clusters::InstancePolicy do - let(:cluster) { create(:cluster, :instance) } let(:user) { create(:user) } - let(:policy) { described_class.new(user, cluster) } + let(:policy) { described_class.new(user, Clusters::Instance.new) } describe 'rules' do context 'when user' do @@ -17,9 +16,21 @@ describe Clusters::InstancePolicy do context 'when admin' do let(:user) { create(:admin) } - it { expect(policy).to be_allowed :read_cluster } - it { expect(policy).to be_allowed :update_cluster } - it { expect(policy).to be_allowed :admin_cluster } + context 'with instance_level_clusters enabled' do + it { expect(policy).to be_allowed :read_cluster } + it { expect(policy).to be_allowed :update_cluster } + it { expect(policy).to be_allowed :admin_cluster } + end + + context 'with instance_level_clusters disabled' do + before do + stub_feature_flags(instance_clusters: false) + end + + it { expect(policy).to be_disallowed :read_cluster } + it { expect(policy).to be_disallowed :update_cluster } + it { expect(policy).to be_disallowed :admin_cluster } + end end end end -- cgit v1.2.1 From 265dee5cc6a298370d708acdd11ee56997667332 Mon Sep 17 00:00:00 2001 From: James Fargher Date: Thu, 2 May 2019 13:07:58 +1200 Subject: Stop using `go` function in clusters controller specs --- spec/controllers/admin/clusters_controller_spec.rb | 108 ++++++++++----------- 1 file changed, 54 insertions(+), 54 deletions(-) (limited to 'spec') diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index c59fc93ae09..e5fcc475d99 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -13,7 +13,7 @@ describe Admin::ClustersController do end describe 'GET index' do - def go(params = {}) + def get_index(params = {}) get :index, params: params end @@ -23,7 +23,7 @@ describe Admin::ClustersController do end it 'renders 404' do - go + get_index expect(response).to have_gitlab_http_status(404) end @@ -45,7 +45,7 @@ describe Admin::ClustersController do end it 'lists available clusters' do - go + get_index expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template(:index) @@ -61,7 +61,7 @@ describe Admin::ClustersController do end it 'redirects to the page' do - go(page: last_page) + get_index(page: last_page) expect(response).to have_gitlab_http_status(:ok) expect(assigns(:clusters).current_page).to eq(last_page) @@ -71,7 +71,7 @@ describe Admin::ClustersController do context 'when instance does not have a cluster' do it 'returns an empty state page' do - go + get_index expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template(:index, partial: :empty_state) @@ -84,14 +84,14 @@ describe Admin::ClustersController do describe 'security' do let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { get_index }.to be_allowed_for(:admin) } + it { expect { get_index }.to be_denied_for(:user) } + it { expect { get_index }.to be_denied_for(:external) } end end describe 'GET new' do - def go + def get_new get :new end @@ -107,7 +107,7 @@ describe Admin::ClustersController do end it 'has authorize_url' do - go + get_new expect(assigns(:authorize_url)).to include(key) expect(session[session_key_for_redirect_uri]).to eq(new_admin_cluster_path) @@ -120,7 +120,7 @@ describe Admin::ClustersController do end it 'does not have authorize_url' do - go + get_new expect(assigns(:authorize_url)).to be_nil end @@ -132,7 +132,7 @@ describe Admin::ClustersController do end it 'has new object' do - go + get_new expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) end @@ -153,16 +153,16 @@ describe Admin::ClustersController do describe 'functionality for existing cluster' do it 'has new object' do - go + get_new expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) end end describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { get_new }.to be_allowed_for(:admin) } + it { expect { get_new }.to be_denied_for(:user) } + it { expect { get_new }.to be_denied_for(:external) } end end @@ -180,7 +180,7 @@ describe Admin::ClustersController do } end - def go + def post_create_gcp post :create_gcp, params: params end @@ -192,7 +192,7 @@ describe Admin::ClustersController do it 'creates a new cluster' do expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } + expect { post_create_gcp }.to change { Clusters::Cluster.count } .and change { Clusters::Providers::Gcp.count } cluster = Clusters::Cluster.instance_type.first @@ -208,7 +208,7 @@ describe Admin::ClustersController do it 'creates a new cluster with legacy_abac_disabled' do expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } + expect { post_create_gcp }.to change { Clusters::Cluster.count } .and change { Clusters::Providers::Gcp.count } expect(Clusters::Cluster.instance_type.first.provider_gcp).not_to be_legacy_abac end @@ -245,9 +245,9 @@ describe Admin::ClustersController do allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil) end - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { post_create_gcp }.to be_allowed_for(:admin) } + it { expect { post_create_gcp }.to be_denied_for(:user) } + it { expect { post_create_gcp }.to be_denied_for(:external) } end end @@ -264,7 +264,7 @@ describe Admin::ClustersController do } end - def go + def post_create_user post :create_user, params: params end @@ -273,7 +273,7 @@ describe Admin::ClustersController do it 'creates a new cluster' do expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } + expect { post_create_user }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } cluster = Clusters::Cluster.instance_type.first @@ -301,7 +301,7 @@ describe Admin::ClustersController do it 'creates a new cluster' do expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } + expect { post_create_user }.to change { Clusters::Cluster.count } .and change { Clusters::Platforms::Kubernetes.count } cluster = Clusters::Cluster.instance_type.first @@ -315,16 +315,16 @@ describe Admin::ClustersController do end describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { post_create_user }.to be_allowed_for(:admin) } + it { expect { post_create_user }.to be_denied_for(:user) } + it { expect { post_create_user }.to be_denied_for(:external) } end end describe 'GET cluster_status' do let(:cluster) { create(:cluster, :providing_by_gcp, :instance) } - def go + def get_cluster_status get :cluster_status, params: { id: cluster @@ -334,7 +334,7 @@ describe Admin::ClustersController do describe 'functionality' do it 'responds with matching schema' do - go + get_cluster_status expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('cluster_status') @@ -343,21 +343,21 @@ describe Admin::ClustersController do it 'invokes schedule_status_update on each application' do expect_any_instance_of(Clusters::Applications::Ingress).to receive(:schedule_status_update) - go + get_cluster_status end end describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { get_cluster_status }.to be_allowed_for(:admin) } + it { expect { get_cluster_status }.to be_denied_for(:user) } + it { expect { get_cluster_status }.to be_denied_for(:external) } end end describe 'GET show' do let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } - def go + def get_show get :show, params: { id: cluster @@ -366,7 +366,7 @@ describe Admin::ClustersController do describe 'functionality' do it 'renders view' do - go + get_show expect(response).to have_gitlab_http_status(:ok) expect(assigns(:cluster)).to eq(cluster) @@ -374,14 +374,14 @@ describe Admin::ClustersController do end describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { get_show }.to be_allowed_for(:admin) } + it { expect { get_show }.to be_denied_for(:user) } + it { expect { get_show }.to be_denied_for(:external) } end end describe 'PUT update' do - def go(format: :html) + def put_update(format: :html) put :update, params: params.merge( id: cluster, format: format @@ -402,7 +402,7 @@ describe Admin::ClustersController do end it 'updates and redirects back to show page' do - go + put_update cluster.reload expect(response).to redirect_to(admin_cluster_path(cluster)) @@ -416,7 +416,7 @@ describe Admin::ClustersController do let(:domain) { 'http://not-a-valid-domain' } it 'does not update cluster attributes' do - go + put_update cluster.reload expect(response).to render_template(:show) @@ -439,7 +439,7 @@ describe Admin::ClustersController do end it 'updates and redirects back to show page' do - go(format: :json) + put_update(format: :json) cluster.reload expect(response).to have_http_status(:no_content) @@ -459,7 +459,7 @@ describe Admin::ClustersController do end it 'rejects changes' do - go(format: :json) + put_update(format: :json) expect(response).to have_http_status(:bad_request) end @@ -470,16 +470,16 @@ describe Admin::ClustersController do describe 'security' do set(:cluster) { create(:cluster, :provided_by_gcp, :instance) } - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { put_update }.to be_allowed_for(:admin) } + it { expect { put_update }.to be_denied_for(:user) } + it { expect { put_update }.to be_denied_for(:external) } end end describe 'DELETE destroy' do let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) } - def go + def delete_destroy delete :destroy, params: { id: cluster @@ -490,7 +490,7 @@ describe Admin::ClustersController do context 'when cluster is provided by GCP' do context 'when cluster is created' do it 'destroys and redirects back to clusters list' do - expect { go } + expect { delete_destroy } .to change { Clusters::Cluster.count }.by(-1) .and change { Clusters::Platforms::Kubernetes.count }.by(-1) .and change { Clusters::Providers::Gcp.count }.by(-1) @@ -504,7 +504,7 @@ describe Admin::ClustersController do let!(:cluster) { create(:cluster, :providing_by_gcp, :production_environment, :instance) } it 'destroys and redirects back to clusters list' do - expect { go } + expect { delete_destroy } .to change { Clusters::Cluster.count }.by(-1) .and change { Clusters::Providers::Gcp.count }.by(-1) @@ -518,7 +518,7 @@ describe Admin::ClustersController do let!(:cluster) { create(:cluster, :provided_by_user, :production_environment, :instance) } it 'destroys and redirects back to clusters list' do - expect { go } + expect { delete_destroy } .to change { Clusters::Cluster.count }.by(-1) .and change { Clusters::Platforms::Kubernetes.count }.by(-1) .and change { Clusters::Providers::Gcp.count }.by(0) @@ -532,9 +532,9 @@ describe Admin::ClustersController do describe 'security' do set(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) } - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } + it { expect { delete_destroy }.to be_allowed_for(:admin) } + it { expect { delete_destroy }.to be_denied_for(:user) } + it { expect { delete_destroy }.to be_denied_for(:external) } end end end -- cgit v1.2.1 From c40a99a05842d0976dd1bca532fbc67c0ccee04d Mon Sep 17 00:00:00 2001 From: James Fargher Date: Fri, 3 May 2019 09:30:53 +1200 Subject: Change specs to match gitlab code standards --- spec/controllers/admin/clusters_controller_spec.rb | 20 ++++++++++---------- .../concerns/enforces_admin_authentication_spec.rb | 2 ++ 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index e5fcc475d99..7b77cb186a4 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -12,7 +12,7 @@ describe Admin::ClustersController do sign_in(admin) end - describe 'GET index' do + describe 'GET #index' do def get_index(params = {}) get :index, params: params end @@ -22,7 +22,7 @@ describe Admin::ClustersController do stub_feature_flags(instance_clusters: false) end - it 'renders 404' do + it 'responds with not found' do get_index expect(response).to have_gitlab_http_status(404) @@ -90,7 +90,7 @@ describe Admin::ClustersController do end end - describe 'GET new' do + describe 'GET #new' do def get_new get :new end @@ -166,7 +166,7 @@ describe Admin::ClustersController do end end - describe 'POST create for new cluster' do + describe 'POST #create_gcp' do let(:legacy_abac_param) { 'true' } let(:params) do { @@ -251,7 +251,7 @@ describe Admin::ClustersController do end end - describe 'POST create for existing cluster' do + describe 'POST #create_user' do let(:params) do { cluster: { @@ -321,7 +321,7 @@ describe Admin::ClustersController do end end - describe 'GET cluster_status' do + describe 'GET #cluster_status' do let(:cluster) { create(:cluster, :providing_by_gcp, :instance) } def get_cluster_status @@ -354,7 +354,7 @@ describe Admin::ClustersController do end end - describe 'GET show' do + describe 'GET #show' do let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } def get_show @@ -365,7 +365,7 @@ describe Admin::ClustersController do end describe 'functionality' do - it 'renders view' do + it 'responds successfully' do get_show expect(response).to have_gitlab_http_status(:ok) @@ -380,7 +380,7 @@ describe Admin::ClustersController do end end - describe 'PUT update' do + describe 'PUT #update' do def put_update(format: :html) put :update, params: params.merge( id: cluster, @@ -476,7 +476,7 @@ describe Admin::ClustersController do end end - describe 'DELETE destroy' do + describe 'DELETE #destroy' do let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) } def delete_destroy diff --git a/spec/controllers/concerns/enforces_admin_authentication_spec.rb b/spec/controllers/concerns/enforces_admin_authentication_spec.rb index 9025293f9ea..e6a6702fdea 100644 --- a/spec/controllers/concerns/enforces_admin_authentication_spec.rb +++ b/spec/controllers/concerns/enforces_admin_authentication_spec.rb @@ -24,6 +24,7 @@ describe EnforcesAdminAuthentication do it 'renders ok' do get :index + expect(response).to have_gitlab_http_status(200) end end @@ -31,6 +32,7 @@ describe EnforcesAdminAuthentication do context 'as a user' do it 'renders a 404' do get :index + expect(response).to have_gitlab_http_status(404) end end -- cgit v1.2.1 From f07d445b0c3296b3070221ccd70443fba15ba708 Mon Sep 17 00:00:00 2001 From: James Fargher Date: Tue, 7 May 2019 08:49:27 +1200 Subject: Remove duplicate clusterable presenter method --- spec/presenters/group_clusterable_presenter_spec.rb | 6 ------ spec/presenters/project_clusterable_presenter_spec.rb | 6 ------ 2 files changed, 12 deletions(-) (limited to 'spec') diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb index cb623fa1fa4..fa77273f6aa 100644 --- a/spec/presenters/group_clusterable_presenter_spec.rb +++ b/spec/presenters/group_clusterable_presenter_spec.rb @@ -82,10 +82,4 @@ describe GroupClusterablePresenter do it { is_expected.to eq(group_cluster_path(group, cluster)) } end - - describe '#clusters_path' do - subject { presenter.clusters_path } - - it { is_expected.to eq(group_clusters_path(group)) } - end end diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb index e5857f75aed..6786a84243f 100644 --- a/spec/presenters/project_clusterable_presenter_spec.rb +++ b/spec/presenters/project_clusterable_presenter_spec.rb @@ -82,10 +82,4 @@ describe ProjectClusterablePresenter do it { is_expected.to eq(project_cluster_path(project, cluster)) } end - - describe '#clusters_path' do - subject { presenter.clusters_path } - - it { is_expected.to eq(project_clusters_path(project)) } - end end -- cgit v1.2.1 From 5ee7884d91c4513e1b7322a4ca30a2c908e931b8 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 6 May 2019 21:24:19 +0000 Subject: GraphQL - Add extra complexity for resolvers If a field is a resolver, its complexity is automatically increased. By default we add extra points for sort and search arguments (which will be common for various resolvers). For specific resolvers we add field-specific complexity, e.g. for Issues complexity is increased if we filter issues by `labelName` (because then SQL query is more complex). We may want to tune these values in future depending on real-life results. Complexity is also dependent on the number of loaded nodes, but only if we don't search by specific ID(s). Also added complexity is limited (by default only twice more than child complexity) - the reason is that although it's more complex to process more items, the complexity increase is not linear (there is not so much difference between loading 10, 20 or 100 records from DB). --- spec/graphql/resolvers/base_resolver_spec.rb | 15 +++++++++++++ .../resolvers/concerns/resolves_pipelines_spec.rb | 8 +++++++ spec/graphql/resolvers/issues_resolver_spec.rb | 7 ++++++ spec/graphql/resolvers/project_resolver_spec.rb | 8 +++++++ spec/graphql/types/base_field_spec.rb | 26 ++++++++++++++++++++++ 5 files changed, 64 insertions(+) (limited to 'spec') diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb index e3a34762b62..9982288e206 100644 --- a/spec/graphql/resolvers/base_resolver_spec.rb +++ b/spec/graphql/resolvers/base_resolver_spec.rb @@ -28,4 +28,19 @@ describe Resolvers::BaseResolver do expect(result).to eq(test: 1) end end + + it 'increases complexity based on arguments' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1) + + expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7 + end + + it 'does not increase complexity when filtering by iids' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + + expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 + expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 + end end diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb index ea7159eacf9..3140af27af5 100644 --- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb +++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb @@ -46,6 +46,14 @@ describe ResolvesPipelines do expect(resolve_pipelines({}, {})).to be_empty end + it 'increases field complexity based on arguments' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, null: false, max_page_size: 1) + + expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 2 + expect(field.to_graphql.complexity.call({}, { sha: 'foo' }, 1)).to eq 4 + expect(field.to_graphql.complexity.call({}, { sha: 'ref' }, 1)).to eq 4 + end + def resolve_pipelines(args = {}, context = { current_user: current_user }) resolve(resolver, obj: project, args: args, ctx: context) end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 399a33dae75..bffcdbfe915 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -120,6 +120,13 @@ describe Resolvers::IssuesResolver do end end + it 'increases field complexity based on arguments' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + + expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4 + expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8 + end + def resolve_issues(args = {}, context = { current_user: current_user }) resolve(described_class, obj: project, args: args, ctx: context) end diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb index d4990c6492c..4fdbb3aa43e 100644 --- a/spec/graphql/resolvers/project_resolver_spec.rb +++ b/spec/graphql/resolvers/project_resolver_spec.rb @@ -26,6 +26,14 @@ describe Resolvers::ProjectResolver do end end + it 'does not increase complexity depending on number of load limits' do + field1 = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + field2 = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1) + + expect(field1.to_graphql.complexity.call({}, {}, 1)).to eq 2 + expect(field2.to_graphql.complexity.call({}, {}, 1)).to eq 2 + end + def resolve_project(full_path) resolve(described_class, args: { full_path: full_path }) end diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb index b5697ee5245..4fe426e2447 100644 --- a/spec/graphql/types/base_field_spec.rb +++ b/spec/graphql/types/base_field_spec.rb @@ -4,6 +4,18 @@ require 'spec_helper' describe Types::BaseField do context 'when considering complexity' do + let(:resolver) do + Class.new(described_class) do + def self.resolver_complexity(args) + 2 if args[:foo] + end + + def self.complexity_multiplier(args) + 0.01 + end + end + end + it 'defaults to 1' do field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true) @@ -15,5 +27,19 @@ describe Types::BaseField do expect(field.to_graphql.complexity).to eq 12 end + + it 'sets complexity depending on arguments for resolvers' do + field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + + expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4 + expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3 + end + + it 'sets complexity depending on number load limits for resolvers' do + field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + + expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2 + expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 + end end end -- cgit v1.2.1 From 35f52b63644c2ae3644b9beaf4cafee67e879434 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Mon, 6 May 2019 22:03:37 +0000 Subject: Resolve "Support Prometheus for group-level clusters" --- spec/frontend/clusters/components/applications_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js index 7c54a27d950..8bcf02f0a34 100644 --- a/spec/frontend/clusters/components/applications_spec.js +++ b/spec/frontend/clusters/components/applications_spec.js @@ -75,7 +75,7 @@ describe('Applications', () => { }); it('renders a row for Prometheus', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeNull(); + expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull(); }); it('renders a row for GitLab Runner', () => { -- cgit v1.2.1 From 875338487343b092f670ab5e0323904ec5457a3a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 21 Mar 2019 22:24:48 +0000 Subject: Show disabled project repo mirrors Show disabled mirrors with a badge so that they can be deleted by project owners. --- .../projects/settings/repository_settings_spec.rb | 12 ++++++++++++ spec/models/remote_mirror_spec.rb | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index f7de769cee9..8c7bc192c50 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -236,5 +236,17 @@ describe 'Projects > Settings > Repository settings' do expect(mirrored_project.remote_mirrors.count).to eq(0) end end + + it 'shows a disabled mirror' do + create(:remote_mirror, project: project, enabled: false) + + visit project_settings_repository_path(project) + + mirror = find('.qa-mirrored-repository-row') + + expect(mirror).to have_selector('.qa-delete-mirror') + expect(mirror).to have_selector('.qa-disabled-mirror-badge') + expect(mirror).not_to have_selector('.qa-update-now-button') + end end end diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index f743dfed31f..e14b19db915 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -373,6 +373,22 @@ describe RemoteMirror, :mailer do end end + describe '#disabled?' do + subject { remote_mirror.disabled? } + + context 'when disabled' do + let(:remote_mirror) { build(:remote_mirror, enabled: false) } + + it { is_expected.to be_truthy } + end + + context 'when enabled' do + let(:remote_mirror) { build(:remote_mirror, enabled: true) } + + it { is_expected.to be_falsy } + end + end + def create_mirror(params) project = FactoryBot.create(:project, :repository) project.remote_mirrors.create!(params) -- cgit v1.2.1 From b7b852c250a95648d5954e0ead6c37b5ff8b35d9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 6 May 2019 14:42:21 -0700 Subject: Additional fix to handle NULL lock_version If the UI sends a string value for lock_version (e.g. "0"), then the previous monkey patch did not properly handle that properly. This commit casts the value to an integer to determine whether to look for NULL lock_versions. For merge requests, GitLab sends a POST request to `namespace/project/merge_requests/:iid` with the `merge_request[lock_version]` parameter with a string `0`. The string value comes from the form field, which explains why https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28145 wasn't sufficient. --- spec/models/issue_spec.rb | 21 ++++++++++++++++----- spec/models/merge_request_spec.rb | 21 ++++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 0ce4add5669..cc777cbf749 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -56,14 +56,25 @@ describe Issue do end describe 'locking' do - it 'works when an issue has a NULL lock_version' do - issue = create(:issue) + using RSpec::Parameterized::TableSyntax - described_class.where(id: issue.id).update_all('lock_version = NULL') + where(:lock_version) do + [ + [0], + ["0"] + ] + end - issue.update!(lock_version: 0, title: 'locking test') + with_them do + it 'works when an issue has a NULL lock_version' do + issue = create(:issue) - expect(issue.reload.title).to eq('locking test') + described_class.where(id: issue.id).update_all('lock_version = NULL') + + issue.update!(lock_version: lock_version, title: 'locking test') + + expect(issue.reload.title).to eq('locking test') + end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index ec2aef6f815..c72b6e9033d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -32,14 +32,25 @@ describe MergeRequest do end describe 'locking' do - it 'works when a merge request has a NULL lock_version' do - merge_request = create(:merge_request) + using RSpec::Parameterized::TableSyntax - described_class.where(id: merge_request.id).update_all('lock_version = NULL') + where(:lock_version) do + [ + [0], + ["0"] + ] + end - merge_request.update!(lock_version: 0, title: 'locking test') + with_them do + it 'works when a merge request has a NULL lock_version' do + merge_request = create(:merge_request) - expect(merge_request.reload.title).to eq('locking test') + described_class.where(id: merge_request.id).update_all('lock_version = NULL') + + merge_request.update!(lock_version: lock_version, title: 'locking test') + + expect(merge_request.reload.title).to eq('locking test') + end end end -- cgit v1.2.1 From d24d77a93a64e4698163c5ca579954f61ad9248a Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Mon, 6 May 2019 12:56:48 -0500 Subject: Resolve discussion when suggestion is applied - Adds color and a tooltip to describe this new behavior - Does not resolve if discussion is already resolved - Adds an action `resolveDiscussion` to simplify `toggleResolveNote` - Updates docs https://gitlab.com/gitlab-org/gitlab-ce/issues/54405 --- .../user_suggests_changes_on_diff_spec.rb | 22 ++-- .../markdown/suggestion_diff_header_spec.js | 103 ++++++++++++++++ spec/javascripts/notes/stores/actions_spec.js | 130 ++++++++++++++++++--- .../markdown/suggestion_diff_header_spec.js | 75 ------------ 4 files changed, 230 insertions(+), 100 deletions(-) create mode 100644 spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js delete mode 100644 spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js (limited to 'spec') diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index 1b5dd6945e0..04c7f4b6c76 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -121,7 +121,7 @@ describe 'User comments on a diff', :js do end context 'multi-line suggestions' do - it 'suggestion is presented' do + before do click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) page.within('.js-discussion-note-form') do @@ -130,7 +130,9 @@ describe 'User comments on a diff', :js do end wait_for_requests + end + it 'suggestion is presented' do page.within('.diff-discussions') do expect(page).to have_button('Apply suggestion') expect(page).to have_content('Suggested change') @@ -160,22 +162,24 @@ describe 'User comments on a diff', :js do end it 'suggestion is appliable' do - click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + page.within('.diff-discussions') do + expect(page).not_to have_content('Applied') - page.within('.js-discussion-note-form') do - fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```") - click_button('Comment') - end + click_button('Apply suggestion') + wait_for_requests - wait_for_requests + expect(page).to have_content('Applied') + end + end + it 'resolves discussion when applied' do page.within('.diff-discussions') do - expect(page).not_to have_content('Applied') + expect(page).not_to have_content('Unresolve discussion') click_button('Apply suggestion') wait_for_requests - expect(page).to have_content('Applied') + expect(page).to have_content('Unresolve discussion') end end end diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js new file mode 100644 index 00000000000..3b6f67457ad --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -0,0 +1,103 @@ +import { GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import SuggestionDiffHeader from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; + +const localVue = createLocalVue(); + +const DEFAULT_PROPS = { + canApply: true, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let wrapper; + + const createComponent = props => { + wrapper = shallowMount(localVue.extend(SuggestionDiffHeader), { + propsData: { + ...DEFAULT_PROPS, + ...props, + }, + localVue, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findApplyButton = () => wrapper.find('.qa-apply-btn'); + const findHeader = () => wrapper.find('.qa-suggestion-diff-header'); + const findHelpButton = () => wrapper.find('.js-help-btn'); + const findLoading = () => wrapper.find(GlLoadingIcon); + + it('renders a suggestion header', () => { + createComponent(); + + const header = findHeader(); + + expect(header.exists()).toBe(true); + expect(header.html().includes('Suggested change')).toBe(true); + }); + + it('renders a help button', () => { + createComponent(); + + expect(findHelpButton().exists()).toBe(true); + }); + + it('renders an apply button', () => { + createComponent(); + + const applyBtn = findApplyButton(); + + expect(applyBtn.exists()).toBe(true); + expect(applyBtn.html().includes('Apply suggestion')).toBe(true); + }); + + it('does not render an apply button if `canApply` is set to false', () => { + createComponent({ canApply: false }); + + expect(findApplyButton().exists()).toBe(false); + }); + + describe('when apply suggestion is clicked', () => { + beforeEach(done => { + createComponent(); + + findApplyButton().vm.$emit('click'); + + wrapper.vm.$nextTick(done); + }); + + it('emits apply', () => { + expect(wrapper.emittedByOrder()).toEqual([{ name: 'apply', args: [expect.any(Function)] }]); + }); + + it('hides apply button', () => { + expect(findApplyButton().exists()).toBe(false); + }); + + it('shows loading', () => { + expect(findLoading().exists()).toBe(true); + expect(wrapper.text()).toContain('Applying suggestion'); + }); + + it('when callback of apply is called, hides loading', done => { + const [callback] = wrapper.emitted().apply[0]; + + callback(); + + wrapper.vm + .$nextTick() + .then(() => { + expect(findApplyButton().exists()).toBe(true); + expect(findLoading().exists()).toBe(false); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 94ce6d8e222..39901276b8c 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -3,11 +3,12 @@ import $ from 'jquery'; import _ from 'underscore'; import { TEST_HOST } from 'spec/test_constants'; import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; -import * as actions from '~/notes/stores/actions'; +import actionsModule, * as actions from '~/notes/stores/actions'; import * as mutationTypes from '~/notes/stores/mutation_types'; import * as notesConstants from '~/notes/constants'; import createStore from '~/notes/stores'; import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub'; +import service from '~/notes/services/notes_service'; import testAction from '../../helpers/vuex_action_helper'; import { resetStore } from '../helpers'; import { @@ -18,11 +19,21 @@ import { individualNote, } from '../mock_data'; +const TEST_ERROR_MESSAGE = 'Test error message'; + describe('Actions Notes Store', () => { + let commit; + let dispatch; + let state; let store; + let flashSpy; beforeEach(() => { store = createStore(); + commit = jasmine.createSpy('commit'); + dispatch = jasmine.createSpy('dispatch'); + state = {}; + flashSpy = spyOnDependency(actionsModule, 'Flash'); }); afterEach(() => { @@ -604,21 +615,6 @@ describe('Actions Notes Store', () => { }); describe('updateOrCreateNotes', () => { - let commit; - let dispatch; - let state; - - beforeEach(() => { - commit = jasmine.createSpy('commit'); - dispatch = jasmine.createSpy('dispatch'); - state = {}; - }); - - afterEach(() => { - commit.calls.reset(); - dispatch.calls.reset(); - }); - it('Updates existing note', () => { const note = { id: 1234 }; const getters = { notesById: { 1234: note } }; @@ -751,4 +747,106 @@ describe('Actions Notes Store', () => { ); }); }); + + describe('resolveDiscussion', () => { + let getters; + let discussionId; + + beforeEach(() => { + discussionId = discussionMock.id; + state.discussions = [discussionMock]; + getters = { + isDiscussionResolved: () => false, + }; + }); + + it('when unresolved, dispatches action', done => { + testAction( + actions.resolveDiscussion, + { discussionId }, + { ...state, ...getters }, + [], + [ + { + type: 'toggleResolveNote', + payload: { + endpoint: discussionMock.resolve_path, + isResolved: false, + discussion: true, + }, + }, + ], + done, + ); + }); + + it('when resolved, does nothing', done => { + getters.isDiscussionResolved = id => id === discussionId; + + testAction( + actions.resolveDiscussion, + { discussionId }, + { ...state, ...getters }, + [], + [], + done, + ); + }); + }); + + describe('submitSuggestion', () => { + const discussionId = 'discussion-id'; + const noteId = 'note-id'; + const suggestionId = 'suggestion-id'; + let flashContainer; + + beforeEach(() => { + spyOn(service, 'applySuggestion'); + dispatch.and.returnValue(Promise.resolve()); + service.applySuggestion.and.returnValue(Promise.resolve()); + flashContainer = {}; + }); + + const testSubmitSuggestion = (done, expectFn) => { + actions + .submitSuggestion( + { commit, dispatch }, + { discussionId, noteId, suggestionId, flashContainer }, + ) + .then(expectFn) + .then(done) + .catch(done.fail); + }; + + it('when service success, commits and resolves discussion', done => { + testSubmitSuggestion(done, () => { + expect(commit.calls.allArgs()).toEqual([ + [mutationTypes.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }], + ]); + + expect(dispatch.calls.allArgs()).toEqual([['resolveDiscussion', { discussionId }]]); + expect(flashSpy).not.toHaveBeenCalled(); + }); + }); + + it('when service fails, flashes error message', done => { + const response = { response: { data: { message: TEST_ERROR_MESSAGE } } }; + + service.applySuggestion.and.returnValue(Promise.reject(response)); + + testSubmitSuggestion(done, () => { + expect(commit).not.toHaveBeenCalled(); + expect(dispatch).not.toHaveBeenCalled(); + expect(flashSpy).toHaveBeenCalledWith(`${TEST_ERROR_MESSAGE}.`, 'alert', flashContainer); + }); + }); + + it('when resolve discussion fails, fail gracefully', done => { + dispatch.and.returnValue(Promise.reject()); + + testSubmitSuggestion(done, () => { + expect(flashSpy).not.toHaveBeenCalled(); + }); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js deleted file mode 100644 index 12ee804f668..00000000000 --- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import Vue from 'vue'; -import SuggestionDiffHeaderComponent from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; - -const MOCK_DATA = { - canApply: true, - isApplied: false, - helpPagePath: 'path_to_docs', -}; - -describe('Suggestion Diff component', () => { - let vm; - - function createComponent(propsData) { - const Component = Vue.extend(SuggestionDiffHeaderComponent); - - return new Component({ - propsData, - }).$mount(); - } - - beforeEach(done => { - vm = createComponent(MOCK_DATA); - Vue.nextTick(done); - }); - - describe('init', () => { - it('renders a suggestion header', () => { - const header = vm.$el.querySelector('.qa-suggestion-diff-header'); - - expect(header).not.toBeNull(); - expect(header.innerHTML.includes('Suggested change')).toBe(true); - }); - - it('renders a help button', () => { - const helpBtn = vm.$el.querySelector('.js-help-btn'); - - expect(helpBtn).not.toBeNull(); - }); - - it('renders an apply button', () => { - const applyBtn = vm.$el.querySelector('.qa-apply-btn'); - - expect(applyBtn).not.toBeNull(); - expect(applyBtn.innerHTML.includes('Apply suggestion')).toBe(true); - }); - - it('does not render an apply button if `canApply` is set to false', () => { - const props = Object.assign(MOCK_DATA, { canApply: false }); - - vm = createComponent(props); - - expect(vm.$el.querySelector('.qa-apply-btn')).toBeNull(); - }); - }); - - describe('applySuggestion', () => { - it('emits when the apply button is clicked', () => { - const props = Object.assign(MOCK_DATA, { canApply: true }); - - vm = createComponent(props); - spyOn(vm, '$emit'); - vm.applySuggestion(); - - expect(vm.$emit).toHaveBeenCalled(); - }); - - it('does not emit when the canApply is set to false', () => { - spyOn(vm, '$emit'); - vm.canApply = false; - vm.applySuggestion(); - - expect(vm.$emit).not.toHaveBeenCalled(); - }); - }); -}); -- cgit v1.2.1 From 0edd1e6714385a87f0cacb25497204d89535d35f Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Tue, 30 Apr 2019 13:36:44 +0100 Subject: Write test that exposes bug with Fugit gem --- spec/lib/gitlab/ci/cron_parser_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 2a3f7807fdb..491e3fba9d9 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -174,6 +174,13 @@ describe Gitlab::Ci::CronParser do it { expect(subject).to be_nil } end + + context 'when cron is scheduled to a non existent day' do + let(:cron) { '0 12 31 2 *' } + let(:cron_timezone) { 'UTC' } + + it { expect(subject).to be_nil } + end end describe '#cron_valid?' do -- cgit v1.2.1 From 673ea5d2ac50e17a1839d0db83641e6851422f88 Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Thu, 2 May 2019 13:29:51 +0100 Subject: Rescue RuntimeError when "too many loops" occur With this workaround we temporarily prevent an exception from Fugit gem to be raised in Gitlab::Ci::CronParser --- spec/lib/gitlab/ci/cron_parser_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 491e3fba9d9..a228334d53e 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -181,6 +181,13 @@ describe Gitlab::Ci::CronParser do it { expect(subject).to be_nil } end + + context 'when cron is scheduled to a non existent day' do + let(:cron) { '0 12 31 2 *' } + let(:cron_timezone) { 'UTC' } + + it { expect(subject).to be_nil } + end end describe '#cron_valid?' do -- cgit v1.2.1 From c75b8ad4dadfa8f9676e0339e06408af3281f512 Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Tue, 7 May 2019 07:52:53 +0100 Subject: Remove workaround by upgrading Fugit gem Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/58241 * Upgrade Fugit gem to 1.2.1 which recognizes cron expressions for invalid days * Reverted previously implemented workaround * Leave test case which previously exposed the bug --- spec/lib/gitlab/ci/cron_parser_spec.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index a228334d53e..491e3fba9d9 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -181,13 +181,6 @@ describe Gitlab::Ci::CronParser do it { expect(subject).to be_nil } end - - context 'when cron is scheduled to a non existent day' do - let(:cron) { '0 12 31 2 *' } - let(:cron_timezone) { 'UTC' } - - it { expect(subject).to be_nil } - end end describe '#cron_valid?' do -- cgit v1.2.1 From 4d405acb6a2a7779a6f22772d3089ade90d34058 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 7 May 2019 10:06:50 +0200 Subject: Set created_at and updated_at for sorting specs This makes sure that the results are predictable and the same on both Postgres and MySQL --- spec/features/dashboard/user_filters_projects_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index e0553086fd7..5b17c49db2d 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe 'Dashboard > User filters projects' do let(:user) { create(:user) } - let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace) } + let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace, created_at: 2.seconds.ago, updated_at: 2.seconds.ago) } let(:user2) { create(:user) } - let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace) } + let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace, created_at: 1.second.ago, updated_at: 1.second.ago) } before do project.add_maintainer(user) @@ -129,7 +129,7 @@ describe 'Dashboard > User filters projects' do list = page.all('.projects-list .project-name').map(&:text) - expect(list).to match(["Private project", "Treasure", "Victorialand"]) + expect(list).to contain_exactly("Private project", "Treasure", "Victorialand") end it 'filters internal projects only' do @@ -139,14 +139,14 @@ describe 'Dashboard > User filters projects' do list = page.all('.projects-list .project-name').map(&:text) - expect(list).to match(['Internal project']) + expect(list).to contain_exactly('Internal project') end it 'filters any project' do select_dropdown_option '#filtered-search-visibility-dropdown', 'Any' list = page.all('.projects-list .project-name').map(&:text) - expect(list).to match(["Internal project", "Private project", "Treasure", "Victorialand"]) + expect(list).to contain_exactly("Internal project", "Private project", "Treasure", "Victorialand") end end -- cgit v1.2.1 From ec341a2bbd81eac9639faf5c5e8e8b670f535096 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 18 Apr 2019 05:10:21 -0700 Subject: Clean up CarrierWave's import/export files Unlike uploads that have been uploaded with Tempfile, the project import/export archives are stored in a temporary cache directory and remain there if: 1. Object storage is enabled 2. `move_to_store` is set to `true`. CarrierWave will leave these files there until disk space runs out or a clean step is run manually. If `move_to_store` is set to `false`, CarrierWave will remove the files after storing them. However, unlike a local file, with object storage, the file is still copied, so setting `move_to_store` to `true` doesn't buy us anything. To ensure files are cleaned up, we can just inherit from the GitlabUploader implementation of `move_to_store`, which returns `true` if it's a local file, `false` otherwise. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/60656 --- spec/uploaders/import_export_uploader_spec.rb | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'spec') diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb index 825c1cabc14..2dea48e3a88 100644 --- a/spec/uploaders/import_export_uploader_spec.rb +++ b/spec/uploaders/import_export_uploader_spec.rb @@ -3,9 +3,18 @@ require 'spec_helper' describe ImportExportUploader do let(:model) { build_stubbed(:import_export_upload) } let(:upload) { create(:upload, model: model) } + let(:import_export_upload) { ImportExportUpload.new } subject { described_class.new(model, :import_file) } + context 'local store' do + describe '#move_to_store' do + it 'returns true' do + expect(subject.move_to_store).to be true + end + end + end + context "object_store is REMOTE" do before do stub_uploads_object_storage @@ -16,5 +25,28 @@ describe ImportExportUploader do it_behaves_like 'builds correct paths', store_dir: %r[import_export_upload/import_file/], upload_path: %r[import_export_upload/import_file/] + + describe '#move_to_store' do + it 'returns false' do + expect(subject.move_to_store).to be false + end + end + + describe 'with an export file directly uploaded' do + let(:tempfile) { Tempfile.new(['test', '.gz']) } + + before do + stub_uploads_object_storage(described_class, direct_upload: true) + import_export_upload.export_file = tempfile + end + + it 'cleans up cached file' do + cache_dir = File.join(import_export_upload.export_file.cache_path(nil), '*') + + import_export_upload.save! + + expect(Dir[cache_dir]).to be_empty + end + end end end -- cgit v1.2.1 From 68e533dc219be27f3485d2335e70aa61a193dabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Tue, 7 May 2019 11:08:25 +0000 Subject: Add improvements to the global search process Removed the conditions added to Project.with_feature_available_for_user, and moved to the IssuableFinder. Now, we ensure that, in the projects retrieved in the Finder, the user has enough access for the feature. --- spec/factories/projects.rb | 1 + spec/finders/issues_finder_spec.rb | 8 +-- spec/finders/merge_requests_finder_spec.rb | 2 +- spec/models/project_spec.rb | 104 ++++++++++++++++++++--------- spec/requests/api/projects_spec.rb | 3 +- 5 files changed, 80 insertions(+), 38 deletions(-) (limited to 'spec') diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index ab185ab3972..743ec322885 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -260,6 +260,7 @@ FactoryBot.define do trait(:merge_requests_enabled) { merge_requests_access_level ProjectFeature::ENABLED } trait(:merge_requests_disabled) { merge_requests_access_level ProjectFeature::DISABLED } trait(:merge_requests_private) { merge_requests_access_level ProjectFeature::PRIVATE } + trait(:merge_requests_public) { merge_requests_access_level ProjectFeature::PUBLIC } trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED } trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED } trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE } diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 6a47cd013f8..89fdaceaa9f 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -641,9 +641,7 @@ describe IssuesFinder do end it 'filters by confidentiality' do - expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) - - subject + expect(subject.to_sql).to match("issues.confidential") end end @@ -660,9 +658,7 @@ describe IssuesFinder do end it 'filters by confidentiality' do - expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) - - subject + expect(subject.to_sql).to match("issues.confidential") end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 117f4a03735..da5e9dab058 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -31,7 +31,7 @@ describe MergeRequestsFinder do end context 'filtering by group' do - it 'includes all merge requests when user has access exceluding merge requests from projects the user does not have access to' do + it 'includes all merge requests when user has access excluding merge requests from projects the user does not have access to' do private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) } private_project.add_guest(user) create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index bb0257e7456..2a17bd6002e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3164,61 +3164,105 @@ describe Project do end describe '.with_feature_available_for_user' do - let!(:user) { create(:user) } - let!(:feature) { MergeRequest } - let!(:project) { create(:project, :public, :merge_requests_enabled) } + let(:user) { create(:user) } + let(:feature) { MergeRequest } subject { described_class.with_feature_available_for_user(feature, user) } - context 'when user has access to project' do - subject { described_class.with_feature_available_for_user(feature, user) } + shared_examples 'feature disabled' do + let(:project) { create(:project, :public, :merge_requests_disabled) } + + it 'does not return projects with the project feature disabled' do + is_expected.not_to include(project) + end + end + + shared_examples 'feature public' do + let(:project) { create(:project, :public, :merge_requests_public) } + + it 'returns projects with the project feature public' do + is_expected.to include(project) + end + end + + shared_examples 'feature enabled' do + let(:project) { create(:project, :public, :merge_requests_enabled) } + + it 'returns projects with the project feature enabled' do + is_expected.to include(project) + end + end + + shared_examples 'feature access level is nil' do + let(:project) { create(:project, :public) } + + it 'returns projects with the project feature access level nil' do + project.project_feature.update(merge_requests_access_level: nil) + + is_expected.to include(project) + end + end + context 'with user' do before do project.add_guest(user) end - context 'when public project' do - context 'when feature is public' do - it 'returns project' do - is_expected.to include(project) + it_behaves_like 'feature disabled' + it_behaves_like 'feature public' + it_behaves_like 'feature enabled' + it_behaves_like 'feature access level is nil' + + context 'when feature is private' do + let(:project) { create(:project, :public, :merge_requests_private) } + + context 'when user does not has access to the feature' do + it 'does not return projects with the project feature private' do + is_expected.not_to include(project) end end - context 'when feature is private' do - let!(:project) { create(:project, :public, :merge_requests_private) } - - it 'returns project when user has access to the feature' do - project.add_maintainer(user) + context 'when user has access to the feature' do + it 'returns projects with the project feature private' do + project.add_reporter(user) is_expected.to include(project) end - - it 'does not return project when user does not have the minimum access level required' do - is_expected.not_to include(project) - end end end + end - context 'when private project' do - let!(:project) { create(:project) } + context 'user is an admin' do + let(:user) { create(:user, :admin) } - it 'returns project when user has access to the feature' do - project.add_maintainer(user) + it_behaves_like 'feature disabled' + it_behaves_like 'feature public' + it_behaves_like 'feature enabled' + it_behaves_like 'feature access level is nil' - is_expected.to include(project) - end + context 'when feature is private' do + let(:project) { create(:project, :public, :merge_requests_private) } - it 'does not return project when user does not have the minimum access level required' do - is_expected.not_to include(project) + it 'returns projects with the project feature private' do + is_expected.to include(project) end end end - context 'when user does not have access to project' do - let!(:project) { create(:project) } + context 'without user' do + let(:user) { nil } - it 'does not return project when user cant access project' do - is_expected.not_to include(project) + it_behaves_like 'feature disabled' + it_behaves_like 'feature public' + it_behaves_like 'feature enabled' + it_behaves_like 'feature access level is nil' + + context 'when feature is private' do + let(:project) { create(:project, :public, :merge_requests_private) } + + it 'does not return projects with the project feature private' do + is_expected.not_to include(project) + end end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 577f61ae8d0..16d306f39cd 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -504,8 +504,9 @@ describe API::Projects do project4.add_reporter(user2) end - it 'returns an array of groups the user has at least developer access' do + it 'returns an array of projects the user has at least developer access' do get api('/projects', user2), params: { min_access_level: 30 } + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array -- cgit v1.2.1 From 58c09eb70941a0954fb5cd7b0c6316c698353edb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 7 May 2019 00:28:35 -0700 Subject: Use a path for the related merge requests endpoint Hosts using a non-standard configuration may have a different hostname/port/scheme than what may be configured on the GitLab server. While expose_url should generate a proper URL, there are cases where it may not work. Since we don't need the full URL, we can use the relative path. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/61280 --- .../api/helpers/related_resources_helpers_spec.rb | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'spec') diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb index 66af7f81535..99fe8795d91 100644 --- a/spec/lib/api/helpers/related_resources_helpers_spec.rb +++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb @@ -5,6 +5,40 @@ describe API::Helpers::RelatedResourcesHelpers do Class.new.include(described_class).new end + describe '#expose_path' do + let(:path) { '/api/v4/awesome_endpoint' } + + context 'empty relative URL root' do + before do + stub_config_setting(relative_url_root: '') + end + + it 'returns the existing path' do + expect(helpers.expose_path(path)).to eq(path) + end + end + + context 'slash relative URL root' do + before do + stub_config_setting(relative_url_root: '/') + end + + it 'returns the existing path' do + expect(helpers.expose_path(path)).to eq(path) + end + end + + context 'with relative URL root' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it 'returns the existing path' do + expect(helpers.expose_path(path)).to eq("/gitlab/root" + path) + end + end + end + describe '#expose_url' do let(:path) { '/api/v4/awesome_endpoint' } subject(:url) { helpers.expose_url(path) } -- cgit v1.2.1 From a4cf01f32e601e73ef7c946274092da7cfa8d008 Mon Sep 17 00:00:00 2001 From: Gosia Ksionek Date: Tue, 7 May 2019 13:51:34 +0000 Subject: Remove from providers with icon Remove puts Remove puts --- spec/features/oauth_login_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb index f4105730402..5ebfc32952d 100644 --- a/spec/features/oauth_login_spec.rb +++ b/spec/features/oauth_login_spec.rb @@ -14,7 +14,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do end providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2, - :facebook, :cas3, :auth0, :authentiq] + :facebook, :cas3, :auth0, :authentiq, :salesforce] before(:all) do # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost` -- cgit v1.2.1 From 31861b0f050ab4ba728c8d17bbe9d1970c4ce190 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 3 May 2019 12:12:58 +0100 Subject: Adds header column to variables list For the variables list in CI/CD settings this commits adds an header row with titles --- .../ci_variable_list/ajax_variable_list_spec.js | 2 +- .../features/variable_list_shared_examples.rb | 36 +++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 481b1a4d4b0..2839922fbd3 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -113,7 +113,7 @@ describe('AjaxFormVariableList', () => { it('hides secret values', done => { mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {}); - const row = container.querySelector('.js-row:first-child'); + const row = container.querySelector('.js-row'); const valueInput = row.querySelector('.js-ci-variable-input-value'); const valuePlaceholder = row.querySelector('.js-secret-value-placeholder'); diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 693b796fbdc..92a19dd22a2 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -17,7 +17,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') end @@ -38,7 +38,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -59,7 +59,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') @@ -116,19 +116,19 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section') do expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) - expect(page).to have_content('*' * 20) + expect(page).to have_content('*' * 17) click_button('Reveal value') expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-value').value).to eq(variable.value) - expect(page).not_to have_content('*' * 20) + expect(page).not_to have_content('*' * 17) click_button('Hide value') expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) - expect(page).to have_content('*' * 20) + expect(page).to have_content('*' * 17) end end @@ -149,7 +149,7 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section') do click_button('Reveal value') - page.within('.js-row:nth-child(1)') do + page.within('.js-row:nth-child(2)') do find('.js-ci-variable-input-key').set('new_key') find('.js-ci-variable-input-value').set('new_value') end @@ -159,7 +159,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-row:nth-child(1)') do + page.within('.js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('new_key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value') end @@ -181,7 +181,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do find('.ci-variable-protected-item .js-project-feature-toggle').click expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -193,7 +193,7 @@ shared_examples 'variable list' do visit page_path # We check the first row because it re-sorts to alphabetical order on refresh - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') @@ -215,7 +215,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do find('.ci-variable-protected-item .js-project-feature-toggle').click expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') @@ -226,7 +226,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('protected_key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') @@ -234,7 +234,7 @@ shared_examples 'variable list' do end it 'edits variable to be unmasked' do - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') find('.ci-variable-masked-item .js-project-feature-toggle').click @@ -247,13 +247,13 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') end end it 'edits variable to be masked' do - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') find('.ci-variable-masked-item .js-project-feature-toggle').click @@ -266,7 +266,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') find('.ci-variable-masked-item .js-project-feature-toggle').click @@ -279,7 +279,7 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') end end @@ -302,7 +302,7 @@ shared_examples 'variable list' do expect(page).to have_selector('.js-row', count: 4) # Remove the `akey` variable - page.within('.js-row:nth-child(2)') do + page.within('.js-row:nth-child(3)') do first('.js-row-remove-button').click end -- cgit v1.2.1 From 61cb27b01daa1ba7016d8dc4bf5f0e3472756bc5 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 17 Apr 2019 14:20:36 +0100 Subject: Fixes empty block in MR widget This commit prevents the pipeline block to be rendered when no pipeline information is provided --- .../merge_request/user_sees_merge_widget_spec.rb | 22 ++++++++++++++++++++++ .../components/mr_widget_pipeline_spec.js | 13 +++++++++++++ 2 files changed, 35 insertions(+) (limited to 'spec') 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 40ba676ff92..a32c6bdcf8f 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -670,4 +670,26 @@ describe 'Merge request > User sees merge widget', :js do end end end + + context 'when MR has pipeline but user does not have permission' do + let(:sha) { project.commit(merge_request.source_branch).sha } + let!(:pipeline) { create(:ci_pipeline_without_jobs, status: 'success', sha: sha, project: project, ref: merge_request.source_branch) } + + before do + project.update( + visibility_level: Gitlab::VisibilityLevel::PUBLIC, + public_builds: false + ) + merge_request.update!(head_pipeline: pipeline) + sign_out(:user) + + visit project_merge_request_path(project, merge_request) + end + + it 'renders a CI pipeline error' do + within '.ci-widget' do + expect(page).to have_content('Could not retrieve the pipeline status.') + end + end + end end 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 de213210cfc..8ac6e6a7b44 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 @@ -78,6 +78,19 @@ describe('MRWidgetPipeline', () => { ); }); + it('should render CI error when no pipeline is provided', () => { + vm = mountComponent(Component, { + pipeline: {}, + hasCi: true, + ciStatus: 'success', + troubleshootingDocsPath: 'help', + }); + + expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( + 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.', + ); + }); + describe('with a pipeline', () => { beforeEach(() => { vm = mountComponent(Component, { -- cgit v1.2.1 From 3565ee2ed66bd064ab6bef96ab43c0be43f75e49 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Tue, 7 May 2019 14:47:09 +0000 Subject: Add external dashboard link Add settings for linking to external dashboard from metrics dashboard --- .../components/external_dashboard_spec.js | 100 +++++++++++++++++++++ spec/javascripts/monitoring/dashboard_spec.js | 62 +++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 spec/frontend/operation_settings/components/external_dashboard_spec.js (limited to 'spec') diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js new file mode 100644 index 00000000000..de1dd219fe0 --- /dev/null +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -0,0 +1,100 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue'; +import { TEST_HOST } from 'helpers/test_constants'; + +describe('operation settings external dashboard component', () => { + let wrapper; + const externalDashboardPath = `http://mock-external-domain.com/external/dashboard/path`; + const externalDashboardHelpPagePath = `${TEST_HOST}/help/page/path`; + + beforeEach(() => { + wrapper = shallowMount(ExternalDashboard, { + propsData: { + externalDashboardPath, + externalDashboardHelpPagePath, + }, + }); + }); + + it('renders header text', () => { + expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard'); + }); + + describe('sub-header', () => { + let subHeader; + + beforeEach(() => { + subHeader = wrapper.find('.js-section-sub-header'); + }); + + it('renders descriptive text', () => { + expect(subHeader.text()).toContain( + 'Add a button to the metrics dashboard linking directly to your existing external dashboards.', + ); + }); + + it('renders help page link', () => { + const link = subHeader.find(GlLink); + + expect(link.text()).toBe('Learn more'); + expect(link.attributes().href).toBe(externalDashboardHelpPagePath); + }); + }); + + describe('form', () => { + let form; + + beforeEach(() => { + form = wrapper.find('form'); + }); + + describe('external dashboard url', () => { + describe('input label', () => { + let formGroup; + + beforeEach(() => { + formGroup = form.find(GlFormGroup); + }); + + it('uses label text', () => { + expect(formGroup.attributes().label).toBe('Full dashboard URL'); + }); + + it('uses description text', () => { + expect(formGroup.attributes().description).toBe( + 'Enter the URL of the dashboard you want to link to', + ); + }); + }); + + describe('input field', () => { + let input; + + beforeEach(() => { + input = form.find(GlFormInput); + }); + + it('defaults to externalDashboardPath prop', () => { + expect(input.attributes().value).toBe(externalDashboardPath); + }); + + it('uses a placeholder', () => { + expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards'); + }); + }); + + describe('submit button', () => { + let submit; + + beforeEach(() => { + submit = form.find(GlButton); + }); + + it('renders button label', () => { + expect(submit.text()).toBe('Save Changes'); + }); + }); + }); + }); +}); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 5c28840d3a4..fc722867b0b 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -37,6 +37,9 @@ describe('Dashboard', () => { window.gon = { ...window.gon, ee: false, + features: { + grafanaDashboardLink: true, + }, }; mock = new MockAdapter(axios); @@ -323,4 +326,63 @@ describe('Dashboard', () => { .catch(done.fail); }); }); + + describe('external dashboard link', () => { + let component; + + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + }); + + afterEach(() => { + component.$destroy(); + }); + + describe('with feature flag enabled', () => { + beforeEach(() => { + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + externalDashboardPath: '/mockPath', + }, + }); + }); + + it('shows the link', done => { + setTimeout(() => { + expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain( + 'View full dashboard', + ); + done(); + }); + }); + }); + + describe('without feature flage enabled', () => { + beforeEach(() => { + window.gon.features.grafanaDashboardLink = false; + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + externalDashboardPath: '', + }, + }); + }); + + it('does not show the link', done => { + setTimeout(() => { + expect(component.$el.querySelector('.js-external-dashboard-link')).toBe(null); + done(); + }); + }); + }); + }); }); -- cgit v1.2.1 From 29f9d92642539eaa04bdb485425d6f1cd3b459fd Mon Sep 17 00:00:00 2001 From: Kerri Miller Date: Tue, 7 May 2019 16:06:53 +0000 Subject: Confirm existence of head_pipeline if pipeline success required Pipelines are created by an async worker, so a rapid sequence of API calls can trigger a state where the pipeline, whose existence is part of determining if we wait for the pipeline to successfully complete before merging, can trigger the MR to be immediately merged instead. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/55127 --- spec/requests/api/merge_requests_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5c94a87529b..007f3517e64 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1495,6 +1495,33 @@ describe API::MergeRequests do expect(json_response['merge_when_pipeline_succeeds']).to eq(true) end + context 'when the MR requires pipeline success' do + it 'returns 405 if the pipeline is missing' do + allow_any_instance_of(MergeRequest) + .to receive(:merge_when_pipeline_succeeds).and_return(true) + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(nil) + + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user) + + expect(response).to have_gitlab_http_status(405) + expect(json_response['message']).to eq('Not allowed: pipeline does not exist') + end + end + + context 'when the request requires pipeline success' do + it 'returns 405 if the pipeline is missing' do + allow_any_instance_of(MergeRequest) + .to receive(:merge_when_pipeline_succeeds).and_return(true) + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(nil) + + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), + params: { merge_when_pipeline_succeeds: true } + + expect(response).to have_gitlab_http_status(405) + expect(json_response['message']).to eq('Not allowed: pipeline does not exist') + end + end + it "returns 404 for an invalid merge request IID" do put api("/projects/#{project.id}/merge_requests/12345/merge", user) -- cgit v1.2.1 From 78d3f94cee23318abdffc39ce3c604eafbd2ecd9 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Tue, 7 May 2019 17:46:33 +0000 Subject: Add custom metrics form to dashboard Use existing form to allow users to add custom metrics via the dashboard --- spec/javascripts/monitoring/dashboard_spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index fc722867b0b..e9bd6050d68 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -20,6 +20,9 @@ const propsData = { emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', environmentsEndpoint: '/root/hello-prometheus/environments/35', currentEnvironmentName: 'production', + customMetricsAvailable: false, + customMetricsPath: '', + validateQueryPath: '', }; export default propsData; @@ -163,7 +166,7 @@ describe('Dashboard', () => { }); }); - it('renders the environments dropdown with a single is-active element', done => { + it('renders the environments dropdown with a single active element', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { @@ -178,7 +181,7 @@ describe('Dashboard', () => { setTimeout(() => { const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item.is-active', + '.js-environments-dropdown .dropdown-item[active="true"]', ); expect(dropdownItems.length).toEqual(1); -- cgit v1.2.1 From 0625ccc652f97310b2444bb16ed8e20ee0642300 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Fri, 3 May 2019 23:11:55 +0200 Subject: Add failing test for saveNote error handling --- spec/javascripts/notes/stores/actions_spec.js | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 39901276b8c..7a9f32ddcff 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -794,6 +794,51 @@ describe('Actions Notes Store', () => { }); }); + describe('saveNote', () => { + const payload = { endpoint: TEST_HOST, data: { 'note[note]': 'some text' } }; + + describe('if response contains errors', () => { + const res = { errors: { something: ['went wrong'] } }; + + it('throws an error', done => { + actions + .saveNote( + { + commit() {}, + dispatch: () => Promise.resolve(res), + }, + payload, + ) + .then(() => done.fail('Expected error to be thrown!')) + .catch(error => { + expect(error.message).toBe('Failed to save comment!'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('if response contains no errors', () => { + const res = { valid: true }; + + it('returns the response', done => { + actions + .saveNote( + { + commit() {}, + dispatch: () => Promise.resolve(res), + }, + payload, + ) + .then(data => { + expect(data).toBe(res); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + describe('submitSuggestion', () => { const discussionId = 'discussion-id'; const noteId = 'note-id'; -- cgit v1.2.1 From 4ce36f3443a3385468fd6fde0ae9f27d54ea1d37 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 6 May 2019 22:04:59 +0200 Subject: Add failing test for hasQuickActions --- spec/frontend/notes/stores/utils_spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 spec/frontend/notes/stores/utils_spec.js (limited to 'spec') diff --git a/spec/frontend/notes/stores/utils_spec.js b/spec/frontend/notes/stores/utils_spec.js new file mode 100644 index 00000000000..b31b7491334 --- /dev/null +++ b/spec/frontend/notes/stores/utils_spec.js @@ -0,0 +1,17 @@ +import { hasQuickActions } from '~/notes/stores/utils'; + +describe('hasQuickActions', () => { + it.each` + input | expected + ${'some comment'} | ${false} + ${'/quickaction'} | ${true} + ${'some comment with\n/quickaction'} | ${true} + `('returns $expected for $input', ({ input, expected }) => { + expect(hasQuickActions(input)).toBe(expected); + }); + + it('is stateless', () => { + expect(hasQuickActions('some comment')).toBe(hasQuickActions('some comment')); + expect(hasQuickActions('/quickaction')).toBe(hasQuickActions('/quickaction')); + }); +}); -- cgit v1.2.1 From 3ad6653b3e64ad81278cbbc24dacd15bd2d32e6f Mon Sep 17 00:00:00 2001 From: Ezekiel Kigbo Date: Tue, 7 May 2019 21:58:16 +0000 Subject: Added user time settings fields to profile Udpated user_edit_profile_spec with time preferences Minor update form fields --- .../profiles/user_edit_preferences_spec.rb | 32 +++++++++ spec/features/profiles/user_edit_profile_spec.rb | 32 +++++++++ .../shared/components/timezone_dropdown_spec.js | 77 ++++++++++++++++++++++ spec/models/user_preference_spec.rb | 6 ++ spec/services/users/update_service_spec.rb | 9 +++ 5 files changed, 156 insertions(+) create mode 100644 spec/features/profiles/user_edit_preferences_spec.rb (limited to 'spec') diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb new file mode 100644 index 00000000000..2d2da222998 --- /dev/null +++ b/spec/features/profiles/user_edit_preferences_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'User edit preferences profile' do + let(:user) { create(:user) } + + before do + stub_feature_flags(user_time_settings: true) + sign_in(user) + visit(profile_preferences_path) + end + + it 'allows the user to toggle their time format preference' do + field = page.find_field("user[time_format_in_24h]") + + expect(field).not_to be_checked + + field.click + + expect(field).to be_checked + end + + it 'allows the user to toggle their time display preference' do + field = page.find_field("user[time_display_relative]") + + expect(field).to be_checked + + field.click + + expect(field).not_to be_checked + end +end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index b43711f6ef6..a53da94ef7d 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -327,5 +327,37 @@ describe 'User edit profile' do end end end + + context 'User time preferences', :js do + let(:issue) { create(:issue, project: project)} + let(:project) { create(:project) } + + before do + stub_feature_flags(user_time_settings: true) + end + + it 'shows the user time preferences form' do + expect(page).to have_content('Time settings') + end + + it 'allows the user to select a time zone from a dropdown list of options' do + expect(page.find('.user-time-preferences .dropdown')).not_to have_css('.show') + + page.find('.user-time-preferences .js-timezone-dropdown').click + + expect(page.find('.user-time-preferences .dropdown')).to have_css('.show') + + page.find("a", text: "Nuku'alofa").click + + tz = page.find('.user-time-preferences #user_timezone', visible: false) + + expect(tz.value).to eq('Pacific/Tongatapu') + end + + it 'timezone defaults to servers default' do + timezone_name = Time.zone.tzinfo.name + expect(page.find('.user-time-preferences #user_timezone', visible: false).value).to eq(timezone_name) + end + end end end diff --git a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js index a89952ee435..5f4dba5ecb9 100644 --- a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js +++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js @@ -3,6 +3,7 @@ import GLDropdown from '~/gl_dropdown'; // eslint-disable-line no-unused-vars import TimezoneDropdown, { formatUtcOffset, formatTimezone, + findTimezoneByIdentifier, } from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown'; describe('Timezone Dropdown', function() { @@ -12,6 +13,7 @@ describe('Timezone Dropdown', function() { let $dropdownEl = null; let $wrapper = null; const tzListSel = '.dropdown-content ul li a.is-active'; + const tzDropdownToggleText = '.dropdown-toggle-text'; describe('Initialize', () => { describe('with dropdown already loaded', () => { @@ -94,6 +96,36 @@ describe('Timezone Dropdown', function() { expect(onSelectTimezone).toHaveBeenCalled(); }); + + it('will correctly set the dropdown label if a timezone identifier is set on the inputEl', () => { + $inputEl.val('America/St_Johns'); + + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + displayFormat: selectedItem => formatTimezone(selectedItem), + }); + + expect($wrapper.find(tzDropdownToggleText).html()).toEqual('[UTC - 2.5] Newfoundland'); + }); + + it('will call a provided `displayFormat` handler to format the dropdown value', () => { + const displayFormat = jasmine.createSpy('displayFormat'); + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + displayFormat, + }); + + $wrapper + .find(tzListSel) + .first() + .trigger('click'); + + expect(displayFormat).toHaveBeenCalled(); + }); }); }); @@ -164,4 +196,49 @@ describe('Timezone Dropdown', function() { ).toEqual('[UTC 0] Accra'); }); }); + + describe('findTimezoneByIdentifier', () => { + const tzList = [ + { + identifier: 'Asia/Tokyo', + name: 'Sapporo', + offset: 32400, + }, + { + identifier: 'Asia/Hong_Kong', + name: 'Hong Kong', + offset: 28800, + }, + { + identifier: 'Asia/Dhaka', + name: 'Dhaka', + offset: 21600, + }, + ]; + + const identifier = 'Asia/Dhaka'; + it('returns the correct object if the identifier exists', () => { + const res = findTimezoneByIdentifier(tzList, identifier); + + expect(res).toBeTruthy(); + expect(res).toBe(tzList[2]); + }); + + it('returns null if it doesnt find the identifier', () => { + const res = findTimezoneByIdentifier(tzList, 'Australia/Melbourne'); + + expect(res).toBeNull(); + }); + + it('returns null if there is no identifier given', () => { + expect(findTimezoneByIdentifier(tzList)).toBeNull(); + expect(findTimezoneByIdentifier(tzList, '')).toBeNull(); + }); + + it('returns null if there is an empty or invalid array given', () => { + expect(findTimezoneByIdentifier([], identifier)).toBeNull(); + expect(findTimezoneByIdentifier(null, identifier)).toBeNull(); + expect(findTimezoneByIdentifier(undefined, identifier)).toBeNull(); + }); + }); }); diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb index b2ef17a81d4..e09c91e874a 100644 --- a/spec/models/user_preference_spec.rb +++ b/spec/models/user_preference_spec.rb @@ -73,4 +73,10 @@ describe UserPreference do it_behaves_like 'a sort_by preference' end end + + describe '#timezone' do + it 'returns server time as default' do + expect(user_preference.timezone).to eq(Time.zone.tzinfo.name) + end + end end diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index e04c05418b0..9384287f98a 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -13,6 +13,15 @@ describe Users::UpdateService do expect(user.name).to eq('New Name') end + it 'updates time preferences' do + result = update_user(user, timezone: 'Europe/Warsaw', time_display_relative: true, time_format_in_24h: false) + + expect(result).to eq(status: :success) + expect(user.reload.timezone).to eq('Europe/Warsaw') + expect(user.time_display_relative).to eq(true) + expect(user.time_format_in_24h).to eq(false) + end + it 'returns an error result when record cannot be updated' do result = {} expect do -- cgit v1.2.1 From 17073c68660f0fa00e911f1b11bd9f457e5eab08 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Wed, 8 May 2019 06:22:40 -0400 Subject: Remove jira_spec from quarantine --- spec/features/issuables/markdown_references/jira_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb index cc04798248c..8eaccfc0949 100644 --- a/spec/features/issuables/markdown_references/jira_spec.rb +++ b/spec/features/issuables/markdown_references/jira_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -describe "Jira", :js, :quarantine do +describe "Jira", :js do let(:user) { create(:user) } let(:actual_project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project) } -- cgit v1.2.1 From b145d9b8734961d391dbb3a96612c71b0684d22a Mon Sep 17 00:00:00 2001 From: Patrick Derichs Date: Wed, 8 May 2019 12:39:54 +0000 Subject: Refactor spec to reload existing_label right after creation This will avoid timestamp comparison issues later within equality check with attributes --- spec/lib/gitlab/bitbucket_import/importer_spec.rb | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index e1a2bae5fe8..a02c00e3340 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -222,6 +222,46 @@ describe Gitlab::BitbucketImport::Importer do body: {}.to_json) end + context 'creating labels on project' do + before do + allow(importer).to receive(:import_wiki) + end + + it 'creates labels as expected' do + expect { importer.execute }.to change { Label.count }.from(0).to(Gitlab::BitbucketImport::Importer::LABELS.size) + end + + it 'does not fail if label is already existing' do + label = Gitlab::BitbucketImport::Importer::LABELS.first + ::Labels::CreateService.new(label).execute(project: project) + + expect { importer.execute }.not_to raise_error + end + + it 'does not create new labels' do + Gitlab::BitbucketImport::Importer::LABELS.each do |label| + create(:label, project: project, title: label[:title]) + end + + expect { importer.execute }.not_to change { Label.count } + end + + it 'does not update existing ones' do + label_title = Gitlab::BitbucketImport::Importer::LABELS.first[:title] + existing_label = create(:label, project: project, title: label_title) + # Reload label from database so we avoid timestamp comparison issues related to time precision when comparing + # attributes later. + existing_label.reload + + Timecop.freeze(Time.now + 1.minute) do + importer.execute + + label_after_import = project.labels.find(existing_label.id) + expect(label_after_import.attributes).to eq(existing_label.attributes) + end + end + end + it 'maps statuses to open or closed' do allow(importer).to receive(:import_wiki) -- cgit v1.2.1 From ad7d62e1ac9ee06edfdd8eac7e0ab3fac868f336 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Wed, 8 May 2019 09:36:38 -0500 Subject: Use process timer for jest timeout testing --- spec/frontend/helpers/timeout.js | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js index 318593a48a4..b30b7f1ce1e 100644 --- a/spec/frontend/helpers/timeout.js +++ b/spec/frontend/helpers/timeout.js @@ -1,24 +1,31 @@ -let testTimeoutInMs; +const NS_PER_SEC = 1e9; +const NS_PER_MS = 1e6; -export const setTestTimeout = newTimeoutInMs => { - testTimeoutInMs = newTimeoutInMs; - jest.setTimeout(newTimeoutInMs); +let testTimeoutNS; + +export const setTestTimeout = newTimeoutMS => { + testTimeoutNS = newTimeoutMS * NS_PER_MS; + jest.setTimeout(newTimeoutMS); }; -export const initializeTestTimeout = defaultTimeoutInMs => { - setTestTimeout(defaultTimeoutInMs); +export const initializeTestTimeout = defaultTimeoutMS => { + setTestTimeout(defaultTimeoutMS); let testStartTime; // https://github.com/facebook/jest/issues/6947 beforeEach(() => { - testStartTime = Date.now(); + testStartTime = process.hrtime(); }); afterEach(() => { - const elapsedTimeInMs = Date.now() - testStartTime; - if (elapsedTimeInMs > testTimeoutInMs) { - throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`); + const [seconds, remainingNs] = process.hrtime(testStartTime); + const elapsedNS = seconds * NS_PER_SEC + remainingNs; + + if (elapsedNS > testTimeoutNS) { + throw new Error( + `Test took too long (${elapsedNS / NS_PER_MS}ms > ${testTimeoutNS / NS_PER_MS}ms)!`, + ); } }); }; -- cgit v1.2.1 From 85cf320258a5c5de855d5238843c1593d8b70826 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Wed, 8 May 2019 11:20:51 -0500 Subject: Fix discussion notes spec timeout --- spec/frontend/notes/components/discussion_notes_spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js index 392c1b6533e..7e037beca9d 100644 --- a/spec/frontend/notes/components/discussion_notes_spec.js +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import '~/behaviors/markdown/render_gfm'; import { SYSTEM_NOTE } from '~/notes/constants'; import DiscussionNotes from '~/notes/components/discussion_notes.vue'; @@ -8,6 +8,7 @@ import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_sys import SystemNote from '~/vue_shared/components/notes/system_note.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import createStore from '~/notes/stores'; +import { setTestTimeout } from 'helpers/timeout'; import { noteableDataMock, discussionMock, @@ -17,6 +18,8 @@ import { const localVue = createLocalVue(); describe('DiscussionNotes', () => { + setTestTimeout(500); + let wrapper; const createComponent = props => { @@ -24,7 +27,7 @@ describe('DiscussionNotes', () => { store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); - wrapper = mount(DiscussionNotes, { + wrapper = shallowMount(DiscussionNotes, { localVue, store, propsData: { -- cgit v1.2.1 From b231329bb9f1402e5971f9134cdc27242cbb3ea7 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 9 May 2019 13:45:37 +0200 Subject: Mock away document.createRange() (cherry picked from commit a0e7e2075c6182838a0645e0bbb3927e5f9a8bba) --- spec/frontend/environment.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index 9612162ad0c..a8c8688441d 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -38,6 +38,16 @@ class CustomEnvironment extends JSDOMEnvironment { this.global.fixturesBasePath = `${process.cwd()}/${ IS_EE ? 'ee/' : '' }spec/javascripts/fixtures`; + + // Not yet supported by JSDOM: https://github.com/jsdom/jsdom/issues/317 + this.global.document.createRange = () => ({ + setStart: () => {}, + setEnd: () => {}, + commonAncestorContainer: { + nodeName: 'BODY', + ownerDocument: this.global.document, + }, + }); } async teardown() { -- cgit v1.2.1 From 444959bfa0b79e827a2a1a7a314acac19390f976 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Thu, 9 May 2019 13:45:25 +0000 Subject: Generate Let's Encrypt private key --- spec/initializers/secret_token_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'spec') diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb index 726ce07a2d1..77bc28a6b07 100644 --- a/spec/initializers/secret_token_spec.rb +++ b/spec/initializers/secret_token_spec.rb @@ -45,11 +45,21 @@ describe 'create_tokens' do expect(keys).to all(match(RSA_KEY)) end + it "generates private key for Let's Encrypt" do + create_tokens + + keys = secrets.values_at(:lets_encrypt_private_key) + + expect(keys.uniq).to eq(keys) + expect(keys).to all(match(RSA_KEY)) + end + it 'warns about the secrets to add to secrets.yml' do expect(self).to receive(:warn_missing_secret).with('secret_key_base') expect(self).to receive(:warn_missing_secret).with('otp_key_base') expect(self).to receive(:warn_missing_secret).with('db_key_base') expect(self).to receive(:warn_missing_secret).with('openid_connect_signing_key') + expect(self).to receive(:warn_missing_secret).with('lets_encrypt_private_key') create_tokens end @@ -78,6 +88,7 @@ describe 'create_tokens' do before do secrets.db_key_base = 'db_key_base' secrets.openid_connect_signing_key = 'openid_connect_signing_key' + secrets.lets_encrypt_private_key = 'lets_encrypt_private_key' allow(File).to receive(:exist?).with('.secret').and_return(true) allow(File).to receive(:read).with('.secret').and_return('file_key') -- cgit v1.2.1 From 8bd6d0cf8b7e0fa010a57a3c7862318ec5b884f9 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 9 May 2019 15:06:54 +0100 Subject: Whitelist Gitaly N+1 for forking in tests This helper is right on the limit of the number of calls being made. When the request store is enabled, and Sidekiq is running in inline mode, just a couple of additional actions result in it being pushed over the line. The operation is entirely artificial, since in reality we'd perform these requests in multiple separate processes, so just whitelist it for now. --- spec/support/helpers/project_forks_helper.rb | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb index 9a86560da2a..bcb11a09b36 100644 --- a/spec/support/helpers/project_forks_helper.rb +++ b/spec/support/helpers/project_forks_helper.rb @@ -1,5 +1,11 @@ module ProjectForksHelper def fork_project(project, user = nil, params = {}) + Gitlab::GitalyClient.allow_n_plus_1_calls do + fork_project_direct(project, user, params) + end + end + + def fork_project_direct(project, user = nil, params = {}) # Load the `fork_network` for the project to fork as there might be one that # wasn't loaded yet. project.reload unless project.fork_network @@ -44,11 +50,16 @@ module ProjectForksHelper end def fork_project_with_submodules(project, user = nil, params = {}) - forked_project = fork_project(project, user, params) - TestEnv.copy_repo(forked_project, - bare_repo: TestEnv.forked_repo_path_bare, - refs: TestEnv::FORKED_BRANCH_SHA) - forked_project.repository.after_import - forked_project + Gitlab::GitalyClient.allow_n_plus_1_calls do + forked_project = fork_project_direct(project, user, params) + TestEnv.copy_repo( + forked_project, + bare_repo: TestEnv.forked_repo_path_bare, + refs: TestEnv::FORKED_BRANCH_SHA + ) + forked_project.repository.after_import + + forked_project + end end end -- cgit v1.2.1 From d030a54e507583a3a1bbf74f96e951e5d03be301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 9 May 2019 16:10:52 +0200 Subject: Fix a flaky spec for sorting projects in dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../dashboard/user_filters_projects_spec.rb | 56 +++++----------------- 1 file changed, 12 insertions(+), 44 deletions(-) (limited to 'spec') diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index 5b17c49db2d..4410c8f887f 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -151,34 +151,22 @@ describe 'Dashboard > User filters projects' do end describe 'Sorting' do - before do - [ - { name: 'Red ribbon army', created_at: 2.days.ago }, - { name: 'Cell saga', created_at: Time.now }, - { name: 'Frieza saga', created_at: 10.days.ago } - ].each do |item| - project = create(:project, name: item[:name], namespace: user.namespace, created_at: item[:created_at]) - project.add_developer(user) - end + let(:desc_sorted_project_names) { %w[Treasure Victorialand] } + before do user.toggle_star(project) user.toggle_star(project2) user2.toggle_star(project2) end - it 'includes sorting direction' do + it 'has all sorting options', :js do sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') expect(sorting_dropdown).to have_css '.reverse-sort-btn' - end - - it 'has all sorting options', :js do - sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') - sorting_option_labels = ['Last updated', 'Created date', 'Name', 'Stars'] sorting_dropdown.click - sorting_option_labels.each do |label| + ['Last updated', 'Created date', 'Name', 'Stars'].each do |label| expect(sorting_dropdown).to have_content(label) end end @@ -194,16 +182,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' - desc = ['Victorialand', 'Treasure', 'Red ribbon army', 'Frieza saga', 'Cell saga'] - asc = ['Cell saga', 'Frieza saga', 'Red ribbon army', 'Treasure', 'Victorialand'] - - click_sort_direction - - expect_to_see_projects(desc) + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end @@ -211,16 +194,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' - desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] - asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(desc) - - click_sort_direction - - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end @@ -228,16 +206,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' - desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] - asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] - - click_sort_direction - - expect_to_see_projects(desc) + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end @@ -245,16 +218,11 @@ describe 'Dashboard > User filters projects' do it 'sorts the project list' do select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' - desc = ["Red ribbon army", "Cell saga", "Frieza saga", "Victorialand", "Treasure"] - asc = ["Treasure", "Victorialand", "Red ribbon army", "Cell saga", "Frieza saga"] - - click_sort_direction - - expect_to_see_projects(desc) + expect_to_see_projects(desc_sorted_project_names) click_sort_direction - expect_to_see_projects(asc) + expect_to_see_projects(desc_sorted_project_names.reverse) end end end -- cgit v1.2.1 From 091b15b7421374df4e06bfdb91bef6a0c36072cf Mon Sep 17 00:00:00 2001 From: Diego Silva Date: Sat, 4 May 2019 10:11:06 +0100 Subject: Change DetectRepositoryLanguagesWorker to not receive user Fixes #60425 --- spec/controllers/projects/graphs_controller_spec.rb | 15 +++++++++++++++ .../projects/detect_repository_languages_service_spec.rb | 2 +- .../projects/repository_languages_service_spec.rb | 4 ++-- spec/workers/detect_repository_languages_worker_spec.rb | 11 ++--------- 4 files changed, 20 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index d390e84c9b0..b5248c7f0c8 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -28,6 +28,21 @@ describe Projects::GraphsController do end describe 'charts' do + context 'with an anonymous user' do + let(:project) { create(:project, :repository, :public) } + + before do + sign_out(user) + end + + it 'renders charts with 200 status code' do + get(:charts, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' }) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:charts) + end + end + context 'when languages were previously detected' do let(:project) { create(:project, :repository, detected_repository_languages: true) } let!(:repository_language) { create(:repository_language, project: project) } diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb index e3e561c971c..df5eed18ac0 100644 --- a/spec/services/projects/detect_repository_languages_service_spec.rb +++ b/spec/services/projects/detect_repository_languages_service_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do set(:project) { create(:project, :repository) } - subject { described_class.new(project, project.owner) } + subject { described_class.new(project) } describe '#execute' do context 'without previous detection' do diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb index 09c61363ad2..46c5095327d 100644 --- a/spec/services/projects/repository_languages_service_spec.rb +++ b/spec/services/projects/repository_languages_service_spec.rb @@ -10,7 +10,7 @@ describe Projects::RepositoryLanguagesService do context 'when a project is without detected programming languages' do it 'schedules a worker and returns the empty result' do - expect(::DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id, project.owner.id) + expect(::DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id) expect(service.execute).to eq([]) end end @@ -19,7 +19,7 @@ describe Projects::RepositoryLanguagesService do let!(:repository_language) { create(:repository_language, project: project) } it 'does not schedule a worker and returns the detected languages' do - expect(::DetectRepositoryLanguagesWorker).not_to receive(:perform_async).with(project.id, project.owner.id) + expect(::DetectRepositoryLanguagesWorker).not_to receive(:perform_async).with(project.id) languages = service.execute diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb index dbf32555985..755eb8dbf6b 100644 --- a/spec/workers/detect_repository_languages_worker_spec.rb +++ b/spec/workers/detect_repository_languages_worker_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' describe DetectRepositoryLanguagesWorker do set(:project) { create(:project) } - let(:user) { project.owner } subject { described_class.new } @@ -14,19 +13,13 @@ describe DetectRepositoryLanguagesWorker do allow(::Projects::DetectRepositoryLanguagesService).to receive(:new).and_return(service) expect(service).to receive(:execute) - subject.perform(project.id, user.id) + subject.perform(project.id) end context 'when invalid ids are used' do it 'does not raise when the project could not be found' do expect do - subject.perform(-1, user.id) - end.not_to raise_error - end - - it 'does not raise when the user could not be found' do - expect do - subject.perform(project.id, -1) + subject.perform(-1) end.not_to raise_error end end -- cgit v1.2.1 From 131f66c91a32f278ce15c59505fdca1a7b35edbd Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 9 May 2019 16:32:18 +0000 Subject: Update usage_data_spec to match EE --- spec/lib/gitlab/usage_data_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 4e910e67ac2..aa975c8bb0b 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -35,7 +35,7 @@ describe Gitlab::UsageData do subject { described_class.data } it "gathers usage data" do - expect(subject.keys).to match_array(%i( + expect(subject.keys).to include(*%i( active_user_count counts recorded_at @@ -68,7 +68,7 @@ describe Gitlab::UsageData do expect(count_data[:boards]).to eq(1) expect(count_data[:projects]).to eq(3) - expect(count_data.keys).to match_array(%i( + expect(count_data.keys).to include(*%i( assignee_lists boards ci_builds -- cgit v1.2.1 From c7d57f2719027388c88e5a87a04003e99db54ec0 Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Thu, 9 May 2019 15:57:53 -0400 Subject: Allow hiding of report status icon in report item Allow hiding of all report items' status icons --- .../reports/components/report_item_spec.js | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 spec/frontend/reports/components/report_item_spec.js (limited to 'spec') diff --git a/spec/frontend/reports/components/report_item_spec.js b/spec/frontend/reports/components/report_item_spec.js new file mode 100644 index 00000000000..bacbb399513 --- /dev/null +++ b/spec/frontend/reports/components/report_item_spec.js @@ -0,0 +1,33 @@ +import { shallowMount } from '@vue/test-utils'; +import { STATUS_SUCCESS } from '~/reports/constants'; +import ReportItem from '~/reports/components/report_item.vue'; +import { componentNames } from '~/reports/components/issue_body'; + +describe('ReportItem', () => { + describe('showReportSectionStatusIcon', () => { + it('does not render CI Status Icon when showReportSectionStatusIcon is false', () => { + const wrapper = shallowMount(ReportItem, { + propsData: { + issue: { foo: 'bar' }, + component: componentNames.TestIssueBody, + status: STATUS_SUCCESS, + showReportSectionStatusIcon: false, + }, + }); + + expect(wrapper.find('issuestatusicon-stub').exists()).toBe(false); + }); + + it('shows status icon when unspecified', () => { + const wrapper = shallowMount(ReportItem, { + propsData: { + issue: { foo: 'bar' }, + component: componentNames.TestIssueBody, + status: STATUS_SUCCESS, + }, + }); + + expect(wrapper.find('issuestatusicon-stub').exists()).toBe(true); + }); + }); +}); -- cgit v1.2.1 From e67481e079023e1319e91fabfb90b88c9a2b2655 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 9 May 2019 07:53:25 -0500 Subject: Properly handle LFS Batch API response in project import Project imports were failing with `undefined method each_with_object for String` because the import was attempting to parse the LFS Batch API and failing due to the fact that the Content-Type wasn't a supported format (e.g. application/vnd.git-lfs+json instead of application/json). We now parse the body as JSON. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/61624 --- .../lfs_download_link_list_service_spec.rb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb index d8427d0bf78..80debcd3a7a 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb @@ -33,7 +33,10 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do before do allow(project).to receive(:lfs_enabled?).and_return(true) - allow(Gitlab::HTTP).to receive(:post).and_return(objects_response) + response = instance_double(HTTParty::Response) + allow(response).to receive(:body).and_return(objects_response.to_json) + allow(response).to receive(:success?).and_return(true) + allow(Gitlab::HTTP).to receive(:post).and_return(response) end describe '#execute' do @@ -89,6 +92,21 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError) end + + shared_examples 'JSON parse errors' do |body| + it 'raises error' do + response = instance_double(HTTParty::Response) + allow(response).to receive(:body).and_return(body) + allow(response).to receive(:success?).and_return(true) + allow(Gitlab::HTTP).to receive(:post).and_return(response) + + expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError) + end + end + + it_behaves_like 'JSON parse errors', '{' + it_behaves_like 'JSON parse errors', '{}' + it_behaves_like 'JSON parse errors', '{ foo: 123 }' end describe '#parse_response_links' do -- cgit v1.2.1 From f16406167e107aa23489469daee98bc842675e06 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 7 May 2019 22:50:43 -0700 Subject: Fix Error 500 when inviting user already present A project admin attempting to invite an already-invited user will see a confusing Error 500 message. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/61574 --- spec/services/members/create_service_spec.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index 2c4fb131ed9..674fe0f666e 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -44,7 +44,18 @@ describe Members::CreateService do result = described_class.new(user, params).execute(project) expect(result[:status]).to eq(:error) - expect(result[:message]).to include(project_user.username) + expect(result[:message]).to include("#{project_user.username}: Access level is not included in the list") expect(project.users).not_to include project_user end + + it 'does not add a member with an existing invite' do + invited_member = create(:project_member, :invited, project: project) + + params = { user_ids: invited_member.invite_email, + access_level: Gitlab::Access::GUEST } + result = described_class.new(user, params).execute(project) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Invite email has already been taken') + end end -- cgit v1.2.1 From 36b1a2d7d08e1615f13fe839cc8251ba76ea87c6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 10 May 2019 06:53:43 -0500 Subject: Don't run full gc in AfterImportService Pull mirrors would run the `Projects::AfterImportService`, which would force a `git gc` each time it finished. This is overkill and not necessary now that we have refs packed more frequently (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27826). Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/11556 --- spec/services/projects/after_import_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb index 95c11f71c5e..51d3fd18881 100644 --- a/spec/services/projects/after_import_service_spec.rb +++ b/spec/services/projects/after_import_service_spec.rb @@ -15,7 +15,7 @@ describe Projects::AfterImportService do describe '#execute' do before do allow(Projects::HousekeepingService) - .to receive(:new).with(project, :gc).and_return(housekeeping_service) + .to receive(:new).with(project).and_return(housekeeping_service) allow(housekeeping_service) .to receive(:execute).and_yield -- cgit v1.2.1 From d14e25e955f2ca2f8dce569288431e897f1434d1 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sun, 21 Apr 2019 12:00:13 +0200 Subject: Call store_frontend_fixture() in JavaScriptFixturesHelpers --- spec/support/helpers/javascript_fixtures_helpers.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 494398dc4de..cdd7724cc13 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -11,6 +11,10 @@ module JavaScriptFixturesHelpers base.around do |example| # pick an arbitrary date from the past, so tests are not time dependent Timecop.freeze(Time.utc(2015, 7, 3, 10)) { example.run } + + raise NoMethodError.new('You need to set `response` for the fixture generator! This will automatically happen with `type: :controller` or `type: :request`.', 'response') unless respond_to?(:response) + + store_frontend_fixture(response, example.description) end end @@ -29,7 +33,13 @@ module JavaScriptFixturesHelpers end end - # Public: Store a response object as fixture file + def remove_repository(project) + Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path) + end + + private + + # Private: Store a response object as fixture file # # response - string or response object to store # fixture_file_name - file name to store the fixture in (relative to .fixture_root_path) @@ -42,12 +52,6 @@ module JavaScriptFixturesHelpers File.write(full_fixture_path, fixture) end - def remove_repository(project) - Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path) - end - - private - # Private: Prepare a response object for use as a frontend fixture # # response - response object to prepare -- cgit v1.2.1 From 3e881f7aa5e2ba783ef5bfb4ba3eda73f1fe9a17 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sat, 20 Apr 2019 14:33:47 +0200 Subject: Expose response variable for raw file fixtures --- spec/javascripts/fixtures/raw.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb index 82770beb39b..8813ed31339 100644 --- a/spec/javascripts/fixtures/raw.rb +++ b/spec/javascripts/fixtures/raw.rb @@ -1,10 +1,11 @@ require 'spec_helper' -describe 'Raw files', '(JavaScript fixtures)', type: :controller do +describe 'Raw files', '(JavaScript fixtures)' do include JavaScriptFixturesHelpers let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:project) { create(:project, :repository, namespace: namespace, path: 'raw-project') } + let(:response) { @blob.data.force_encoding('UTF-8') } before(:all) do clean_frontend_fixtures('blob/notebook/') @@ -15,20 +16,20 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do end it 'blob/notebook/basic.json' do |example| - blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') + @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') - store_frontend_fixture(blob.data, example.description) + store_frontend_fixture(response, example.description) end it 'blob/notebook/worksheets.json' do |example| - blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb') + @blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb') - store_frontend_fixture(blob.data, example.description) + store_frontend_fixture(response, example.description) end it 'blob/notebook/math.json' do |example| - blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb') + @blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb') - store_frontend_fixture(blob.data, example.description) + store_frontend_fixture(response, example.description) end end -- cgit v1.2.1 From dff8bd0c03f2d8f186909855a46d46bb90d9b391 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sat, 20 Apr 2019 14:36:38 +0200 Subject: Merge PDF fixture into raw file fixtures --- spec/javascripts/fixtures/pdf.rb | 18 ------------------ spec/javascripts/fixtures/raw.rb | 7 +++++++ 2 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 spec/javascripts/fixtures/pdf.rb (limited to 'spec') diff --git a/spec/javascripts/fixtures/pdf.rb b/spec/javascripts/fixtures/pdf.rb deleted file mode 100644 index ef9976b9fd3..00000000000 --- a/spec/javascripts/fixtures/pdf.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe 'PDF file', '(JavaScript fixtures)', type: :controller do - include JavaScriptFixturesHelpers - - let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, :repository, namespace: namespace, path: 'pdf-project') } - - before(:all) do - clean_frontend_fixtures('blob/pdf/') - end - - it 'blob/pdf/test.pdf' do |example| - blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf') - - store_frontend_fixture(blob.data.force_encoding("utf-8"), example.description) - end -end diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb index 8813ed31339..80304767788 100644 --- a/spec/javascripts/fixtures/raw.rb +++ b/spec/javascripts/fixtures/raw.rb @@ -9,6 +9,7 @@ describe 'Raw files', '(JavaScript fixtures)' do before(:all) do clean_frontend_fixtures('blob/notebook/') + clean_frontend_fixtures('blob/pdf/') end after do @@ -32,4 +33,10 @@ describe 'Raw files', '(JavaScript fixtures)' do store_frontend_fixture(response, example.description) end + + it 'blob/pdf/test.pdf' do |example| + @blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf') + + store_frontend_fixture(response, example.description) + end end -- cgit v1.2.1 From 66cd2e97fbb520ff27f5e6873fb9e598e17b1224 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sat, 20 Apr 2019 14:39:36 +0200 Subject: Merge balsamiq fixture into raw file fixtures --- spec/javascripts/fixtures/balsamiq.rb | 18 ------------------ spec/javascripts/fixtures/raw.rb | 7 +++++++ 2 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 spec/javascripts/fixtures/balsamiq.rb (limited to 'spec') diff --git a/spec/javascripts/fixtures/balsamiq.rb b/spec/javascripts/fixtures/balsamiq.rb deleted file mode 100644 index 234e246119a..00000000000 --- a/spec/javascripts/fixtures/balsamiq.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe 'Balsamiq file', '(JavaScript fixtures)', type: :controller do - include JavaScriptFixturesHelpers - - let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, :repository, namespace: namespace, path: 'balsamiq-project') } - - before(:all) do - clean_frontend_fixtures('blob/balsamiq/') - end - - it 'blob/balsamiq/test.bmpr' do |example| - blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr') - - store_frontend_fixture(blob.data.force_encoding('utf-8'), example.description) - end -end diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb index 80304767788..264558bed4a 100644 --- a/spec/javascripts/fixtures/raw.rb +++ b/spec/javascripts/fixtures/raw.rb @@ -8,6 +8,7 @@ describe 'Raw files', '(JavaScript fixtures)' do let(:response) { @blob.data.force_encoding('UTF-8') } before(:all) do + clean_frontend_fixtures('blob/balsamiq/') clean_frontend_fixtures('blob/notebook/') clean_frontend_fixtures('blob/pdf/') end @@ -16,6 +17,12 @@ describe 'Raw files', '(JavaScript fixtures)' do remove_repository(project) end + it 'blob/balsamiq/test.bmpr' do |example| + @blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr') + + store_frontend_fixture(response, example.description) + end + it 'blob/notebook/basic.json' do |example| @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') -- cgit v1.2.1 From cf33353b702ac63a193addb73a3cc07a09fc1902 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sun, 21 Apr 2019 12:08:22 +0200 Subject: Remove duplicate store_frontend_fixture() calls for f in $(find spec/javascripts/fixtures/ -name '*.rb') do grep -v store_frontend_fixture < $f > $f.tmp; mv $f.tmp $f done --- spec/javascripts/fixtures/abuse_reports.rb | 1 - spec/javascripts/fixtures/admin_users.rb | 1 - spec/javascripts/fixtures/application_settings.rb | 1 - spec/javascripts/fixtures/autocomplete_sources.rb | 1 - spec/javascripts/fixtures/blob.rb | 1 - spec/javascripts/fixtures/boards.rb | 1 - spec/javascripts/fixtures/branches.rb | 1 - spec/javascripts/fixtures/clusters.rb | 1 - spec/javascripts/fixtures/commit.rb | 1 - spec/javascripts/fixtures/deploy_keys.rb | 1 - spec/javascripts/fixtures/groups.rb | 2 -- spec/javascripts/fixtures/issues.rb | 3 --- spec/javascripts/fixtures/jobs.rb | 2 -- spec/javascripts/fixtures/labels.rb | 2 -- spec/javascripts/fixtures/merge_requests.rb | 2 -- spec/javascripts/fixtures/merge_requests_diffs.rb | 1 - spec/javascripts/fixtures/pipeline_schedules.rb | 2 -- spec/javascripts/fixtures/pipelines.rb | 1 - spec/javascripts/fixtures/projects.rb | 5 ----- spec/javascripts/fixtures/prometheus_service.rb | 1 - spec/javascripts/fixtures/raw.rb | 5 ----- spec/javascripts/fixtures/search.rb | 1 - spec/javascripts/fixtures/services.rb | 1 - spec/javascripts/fixtures/sessions.rb | 1 - spec/javascripts/fixtures/snippet.rb | 1 - spec/javascripts/fixtures/todos.rb | 2 -- spec/javascripts/fixtures/u2f.rb | 2 -- 27 files changed, 44 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb index 54b6419bcdb..4d15b426f4d 100644 --- a/spec/javascripts/fixtures/abuse_reports.rb +++ b/spec/javascripts/fixtures/abuse_reports.rb @@ -22,6 +22,5 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll get :index expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb index 76dbdf603da..c035fe678b9 100644 --- a/spec/javascripts/fixtures/admin_users.rb +++ b/spec/javascripts/fixtures/admin_users.rb @@ -24,6 +24,5 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do get :new expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb index c535e598e12..db88fa909b4 100644 --- a/spec/javascripts/fixtures/application_settings.rb +++ b/spec/javascripts/fixtures/application_settings.rb @@ -29,6 +29,5 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c get :show expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb index c117fb7cd24..2afce6e41a6 100644 --- a/spec/javascripts/fixtures/autocomplete_sources.rb +++ b/spec/javascripts/fixtures/autocomplete_sources.rb @@ -35,6 +35,5 @@ describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: } expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index db7749bc000..1a64f629125 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -30,6 +30,5 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do }) expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb index c4390e89578..42f8a5993da 100644 --- a/spec/javascripts/fixtures/boards.rb +++ b/spec/javascripts/fixtures/boards.rb @@ -24,6 +24,5 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller }) expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb index 5d2d6c7ec0e..70e11aa5922 100644 --- a/spec/javascripts/fixtures/branches.rb +++ b/spec/javascripts/fixtures/branches.rb @@ -28,6 +28,5 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle } expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 8ebd8a41366..01f6630fd45 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -30,6 +30,5 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle } expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index ab10f559e4b..3fe973675bf 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -29,6 +29,5 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller get :show, params: params expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb index a333d9c0150..cae9bc257d1 100644 --- a/spec/javascripts/fixtures/deploy_keys.rb +++ b/spec/javascripts/fixtures/deploy_keys.rb @@ -39,6 +39,5 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control }, format: :json expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index 16e31028b05..abad14998af 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -22,7 +22,6 @@ describe 'Groups (JavaScript fixtures)', type: :controller do get :edit, params: { id: group } expect(response).to be_success - store_frontend_fixture(response, example.description) end end @@ -31,7 +30,6 @@ describe 'Groups (JavaScript fixtures)', type: :controller do get :show, params: { group_id: group } expect(response).to be_success - store_frontend_fixture(response, example.description) end end end diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 0f3f9a10f94..3c7998bd125 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -49,7 +49,6 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller } expect(response).to be_success - store_frontend_fixture(response, example.description) end private @@ -62,7 +61,6 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller } expect(response).to be_success - store_frontend_fixture(response, fixture_file_name) end end @@ -120,6 +118,5 @@ describe API::Issues, '(JavaScript fixtures)', type: :request do get_related_merge_requests(project.id, issue.iid, user) expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 941235190b5..9bc76c042ee 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -40,7 +40,6 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do } expect(response).to be_success - store_frontend_fixture(response, example.description) end it 'jobs/delayed.json' do |example| @@ -51,6 +50,5 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do }, format: :json expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb index 9420194e675..427358a77c4 100644 --- a/spec/javascripts/fixtures/labels.rb +++ b/spec/javascripts/fixtures/labels.rb @@ -36,7 +36,6 @@ describe 'Labels (JavaScript fixtures)' do }, format: 'json' expect(response).to be_success - store_frontend_fixture(response, example.description) end end @@ -54,7 +53,6 @@ describe 'Labels (JavaScript fixtures)' do }, format: 'json' expect(response).to be_success - store_frontend_fixture(response, example.description) end end end diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 7df1e5cb512..ed62ed56fc2 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -121,7 +121,6 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont id: merge_request.to_param }, format: :json - store_frontend_fixture(response, fixture_file_name) end def render_merge_request(fixture_file_name, merge_request) @@ -132,6 +131,5 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont }, format: :html expect(response).to be_success - store_frontend_fixture(response, fixture_file_name) end end diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb index 57462e74bb2..1a330141749 100644 --- a/spec/javascripts/fixtures/merge_requests_diffs.rb +++ b/spec/javascripts/fixtures/merge_requests_diffs.rb @@ -66,6 +66,5 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type }, format: :json expect(response).to be_success - store_frontend_fixture(response, fixture_file_name) end end diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb index e5176a58273..eaf86a68639 100644 --- a/spec/javascripts/fixtures/pipeline_schedules.rb +++ b/spec/javascripts/fixtures/pipeline_schedules.rb @@ -29,7 +29,6 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : } expect(response).to be_success - store_frontend_fixture(response, example.description) end it 'pipeline_schedules/edit_with_variables.html' do |example| @@ -40,6 +39,5 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : } expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb index 42b552e81c0..3779dcaa311 100644 --- a/spec/javascripts/fixtures/pipelines.rb +++ b/spec/javascripts/fixtures/pipelines.rb @@ -30,6 +30,5 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll }, format: :json expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 446da83a7f9..91e35c819a2 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -35,7 +35,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do } expect(response).to be_success - store_frontend_fixture(response, example.description) end it 'projects/overview.html' do |example| @@ -45,7 +44,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do } expect(response).to be_success - store_frontend_fixture(response, example.description) end it 'projects/edit.html' do |example| @@ -55,7 +53,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do } expect(response).to be_success - store_frontend_fixture(response, example.description) end end @@ -67,7 +64,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do } expect(response).to be_success - store_frontend_fixture(response, example.description) end it 'projects/ci_cd_settings_with_variables.html' do |example| @@ -80,7 +76,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do } expect(response).to be_success - store_frontend_fixture(response, example.description) end end end diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb index 29dc95305b7..da8e1d49c46 100644 --- a/spec/javascripts/fixtures/prometheus_service.rb +++ b/spec/javascripts/fixtures/prometheus_service.rb @@ -30,6 +30,5 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle } expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb index 264558bed4a..47c1a60a22f 100644 --- a/spec/javascripts/fixtures/raw.rb +++ b/spec/javascripts/fixtures/raw.rb @@ -20,30 +20,25 @@ describe 'Raw files', '(JavaScript fixtures)' do it 'blob/balsamiq/test.bmpr' do |example| @blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr') - store_frontend_fixture(response, example.description) end it 'blob/notebook/basic.json' do |example| @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') - store_frontend_fixture(response, example.description) end it 'blob/notebook/worksheets.json' do |example| @blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb') - store_frontend_fixture(response, example.description) end it 'blob/notebook/math.json' do |example| @blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb') - store_frontend_fixture(response, example.description) end it 'blob/pdf/test.pdf' do |example| @blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf') - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb index 5f5b4d4e60d..a56e7ff1676 100644 --- a/spec/javascripts/fixtures/search.rb +++ b/spec/javascripts/fixtures/search.rb @@ -13,6 +13,5 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do get :show expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb index dc7ee484c22..5641901f043 100644 --- a/spec/javascripts/fixtures/services.rb +++ b/spec/javascripts/fixtures/services.rb @@ -30,6 +30,5 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle } expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb index 8656dea696a..ed54235f9a4 100644 --- a/spec/javascripts/fixtures/sessions.rb +++ b/spec/javascripts/fixtures/sessions.rb @@ -20,7 +20,6 @@ describe 'Sessions (JavaScript fixtures)' do get :new expect(response).to be_success - store_frontend_fixture(response, example.description) end end end diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index ebc5b793166..608988b5ff4 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -29,6 +29,5 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do get(:show, params: { id: snippet.to_param }) expect(response).to be_success - store_frontend_fixture(response, example.description) end end diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb index 6e37a2e5a4c..9777b861d11 100644 --- a/spec/javascripts/fixtures/todos.rb +++ b/spec/javascripts/fixtures/todos.rb @@ -30,7 +30,6 @@ describe 'Todos (JavaScript fixtures)' do get :index expect(response).to be_success - store_frontend_fixture(response, example.description) end end @@ -50,7 +49,6 @@ describe 'Todos (JavaScript fixtures)' do }, format: 'json' expect(response).to be_success - store_frontend_fixture(response, example.description) end end end diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index 15866d65a4f..d3f617dc590 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -24,7 +24,6 @@ context 'U2F' do post :create, params: { user: { login: user.username, password: user.password } } expect(response).to be_success - store_frontend_fixture(response, example.description) end end @@ -40,7 +39,6 @@ context 'U2F' do get :show expect(response).to be_success - store_frontend_fixture(response, example.description) end end end -- cgit v1.2.1 From d4bde8621cf5b10810508e6997674982fccf882b Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sun, 21 Apr 2019 12:58:07 +0200 Subject: Remove example block parameters for f in $(find spec/javascripts/fixtures/ -name '*.rb') do sed 's/do |example|/do/' < $f > $f.tmp; mv $f.tmp $f done --- spec/javascripts/fixtures/abuse_reports.rb | 2 +- spec/javascripts/fixtures/admin_users.rb | 2 +- spec/javascripts/fixtures/application_settings.rb | 2 +- spec/javascripts/fixtures/autocomplete_sources.rb | 2 +- spec/javascripts/fixtures/blob.rb | 2 +- spec/javascripts/fixtures/boards.rb | 2 +- spec/javascripts/fixtures/branches.rb | 2 +- spec/javascripts/fixtures/clusters.rb | 2 +- spec/javascripts/fixtures/commit.rb | 2 +- spec/javascripts/fixtures/deploy_keys.rb | 2 +- spec/javascripts/fixtures/groups.rb | 4 ++-- spec/javascripts/fixtures/issues.rb | 12 ++++++------ spec/javascripts/fixtures/jobs.rb | 4 ++-- spec/javascripts/fixtures/labels.rb | 4 ++-- spec/javascripts/fixtures/merge_requests.rb | 18 +++++++++--------- spec/javascripts/fixtures/merge_requests_diffs.rb | 6 +++--- spec/javascripts/fixtures/pipeline_schedules.rb | 4 ++-- spec/javascripts/fixtures/pipelines.rb | 2 +- spec/javascripts/fixtures/projects.rb | 10 +++++----- spec/javascripts/fixtures/prometheus_service.rb | 2 +- spec/javascripts/fixtures/raw.rb | 10 +++++----- spec/javascripts/fixtures/search.rb | 2 +- spec/javascripts/fixtures/services.rb | 2 +- spec/javascripts/fixtures/sessions.rb | 2 +- spec/javascripts/fixtures/snippet.rb | 2 +- spec/javascripts/fixtures/todos.rb | 4 ++-- spec/javascripts/fixtures/u2f.rb | 4 ++-- 27 files changed, 56 insertions(+), 56 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb index 4d15b426f4d..e0aaecf626a 100644 --- a/spec/javascripts/fixtures/abuse_reports.rb +++ b/spec/javascripts/fixtures/abuse_reports.rb @@ -18,7 +18,7 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll sign_in(admin) end - it 'abuse_reports/abuse_reports_list.html' do |example| + it 'abuse_reports/abuse_reports_list.html' do get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb index c035fe678b9..22a5de66577 100644 --- a/spec/javascripts/fixtures/admin_users.rb +++ b/spec/javascripts/fixtures/admin_users.rb @@ -17,7 +17,7 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('admin/users') end - it 'admin/users/new_with_internal_user_regex.html' do |example| + it 'admin/users/new_with_internal_user_regex.html' do stub_application_setting(user_default_external: true) stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?') diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb index db88fa909b4..d4651fa6ece 100644 --- a/spec/javascripts/fixtures/application_settings.rb +++ b/spec/javascripts/fixtures/application_settings.rb @@ -23,7 +23,7 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c remove_repository(project) end - it 'application_settings/accounts_and_limit.html' do |example| + it 'application_settings/accounts_and_limit.html' do stub_application_setting(user_default_external: false) get :show diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb index 2afce6e41a6..b20a0159d7d 100644 --- a/spec/javascripts/fixtures/autocomplete_sources.rb +++ b/spec/javascripts/fixtures/autocomplete_sources.rb @@ -18,7 +18,7 @@ describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: sign_in(admin) end - it 'autocomplete_sources/labels.json' do |example| + it 'autocomplete_sources/labels.json' do issue.labels << create(:label, project: project, title: 'bug') issue.labels << create(:label, project: project, title: 'critical') diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index 1a64f629125..07670552cd5 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -22,7 +22,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'blob/show.html' do |example| + it 'blob/show.html' do get(:show, params: { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb index 42f8a5993da..5835721d3d5 100644 --- a/spec/javascripts/fixtures/boards.rb +++ b/spec/javascripts/fixtures/boards.rb @@ -17,7 +17,7 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller sign_in(admin) end - it 'boards/show.html' do |example| + it 'boards/show.html' do get(:index, params: { namespace_id: project.namespace, project_id: project diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb index 70e11aa5922..204aa9b7c7a 100644 --- a/spec/javascripts/fixtures/branches.rb +++ b/spec/javascripts/fixtures/branches.rb @@ -21,7 +21,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'branches/new_branch.html' do |example| + it 'branches/new_branch.html' do get :new, params: { namespace_id: project.namespace.to_param, project_id: project diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 01f6630fd45..1076404e0e3 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -22,7 +22,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'clusters/show_cluster.html' do |example| + it 'clusters/show_cluster.html' do get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index 3fe973675bf..ff9a4bc1adc 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -19,7 +19,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end - it 'commit/show.html' do |example| + it 'commit/show.html' do params = { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb index cae9bc257d1..38eab853da2 100644 --- a/spec/javascripts/fixtures/deploy_keys.rb +++ b/spec/javascripts/fixtures/deploy_keys.rb @@ -24,7 +24,7 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control render_views - it 'deploy_keys/keys.json' do |example| + it 'deploy_keys/keys.json' do create(:rsa_deploy_key_2048, public: true) project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com') internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com') diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index abad14998af..4d0afc3ce1a 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -18,7 +18,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe GroupsController, '(JavaScript fixtures)', type: :controller do - it 'groups/edit.html' do |example| + it 'groups/edit.html' do get :edit, params: { id: group } expect(response).to be_success @@ -26,7 +26,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'groups/ci_cd_settings.html' do |example| + it 'groups/ci_cd_settings.html' do get :show, params: { group_id: group } expect(response).to be_success diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 3c7998bd125..2ac37ac70b6 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -21,26 +21,26 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller remove_repository(project) end - it 'issues/open-issue.html' do |example| + it 'issues/open-issue.html' do render_issue(example.description, create(:issue, project: project)) end - it 'issues/closed-issue.html' do |example| + it 'issues/closed-issue.html' do render_issue(example.description, create(:closed_issue, project: project)) end - it 'issues/issue-with-task-list.html' do |example| + it 'issues/issue-with-task-list.html' do issue = create(:issue, project: project, description: '- [ ] Task List Item') render_issue(example.description, issue) end - it 'issues/issue_with_comment.html' do |example| + it 'issues/issue_with_comment.html' do issue = create(:issue, project: project) create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save render_issue(example.description, issue) end - it 'issues/issue_list.html' do |example| + it 'issues/issue_list.html' do create(:issue, project: project) get :index, params: { @@ -87,7 +87,7 @@ describe API::Issues, '(JavaScript fixtures)', type: :request do end end - it 'issues/related_merge_requests.json' do |example| + it 'issues/related_merge_requests.json' do user = create(:user) project = create(:project, :public, creator_id: user.id, namespace: user.namespace) issue_title = 'foo' diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 9bc76c042ee..46ccd6f8c8a 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -32,7 +32,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'builds/build-with-artifacts.html' do |example| + it 'builds/build-with-artifacts.html' do get :show, params: { namespace_id: project.namespace.to_param, project_id: project, @@ -42,7 +42,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do expect(response).to be_success end - it 'jobs/delayed.json' do |example| + it 'jobs/delayed.json' do get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb index 427358a77c4..4d1b7317274 100644 --- a/spec/javascripts/fixtures/labels.rb +++ b/spec/javascripts/fixtures/labels.rb @@ -30,7 +30,7 @@ describe 'Labels (JavaScript fixtures)' do sign_in(admin) end - it 'labels/group_labels.json' do |example| + it 'labels/group_labels.json' do get :index, params: { group_id: group }, format: 'json' @@ -46,7 +46,7 @@ describe 'Labels (JavaScript fixtures)' do sign_in(admin) end - it 'labels/project_labels.json' do |example| + it 'labels/project_labels.json' do get :index, params: { namespace_id: group, project_id: project diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index ed62ed56fc2..354fe8db4bc 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -42,19 +42,19 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont remove_repository(project) end - it 'merge_requests/merge_request_of_current_user.html' do |example| + it 'merge_requests/merge_request_of_current_user.html' do merge_request.update(author: admin) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_task_list.html' do |example| + it 'merge_requests/merge_request_with_task_list.html' do create(:ci_build, :pending, pipeline: pipeline) render_merge_request(example.description, merge_request) end - it 'merge_requests/merged_merge_request.html' do |example| + it 'merge_requests/merged_merge_request.html' do expect_next_instance_of(MergeRequest) do |merge_request| allow(merge_request).to receive(:source_branch_exists?).and_return(true) allow(merge_request).to receive(:can_remove_source_branch?).and_return(true) @@ -62,28 +62,28 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merged_merge_request) end - it 'merge_requests/diff_comment.html' do |example| + it 'merge_requests/diff_comment.html' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_comment.html' do |example| + it 'merge_requests/merge_request_with_comment.html' do create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item') render_merge_request(example.description, merge_request) end - it 'merge_requests/discussions.json' do |example| + it 'merge_requests/discussions.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) render_discussions_json(merge_request, example.description) end - it 'merge_requests/diff_discussion.json' do |example| + it 'merge_requests/diff_discussion.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) render_discussions_json(merge_request, example.description) end - it 'merge_requests/resolved_diff_discussion.json' do |example| + it 'merge_requests/resolved_diff_discussion.json' do note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request) create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id) @@ -106,7 +106,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont ) end - it 'merge_requests/image_diff_discussion.json' do |example| + it 'merge_requests/image_diff_discussion.json' do create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position) render_discussions_json(merge_request2, example.description) end diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb index 1a330141749..29d76138fcc 100644 --- a/spec/javascripts/fixtures/merge_requests_diffs.rb +++ b/spec/javascripts/fixtures/merge_requests_diffs.rb @@ -34,7 +34,7 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type remove_repository(project) end - it 'merge_request_diffs/with_commit.json' do |example| + it 'merge_request_diffs/with_commit.json' do # Create a user that matches the selected commit author # This is so that the "author" information will be populated create(:user, email: selected_commit.author_email, name: selected_commit.author_name) @@ -42,13 +42,13 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type render_merge_request(example.description, merge_request, commit_id: selected_commit.sha) end - it 'merge_request_diffs/inline_changes_tab_with_comments.json' do |example| + it 'merge_request_diffs/inline_changes_tab_with_comments.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) render_merge_request(example.description, merge_request) end - it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do |example| + it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) render_merge_request(example.description, merge_request, view: 'parallel') diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb index eaf86a68639..aecd56e6198 100644 --- a/spec/javascripts/fixtures/pipeline_schedules.rb +++ b/spec/javascripts/fixtures/pipeline_schedules.rb @@ -21,7 +21,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : sign_in(admin) end - it 'pipeline_schedules/edit.html' do |example| + it 'pipeline_schedules/edit.html' do get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, @@ -31,7 +31,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : expect(response).to be_success end - it 'pipeline_schedules/edit_with_variables.html' do |example| + it 'pipeline_schedules/edit_with_variables.html' do get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb index 3779dcaa311..de6fcfe10f4 100644 --- a/spec/javascripts/fixtures/pipelines.rb +++ b/spec/javascripts/fixtures/pipelines.rb @@ -23,7 +23,7 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll sign_in(admin) end - it 'pipelines/pipelines.json' do |example| + it 'pipelines/pipelines.json' do get :index, params: { namespace_id: namespace, project_id: project diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 91e35c819a2..94c59207898 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -28,7 +28,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe ProjectsController, '(JavaScript fixtures)', type: :controller do - it 'projects/dashboard.html' do |example| + it 'projects/dashboard.html' do get :show, params: { namespace_id: project.namespace.to_param, id: project @@ -37,7 +37,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do expect(response).to be_success end - it 'projects/overview.html' do |example| + it 'projects/overview.html' do get :show, params: { namespace_id: project_with_repo.namespace.to_param, id: project_with_repo @@ -46,7 +46,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do expect(response).to be_success end - it 'projects/edit.html' do |example| + it 'projects/edit.html' do get :edit, params: { namespace_id: project.namespace.to_param, id: project @@ -57,7 +57,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'projects/ci_cd_settings.html' do |example| + it 'projects/ci_cd_settings.html' do get :show, params: { namespace_id: project.namespace.to_param, project_id: project @@ -66,7 +66,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do expect(response).to be_success end - it 'projects/ci_cd_settings_with_variables.html' do |example| + it 'projects/ci_cd_settings_with_variables.html' do create(:ci_variable, project: project_variable_populated) create(:ci_variable, project: project_variable_populated) diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb index da8e1d49c46..f3171fdd97b 100644 --- a/spec/javascripts/fixtures/prometheus_service.rb +++ b/spec/javascripts/fixtures/prometheus_service.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/prometheus/prometheus_service.html' do |example| + it 'services/prometheus/prometheus_service.html' do get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb index 47c1a60a22f..c207b4e06f6 100644 --- a/spec/javascripts/fixtures/raw.rb +++ b/spec/javascripts/fixtures/raw.rb @@ -17,27 +17,27 @@ describe 'Raw files', '(JavaScript fixtures)' do remove_repository(project) end - it 'blob/balsamiq/test.bmpr' do |example| + it 'blob/balsamiq/test.bmpr' do @blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr') end - it 'blob/notebook/basic.json' do |example| + it 'blob/notebook/basic.json' do @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') end - it 'blob/notebook/worksheets.json' do |example| + it 'blob/notebook/worksheets.json' do @blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb') end - it 'blob/notebook/math.json' do |example| + it 'blob/notebook/math.json' do @blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb') end - it 'blob/pdf/test.pdf' do |example| + it 'blob/pdf/test.pdf' do @blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf') end diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb index a56e7ff1676..22fc546d761 100644 --- a/spec/javascripts/fixtures/search.rb +++ b/spec/javascripts/fixtures/search.rb @@ -9,7 +9,7 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('search/') end - it 'search/show.html' do |example| + it 'search/show.html' do get :show expect(response).to be_success diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb index 5641901f043..2237702ccca 100644 --- a/spec/javascripts/fixtures/services.rb +++ b/spec/javascripts/fixtures/services.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/edit_service.html' do |example| + it 'services/edit_service.html' do get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb index ed54235f9a4..92b74c01c89 100644 --- a/spec/javascripts/fixtures/sessions.rb +++ b/spec/javascripts/fixtures/sessions.rb @@ -16,7 +16,7 @@ describe 'Sessions (JavaScript fixtures)' do set_devise_mapping(context: @request) end - it 'sessions/new.html' do |example| + it 'sessions/new.html' do get :new expect(response).to be_success diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index 608988b5ff4..ace84b14eb7 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -23,7 +23,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'snippets/show.html' do |example| + it 'snippets/show.html' do create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') get(:show, params: { id: snippet.to_param }) diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb index 9777b861d11..d0c8a6eca01 100644 --- a/spec/javascripts/fixtures/todos.rb +++ b/spec/javascripts/fixtures/todos.rb @@ -26,7 +26,7 @@ describe 'Todos (JavaScript fixtures)' do sign_in(admin) end - it 'todos/todos.html' do |example| + it 'todos/todos.html' do get :index expect(response).to be_success @@ -40,7 +40,7 @@ describe 'Todos (JavaScript fixtures)' do sign_in(admin) end - it 'todos/todos.json' do |example| + it 'todos/todos.json' do post :create, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index d3f617dc590..f52832b6efb 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -18,7 +18,7 @@ context 'U2F' do set_devise_mapping(context: @request) end - it 'u2f/authenticate.html' do |example| + it 'u2f/authenticate.html' do allow(controller).to receive(:find_user).and_return(user) post :create, params: { user: { login: user.username, password: user.password } } @@ -35,7 +35,7 @@ context 'U2F' do allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares') end - it 'u2f/register.html' do |example| + it 'u2f/register.html' do get :show expect(response).to be_success -- cgit v1.2.1 From 3ee2226b992ae2c801885d03e2818f6709cc5a65 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 23 Apr 2019 12:21:36 +0200 Subject: Remove unused fixture_file_name parameters for f in $(find spec/javascripts/fixtures/ -name '*.rb') do sed -E -e 's/(, ){0,1}(fixture_file_name| example\.description)(, ){0,1}//' < $f > $f.tmp; mv $f.tmp $f done --- spec/javascripts/fixtures/issues.rb | 10 +++++----- spec/javascripts/fixtures/merge_requests.rb | 22 +++++++++++----------- spec/javascripts/fixtures/merge_requests_diffs.rb | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 2ac37ac70b6..d8d77f767de 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -22,22 +22,22 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller end it 'issues/open-issue.html' do - render_issue(example.description, create(:issue, project: project)) + render_issue(create(:issue, project: project)) end it 'issues/closed-issue.html' do - render_issue(example.description, create(:closed_issue, project: project)) + render_issue(create(:closed_issue, project: project)) end it 'issues/issue-with-task-list.html' do issue = create(:issue, project: project, description: '- [ ] Task List Item') - render_issue(example.description, issue) + render_issue(issue) end it 'issues/issue_with_comment.html' do issue = create(:issue, project: project) create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save - render_issue(example.description, issue) + render_issue(issue) end it 'issues/issue_list.html' do @@ -53,7 +53,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller private - def render_issue(fixture_file_name, issue) + def render_issue(issue) get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 354fe8db4bc..68e5aca7a68 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -45,13 +45,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont it 'merge_requests/merge_request_of_current_user.html' do merge_request.update(author: admin) - render_merge_request(example.description, merge_request) + render_merge_request(merge_request) end it 'merge_requests/merge_request_with_task_list.html' do create(:ci_build, :pending, pipeline: pipeline) - render_merge_request(example.description, merge_request) + render_merge_request(merge_request) end it 'merge_requests/merged_merge_request.html' do @@ -59,35 +59,35 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont allow(merge_request).to receive(:source_branch_exists?).and_return(true) allow(merge_request).to receive(:can_remove_source_branch?).and_return(true) end - render_merge_request(example.description, merged_merge_request) + render_merge_request(merged_merge_request) end it 'merge_requests/diff_comment.html' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) - render_merge_request(example.description, merge_request) + render_merge_request(merge_request) end it 'merge_requests/merge_request_with_comment.html' do create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item') - render_merge_request(example.description, merge_request) + render_merge_request(merge_request) end it 'merge_requests/discussions.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) - render_discussions_json(merge_request, example.description) + render_discussions_json(merge_request) end it 'merge_requests/diff_discussion.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) - render_discussions_json(merge_request, example.description) + render_discussions_json(merge_request) end it 'merge_requests/resolved_diff_discussion.json' do note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request) create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id) - render_discussions_json(merge_request, example.description) + render_discussions_json(merge_request) end context 'with image diff' do @@ -108,13 +108,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont it 'merge_requests/image_diff_discussion.json' do create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position) - render_discussions_json(merge_request2, example.description) + render_discussions_json(merge_request2) end end private - def render_discussions_json(merge_request, fixture_file_name) + def render_discussions_json(merge_request) get :discussions, params: { namespace_id: project.namespace.to_param, project_id: project, @@ -123,7 +123,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont end - def render_merge_request(fixture_file_name, merge_request) + def render_merge_request(merge_request) get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb index 29d76138fcc..03b9b713fd8 100644 --- a/spec/javascripts/fixtures/merge_requests_diffs.rb +++ b/spec/javascripts/fixtures/merge_requests_diffs.rb @@ -39,24 +39,24 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type # This is so that the "author" information will be populated create(:user, email: selected_commit.author_email, name: selected_commit.author_name) - render_merge_request(example.description, merge_request, commit_id: selected_commit.sha) + render_merge_request(merge_request, commit_id: selected_commit.sha) end it 'merge_request_diffs/inline_changes_tab_with_comments.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) - render_merge_request(example.description, merge_request) + render_merge_request(merge_request) end it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) - render_merge_request(example.description, merge_request, view: 'parallel') + render_merge_request(merge_request, view: 'parallel') end private - def render_merge_request(fixture_file_name, merge_request, view: 'inline', **extra_params) + def render_merge_request(merge_request, view: 'inline', **extra_params) get :show, params: { namespace_id: project.namespace.to_param, project_id: project, -- cgit v1.2.1 From 8c7c68a5ebbe448e6ef9cff14ea2931d192eea93 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 24 Apr 2019 22:11:32 +0200 Subject: Remove trailing empty lines --- spec/javascripts/fixtures/merge_requests.rb | 1 - spec/javascripts/fixtures/raw.rb | 5 ----- 2 files changed, 6 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 68e5aca7a68..05860be2291 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -120,7 +120,6 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont project_id: project, id: merge_request.to_param }, format: :json - end def render_merge_request(merge_request) diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb index c207b4e06f6..801c80a0112 100644 --- a/spec/javascripts/fixtures/raw.rb +++ b/spec/javascripts/fixtures/raw.rb @@ -19,26 +19,21 @@ describe 'Raw files', '(JavaScript fixtures)' do it 'blob/balsamiq/test.bmpr' do @blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr') - end it 'blob/notebook/basic.json' do @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') - end it 'blob/notebook/worksheets.json' do @blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb') - end it 'blob/notebook/math.json' do @blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb') - end it 'blob/pdf/test.pdf' do @blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf') - end end -- cgit v1.2.1 From f59438c0a9da7bb4d98291d2adedfc5a13a50798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 10 May 2019 14:30:14 +0200 Subject: Fix unrelated errors when teardowning quanrantined specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we're skipping the example in a before hook, the @_subscribers instance variable that is normally set in the setup phase of the test by https://github.com/rails/rails-controller-testing/blob/21014e48be124dd8d0af250b79f682519d445015/lib/rails/controller/testing/template_assertions.rb#L21 isn't set but the teardown phase of the example still happens and tries to iterate over the array of subcribers: https://github.com/rails/rails-controller-testing/blob/21014e48be124dd8d0af250b79f682519d445015/lib/rails/controller/testing/template_assertions.rb#L54 Using an around hook allows the @_subscribers instance variable to be set. Signed-off-by: Rémy Coutable --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9266bee34d6..69589c9aa33 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -138,7 +138,7 @@ RSpec.configure do |config| .and_return(false) end - config.before(:example, :quarantine) do + config.around(:example, :quarantine) do # Skip tests in quarantine unless we explicitly focus on them. skip('In quarantine') unless config.inclusion_filter[:quarantine] end -- cgit v1.2.1 From db453ffdbd8108739a227519a327978778c28cf9 Mon Sep 17 00:00:00 2001 From: Patrick Derichs Date: Fri, 10 May 2019 13:33:44 +0000 Subject: Resolve "JIRA service: NoMethodError: undefined method `find' for nil:NilClass" --- spec/models/project_services/jira_service_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 4a7eee1fbf3..04ae9390436 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -166,6 +166,13 @@ describe JiraService do ).once end + it 'does not fail if remote_link.all on issue returns nil' do + allow(JIRA::Resource::Remotelink).to receive(:all).and_return(nil) + + expect { @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) } + .not_to raise_error(NoMethodError) + end + # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links # for more information it 'creates Remote Link reference in JIRA for comment' do -- cgit v1.2.1 From 0b27841fd61e97388c6b645d5812e8a06be567a3 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 10 May 2019 11:03:25 -0500 Subject: Eager load the GraphQL schema in specs Avoid counting on Rails eager loading the GraphQL types, but preload them before the specs. This would avoid loading the schema in 2 separate threads concurrently (1 inside the specs, 1 inside the test-server that will receive requests). Loading the schema in parallel like that could cause duplicate definition errors. --- spec/support/helpers/graphql_helpers.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index f15944652fd..44ed9da25fc 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -197,3 +197,7 @@ module GraphqlHelpers allow(GitlabSchema).to receive(:max_query_depth).with(any_args).and_return nil end end + +# This warms our schema, doing this as part of loading the helpers to avoid +# duplicate loading error when Rails tries autoload the types. +GitlabSchema.graphql_definition -- cgit v1.2.1 From 40eb7f72b9b3f97c5548129bd1c3bf66fd26434c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 10 May 2019 19:06:37 +0100 Subject: Fixes issues with show whitespace button in diffs Correctly updates the URL without overwriting parameters Reloads the diff file content without reloading the page Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/58852, https://gitlab.com/gitlab-org/gitlab-ce/issues/42597 --- spec/javascripts/diffs/store/actions_spec.js | 30 +++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index c82dcadd2f1..6309a8823d7 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -82,7 +82,7 @@ describe('DiffsStoreActions', () => { describe('fetchDiffFiles', () => { it('should fetch diff files', done => { - const endpoint = '/fetch/diff/files'; + const endpoint = '/fetch/diff/files?w=1'; const mock = new MockAdapter(axios); const res = { diff_files: 1, merge_request_diffs: [] }; mock.onGet(endpoint).reply(200, res); @@ -828,6 +828,10 @@ describe('DiffsStoreActions', () => { }); describe('setShowWhitespace', () => { + beforeEach(() => { + spyOn(eventHub, '$emit').and.stub(); + }); + it('commits SET_SHOW_WHITESPACE', done => { testAction( setShowWhitespace, @@ -855,6 +859,30 @@ describe('DiffsStoreActions', () => { expect(window.history.pushState).toHaveBeenCalled(); }); + + it('calls history pushState with merged params', () => { + const originalPushState = window.history; + + originalPushState.pushState({}, '', '?test=1'); + + spyOn(localStorage, 'setItem').and.stub(); + spyOn(window.history, 'pushState').and.stub(); + + setShowWhitespace({ commit() {} }, { showWhitespace: true, pushState: true }); + + expect(window.history.pushState.calls.mostRecent().args[2]).toMatch(/(.*)\?test=1&w=0/); + + originalPushState.pushState({}, '', '?'); + }); + + it('emits eventHub event', () => { + spyOn(localStorage, 'setItem').and.stub(); + spyOn(window.history, 'pushState').and.stub(); + + setShowWhitespace({ commit() {} }, { showWhitespace: true, pushState: true }); + + expect(eventHub.$emit).toHaveBeenCalledWith('refetchDiffData'); + }); }); describe('setRenderIt', () => { -- cgit v1.2.1 From c2674c2d37a55ad489392e4dd62feefad7bc8fae Mon Sep 17 00:00:00 2001 From: Lukas Eipert Date: Mon, 6 May 2019 18:37:13 -0500 Subject: Move to pdf.js distributed with npm pdf.js is available on npm. Importing the dependency from there instead of vendoring it has certain benefits, e.g. the discoverability of updates (especially security fixes). --- spec/javascripts/pdf/index_spec.js | 4 ++-- spec/javascripts/pdf/page_spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/pdf/index_spec.js b/spec/javascripts/pdf/index_spec.js index 7191b65b4cd..c746d5644e8 100644 --- a/spec/javascripts/pdf/index_spec.js +++ b/spec/javascripts/pdf/index_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import { GlobalWorkerOptions } from 'vendor/pdf'; -import workerSrc from 'vendor/pdf.worker.min'; +import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf'; +import workerSrc from 'pdfjs-dist/build/pdf.worker.min'; import PDFLab from '~/pdf/index.vue'; import { FIXTURES_PATH } from 'spec/test_constants'; diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js index f899b5b3a0d..6dea570266b 100644 --- a/spec/javascripts/pdf/page_spec.js +++ b/spec/javascripts/pdf/page_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import pdfjsLib from 'vendor/pdf'; -import workerSrc from 'vendor/pdf.worker.min'; +import pdfjsLib from 'pdfjs-dist/build/pdf'; +import workerSrc from 'pdfjs-dist/build/pdf.worker.min'; import PageComponent from '~/pdf/page/index.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -- cgit v1.2.1 From fae745c3421efe5793e55e4b29a538277608120c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 10 May 2019 23:00:36 +0100 Subject: Resolve CE/EE diffs in new proj member import Part of single codebase changes. --- spec/helpers/projects_helper_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'spec') diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 554cb861563..83271aa24a3 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -799,4 +799,24 @@ describe ProjectsHelper do it { is_expected.to eq(result) } end end + + describe '#can_import_members?' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:owner) { project.owner } + + before do + helper.instance_variable_set(:@project, project) + end + + it 'returns false if user cannot admin_project_member' do + allow(helper).to receive(:current_user) { user } + expect(helper.can_import_members?).to eq false + end + + it 'returns true if user can admin_project_member' do + allow(helper).to receive(:current_user) { owner } + expect(helper.can_import_members?).to eq true + end + end end -- cgit v1.2.1 From bd4652753b6be1c2d4d74bd9e5599949974185aa Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sat, 11 May 2019 05:16:19 +0100 Subject: Resolve CE/EE diffs in zero_authorized_projects Part of single codebase changes. --- spec/helpers/dashboard_helper_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb index 7ba24ba2956..023238ee0ae 100644 --- a/spec/helpers/dashboard_helper_spec.rb +++ b/spec/helpers/dashboard_helper_spec.rb @@ -21,4 +21,10 @@ describe DashboardHelper do expect(helper.dashboard_nav_links).not_to include(:activity, :milestones) end end + + describe '.has_start_trial?' do + subject { helper.has_start_trial? } + + it { is_expected.to eq(false) } + end end -- cgit v1.2.1 From 974e71a9c51b6f2d4ef2fb8ba9a538ced4106480 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 8 May 2019 06:26:10 +0100 Subject: Resolve CE/EE diffs in app/views/layouts view Part of single codebase changes. --- spec/helpers/nav_helper_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index e840c927d59..979d89812f5 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -50,4 +50,16 @@ describe NavHelper do expect(helper.header_links).to contain_exactly(:sign_in, :search) end end + + context '.admin_monitoring_nav_links' do + subject { helper.admin_monitoring_nav_links } + + it { is_expected.to all(be_a(String)) } + end + + context '.group_issues_sub_menu_items' do + subject { helper.group_issues_sub_menu_items } + + it { is_expected.to all(be_a(String)) } + end end -- cgit v1.2.1 From eb88ca76469539431b7f06592f177a77d5738612 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Sun, 12 May 2019 23:42:06 -0500 Subject: Rename methods that conflict in Rails 5.2 Adds suffix to enum methods and changes `in_groups` to `of_groups` --- spec/models/event_spec.rb | 2 +- spec/models/push_event_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index e91b5c4c86f..62663c247d1 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -88,7 +88,7 @@ describe Event do let(:event) { create_push_event(project, user) } it do - expect(event.push?).to be_truthy + expect(event.push_action?).to be_truthy expect(event.visible_to_user?(user)).to be_truthy expect(event.visible_to_user?(nil)).to be_falsey expect(event.tag?).to be_falsey diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb index f86500f91cd..5509ed87308 100644 --- a/spec/models/push_event_spec.rb +++ b/spec/models/push_event_spec.rb @@ -123,9 +123,9 @@ describe PushEvent do end end - describe '#push?' do + describe '#push_action?' do it 'returns true' do - expect(event).to be_push + expect(event).to be_push_action end end -- cgit v1.2.1 From e48305c02c5ed566ed6d98ff82e0d897a692008d Mon Sep 17 00:00:00 2001 From: lmcandrew Date: Mon, 13 May 2019 13:23:04 +0100 Subject: EE backport to CE of favicon_spec.rb --- spec/lib/gitlab/favicon_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb index 49a423191bb..dce56bbd2c4 100644 --- a/spec/lib/gitlab/favicon_spec.rb +++ b/spec/lib/gitlab/favicon_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Gitlab::Favicon, :request_store do expect(described_class.main).to match_asset_path '/assets/favicon.png' end - it 'has blue favicon for development' do + it 'has blue favicon for development', unless: Gitlab.ee? do allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development')) expect(described_class.main).to match_asset_path '/assets/favicon-blue.png' end -- cgit v1.2.1 From 4c2f6814907f3988c86c9f79a155d1d48ba61793 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 10 May 2019 16:12:33 -0500 Subject: Add correlation id to all sentry errors Before this, we were only adding the correlation id to the "acceptable exceptions" which we handle in code. But we need to add it to the default raven context so the information would be available for uncaught exceptions. --- spec/lib/gitlab/sentry_spec.rb | 5 ++++- spec/requests/api/helpers_spec.rb | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb index ae522a588ee..af8b059b984 100644 --- a/spec/lib/gitlab/sentry_spec.rb +++ b/spec/lib/gitlab/sentry_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' describe Gitlab::Sentry do describe '.context' do - it 'adds the locale to the tags' do + it 'adds the expected tags' do expect(described_class).to receive(:enabled?).and_return(true) + allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') described_class.context(nil) expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s) + expect(Raven.tags_context[Labkit::Correlation::CorrelationId::LOG_KEY.to_sym].to_s) + .to eq('cid') end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 25a312cb734..ed907841bd8 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -247,9 +247,8 @@ describe API::Helpers do exception = RuntimeError.new('test error') allow(exception).to receive(:backtrace).and_return(caller) - expect(Raven).to receive(:capture_exception).with(exception, tags: { - correlation_id: 'new-correlation-id' - }, extra: {}) + expect(Raven).to receive(:capture_exception).with(exception, tags: + a_hash_including(correlation_id: 'new-correlation-id'), extra: {}) Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do handle_api_exception(exception) -- cgit v1.2.1 From 99f06bc8589cf65012898ca5b32328613d5bd602 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Mon, 13 May 2019 01:13:30 -0500 Subject: Fix hiding of quick action prompt in specs --- spec/support/helpers/features/notes_helpers.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 38f30a14409..8a139fafac2 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -25,12 +25,10 @@ module Spec page.within('.js-main-target-form') do filled_text = fill_in('note[note]', with: text) - begin - # Dismiss quick action prompt if it appears - filled_text.parent.send_keys(:escape) - rescue Selenium::WebDriver::Error::ElementNotInteractableError - # It's fine if we can't escape when there's no prompt. - end + # Wait for quick action prompt to load and then dismiss it with ESC + # because it may block the Preview button + wait_for_requests + filled_text.send_keys(:escape) click_on('Preview') -- cgit v1.2.1 From b554011271b3acdc58bc2ef5bff1a3d40b69c5dd Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Tue, 14 May 2019 08:45:08 +0200 Subject: Fix project visibility level validation --- spec/models/project_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2a17bd6002e..425096d7e80 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -214,6 +214,13 @@ describe Project do expect(project2).not_to be_valid end + it 'validates the visibility' do + expect_any_instance_of(described_class).to receive(:visibility_level_allowed_as_fork).and_call_original + expect_any_instance_of(described_class).to receive(:visibility_level_allowed_by_group).and_call_original + + create(:project) + end + describe 'wiki path conflict' do context "when the new path has been used by the wiki of other Project" do it 'has an error on the name attribute' do -- cgit v1.2.1 From b6424b378d3fd79a78c597f1c3d630ab2245f460 Mon Sep 17 00:00:00 2001 From: Patrick Derichs Date: Tue, 14 May 2019 13:16:30 +0200 Subject: Fix confidential issue label disclosure on milestone view Add changelog entry Method should be public Use milestonish method Use render data to filter labels Add specs for label visibility on milestone --- .../projects/milestones_controller_spec.rb | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index f8470a94f98..767cee7d54a 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -175,6 +175,40 @@ describe Projects::MilestonesController do end end + describe '#labels' do + render_views + + context 'as json' do + let!(:guest) { create(:user, username: 'guest1') } + let!(:group) { create(:group, :public) } + let!(:project) { create(:project, :public, group: group) } + let!(:label) { create(:label, title: 'test_label_on_private_issue', project: project) } + let!(:confidential_issue) { create(:labeled_issue, confidential: true, project: project, milestone: milestone, labels: [label]) } + + it 'does not render labels of private issues if user has no access' do + sign_in(guest) + + get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type).to eq 'application/json' + + expect(json_response['html']).not_to include(label.title) + end + + it 'does render labels of private issues if user has access' do + sign_in(user) + + get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type).to eq 'application/json' + + expect(json_response['html']).to include(label.title) + end + end + end + context 'promotion succeeds' do before do group.add_developer(user) -- cgit v1.2.1 From 79dd92c8189600f24a29fe2da8d391cdb021f8fd Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Sat, 11 May 2019 07:06:44 -0500 Subject: Optimise upload path calls String#underscore isn't particularly slow, but it's possible for us to call it many times in a users autocomplete request, with mostly-static values ('User', 'Group', etc.). We can memoise this and save a surprising amount of time (around 10% of the total request time in some cases). --- spec/controllers/concerns/send_file_upload_spec.rb | 3 ++- spec/factories/uploads.rb | 2 +- spec/models/application_record_spec.rb | 6 ++++++ spec/uploaders/object_storage_spec.rb | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index 8408578a7db..a3ce08f736c 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -1,3 +1,4 @@ +# coding: utf-8 # frozen_string_literal: true require 'spec_helper' @@ -13,7 +14,7 @@ describe SendFileUpload do # user/:id def dynamic_segment - File.join(model.class.to_s.underscore, model.id.to_s) + File.join(model.class.underscore, model.id.to_s) end end end diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index 7256f785e1f..426abdc2a6c 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -13,7 +13,7 @@ FactoryBot.define do end # this needs to comply with RecordsUpload::Concern#upload_path - path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') } + path { File.join("uploads/-/system", model.class.underscore, mount_point.to_s, 'avatar.jpg') } trait :personal_snippet_upload do uploader "PersonalFileUploader" diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb index cc90a998d3f..74573d0941c 100644 --- a/spec/models/application_record_spec.rb +++ b/spec/models/application_record_spec.rb @@ -52,4 +52,10 @@ describe ApplicationRecord do expect { Suggestion.find_or_create_by!(note: nil) }.to raise_error(ActiveRecord::RecordInvalid) end end + + describe '.underscore' do + it 'returns the underscored value of the class as a string' do + expect(MergeRequest.underscore).to eq('merge_request') + end + end end diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index a62830c35f1..6bad5d49b1c 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -12,7 +12,7 @@ class Implementation < GitlabUploader # user/:id def dynamic_segment - File.join(model.class.to_s.underscore, model.id.to_s) + File.join(model.class.underscore, model.id.to_s) end end -- cgit v1.2.1 From f86797b5a91791875ccd1a02806d4cab236c1ac6 Mon Sep 17 00:00:00 2001 From: John Cai Date: Mon, 6 May 2019 16:57:44 -0700 Subject: Omit max-count for diverging_commit_counts behind feature flag We want to optimize the query for the CountDivergingCommits rpc by removing the --max-count argument now that we have commit graphs enabled for all repositories during housekeeping. However, we want to test this first behind a feature flag. --- spec/models/repository_spec.rb | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 9ff0f355fd4..c5ab7e57272 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2286,12 +2286,45 @@ describe Repository do end describe '#diverging_commit_counts' do + let(:diverged_branch) { repository.find_branch('fix') } + let(:root_ref_sha) { repository.raw_repository.commit(repository.root_ref).id } + let(:diverged_branch_sha) { diverged_branch.dereferenced_target.sha } + it 'returns the commit counts behind and ahead of default branch' do - result = repository.diverging_commit_counts( - repository.find_branch('fix')) + result = repository.diverging_commit_counts(diverged_branch) expect(result).to eq(behind: 29, ahead: 2) end + + context 'when gitaly_count_diverging_commits_no_max is enabled' do + before do + stub_feature_flags(gitaly_count_diverging_commits_no_max: true) + end + + it 'calls diverging_commit_count without max count' do + expect(repository.raw_repository) + .to receive(:diverging_commit_count) + .with(root_ref_sha, diverged_branch_sha) + .and_return([29, 2]) + + repository.diverging_commit_counts(diverged_branch) + end + end + + context 'when gitaly_count_diverging_commits_no_max is disabled' do + before do + stub_feature_flags(gitaly_count_diverging_commits_no_max: false) + end + + it 'calls diverging_commit_count with max count' do + expect(repository.raw_repository) + .to receive(:diverging_commit_count) + .with(root_ref_sha, diverged_branch_sha, max_count: Repository::MAX_DIVERGING_COUNT) + .and_return([29, 2]) + + repository.diverging_commit_counts(diverged_branch) + end + end end describe '#refresh_method_caches' do -- cgit v1.2.1 From 0f6500d5a770a11e7096208584ad4448eecc355e Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Fri, 10 May 2019 16:59:47 +0700 Subject: Added RoutableActions tests --- spec/controllers/concerns/routable_actions_spec.rb | 119 +++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 spec/controllers/concerns/routable_actions_spec.rb (limited to 'spec') diff --git a/spec/controllers/concerns/routable_actions_spec.rb b/spec/controllers/concerns/routable_actions_spec.rb new file mode 100644 index 00000000000..f6f2b8a280d --- /dev/null +++ b/spec/controllers/concerns/routable_actions_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe RoutableActions do + controller(::ApplicationController) do + include RoutableActions # rubocop:disable RSpec/DescribedClass + + before_action :routable + + def routable + @klass = params[:type].constantize + @routable = find_routable!(params[:type].constantize, params[:id]) + end + + def show + head :ok + end + end + + def get_routable(routable) + get :show, params: { id: routable.full_path, type: routable.class } + end + + describe '#find_routable!' do + context 'when signed in' do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'with a project' do + let(:routable) { create(:project) } + + context 'when authorized' do + before do + routable.add_guest(user) + end + + it 'returns the project' do + get_routable(routable) + + expect(assigns[:routable]).to be_a(Project) + end + + it 'allows access' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(200) + end + end + + it 'prevents access when not authorized' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'with a group' do + let(:routable) { create(:group, :private) } + + context 'when authorized' do + before do + routable.add_guest(user) + end + + it 'returns the group' do + get_routable(routable) + + expect(assigns[:routable]).to be_a(Group) + end + + it 'allows access' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(200) + end + end + + it 'prevents access when not authorized' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'with a user' do + let(:routable) { user } + + it 'allows access when authorized' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(200) + end + + it 'prevents access when unauthorized' do + allow(subject).to receive(:can?).and_return(false) + + get_routable(user) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + context 'when not signed in' do + it 'redirects to sign in for private resouces' do + routable = create(:project, :private) + + get_routable(routable) + + expect(response).to have_gitlab_http_status(302) + expect(response.location).to end_with('/users/sign_in') + end + end + end +end -- cgit v1.2.1 From 5ec28dc387fb4adc3c5b65ac47819a8663186954 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Mon, 8 Apr 2019 14:17:22 +0300 Subject: Changes to issues api When issues_controller endpoint was used for search, the parameters passed to the controller were slightly different then the ones passed to API. Because the searchbar UI is reused in different places and builds the parameters passed to request in same way we need to account for old parameter names. Add issues_statistics api endpoints Adds issue_statistics api endpoints for issue lists and returns counts of issues for all, closed and opened states. Expose more label attributes based on a param When requesting issues list through API expose more attributes for labels, like color, description if with_labels_data param is being passed, avoiding this way to change response schema for users that already use API. https://gitlab.com/gitlab-org/gitlab-ce/issues/57402 --- spec/fixtures/api/schemas/public_api/v4/issue.json | 6 +- .../api/schemas/public_api/v4/label_basic.json | 24 + spec/requests/api/issues/get_group_issues_spec.rb | 630 ++++++ .../requests/api/issues/get_project_issues_spec.rb | 783 +++++++ spec/requests/api/issues/issues_spec.rb | 750 +++++++ .../api/issues/post_projects_issues_spec.rb | 549 +++++ .../api/issues/put_projects_issues_spec.rb | 392 ++++ spec/requests/api/issues_spec.rb | 2265 -------------------- .../requests/api/issues_shared_example_spec.rb | 68 + 9 files changed, 3200 insertions(+), 2267 deletions(-) create mode 100644 spec/fixtures/api/schemas/public_api/v4/label_basic.json create mode 100644 spec/requests/api/issues/get_group_issues_spec.rb create mode 100644 spec/requests/api/issues/get_project_issues_spec.rb create mode 100644 spec/requests/api/issues/issues_spec.rb create mode 100644 spec/requests/api/issues/post_projects_issues_spec.rb create mode 100644 spec/requests/api/issues/put_projects_issues_spec.rb delete mode 100644 spec/requests/api/issues_spec.rb create mode 100644 spec/support/shared_examples/requests/api/issues_shared_example_spec.rb (limited to 'spec') diff --git a/spec/fixtures/api/schemas/public_api/v4/issue.json b/spec/fixtures/api/schemas/public_api/v4/issue.json index 147f53239e0..455941f05da 100644 --- a/spec/fixtures/api/schemas/public_api/v4/issue.json +++ b/spec/fixtures/api/schemas/public_api/v4/issue.json @@ -14,7 +14,7 @@ "labels": { "type": "array", "items": { - "type": "string" + "$ref": "../../entities/label.json" } }, "milestone": { @@ -79,6 +79,8 @@ "due_date": { "type": ["date", "null"] }, "confidential": { "type": "boolean" }, "web_url": { "type": "uri" }, + "has_tasks": {"type": "boolean"}, + "task_status": {"type": "string"}, "time_stats": { "time_estimate": { "type": "integer" }, "total_time_spent": { "type": "integer" }, @@ -91,6 +93,6 @@ "state", "created_at", "updated_at", "labels", "milestone", "assignees", "author", "user_notes_count", "upvotes", "downvotes", "due_date", "confidential", - "web_url" + "has_tasks", "web_url" ] } diff --git a/spec/fixtures/api/schemas/public_api/v4/label_basic.json b/spec/fixtures/api/schemas/public_api/v4/label_basic.json new file mode 100644 index 00000000000..37bbdcb14fe --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/label_basic.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "required": [ + "id", + "name", + "color", + "description", + "text_color" + ], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "color": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{3}{1,2}$" + }, + "description": { "type": ["string", "null"] }, + "text_color": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{3}{1,2}$" + } + }, + "additionalProperties": false +} diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb new file mode 100644 index 00000000000..aafe0f56dfb --- /dev/null +++ b/spec/requests/api/issues/get_group_issues_spec.rb @@ -0,0 +1,630 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Issues do + set(:user) { create(:user) } + let(:user2) { create(:user) } + let(:non_member) { create(:user) } + set(:guest) { create(:user) } + set(:author) { create(:author) } + set(:assignee) { create(:assignee) } + let(:admin) { create(:user, :admin) } + + let(:issue_title) { 'foo' } + let(:issue_description) { 'closed' } + + let(:no_milestone_title) { 'None' } + let(:any_milestone_title) { 'Any' } + + before do + stub_licensed_features(multiple_issue_assignees: false, issue_weights: false) + end + + describe 'GET /groups/:id/issues' do + let!(:group) { create(:group) } + let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } + let!(:group_closed_issue) do + create :closed_issue, + author: user, + assignees: [user], + project: group_project, + state: :closed, + milestone: group_milestone, + updated_at: 3.hours.ago, + created_at: 1.day.ago + end + let!(:group_confidential_issue) do + create :issue, + :confidential, + project: group_project, + author: author, + assignees: [assignee], + updated_at: 2.hours.ago, + created_at: 2.days.ago + end + let!(:group_issue) do + create :issue, + author: user, + assignees: [user], + project: group_project, + milestone: group_milestone, + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description, + created_at: 5.days.ago + end + let!(:group_label) do + create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) + end + let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) } + let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) } + let!(:group_empty_milestone) do + create(:milestone, title: '4.0.0', project: group_project) + end + let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) } + + let(:base_url) { "/groups/#{group.id}/issues" } + + shared_examples 'group issues statistics' do + it 'returns issues statistics' do + get api("/groups/#{group.id}/issues_statistics", user), params: params + + expect(response).to have_gitlab_http_status(200) + expect(json_response['statistics']).not_to be_nil + expect(json_response['statistics']['counts']['all']).to eq counts[:all] + expect(json_response['statistics']['counts']['closed']).to eq counts[:closed] + expect(json_response['statistics']['counts']['opened']).to eq counts[:opened] + end + end + + context 'when group has subgroups', :nested_groups do + let(:subgroup_1) { create(:group, parent: group) } + let(:subgroup_2) { create(:group, parent: subgroup_1) } + + let(:subgroup_1_project) { create(:project, :public, namespace: subgroup_1) } + let(:subgroup_2_project) { create(:project, namespace: subgroup_2) } + + let!(:issue_1) { create(:issue, project: subgroup_1_project) } + let!(:issue_2) { create(:issue, project: subgroup_2_project) } + + context 'when user is unauthenticated' do + it 'also returns subgroups public projects issues' do + get api(base_url) + + expect_paginated_array_response([issue_1.id, group_closed_issue.id, group_issue.id]) + end + + it 'also returns subgroups public projects issues filtered by milestone' do + get api(base_url), params: { milestone: group_milestone.title } + + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: group_milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: group_milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + end + end + + context 'when user is a group member' do + before do + group.add_developer(user) + end + + it 'also returns subgroups projects issues' do + get api(base_url, user) + + expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id]) + end + + it 'also returns subgroups public projects issues filtered by milestone' do + get api(base_url, user), params: { milestone: group_milestone.title } + + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 5, closed: 1, opened: 4 } } + + it_behaves_like 'group issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 5, closed: 1, opened: 4 } } + + it_behaves_like 'group issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 5, closed: 1, opened: 4 } } + + it_behaves_like 'group issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 5, closed: 1, opened: 4 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: group_milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: group_milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + end + end + end + + context 'when user is unauthenticated' do + it 'lists all issues in public projects' do + get api(base_url) + + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) + end + + it 'also returns subgroups public projects issues filtered by milestone' do + get api(base_url), params: { milestone: group_milestone.title } + + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: group_milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: group_milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + end + end + + context 'when user is a group member' do + before do + group_project.add_reporter(user) + end + + it 'returns all group issues (including opened and closed)' do + get api(base_url, admin) + + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) + end + + it 'returns group issues without confidential issues for non project members' do + get api(base_url, non_member), params: { state: :opened } + + expect_paginated_array_response(group_issue.id) + end + + it 'returns group confidential issues for author' do + get api(base_url, author), params: { state: :opened } + + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) + end + + it 'returns group confidential issues for assignee' do + get api(base_url, assignee), params: { state: :opened } + + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) + end + + it 'returns group issues with confidential issues for project members' do + get api(base_url, user), params: { state: :opened } + + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) + end + + it 'returns group confidential issues for admin' do + get api(base_url, admin), params: { state: :opened } + + expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) + end + + it 'returns only confidential issues' do + get api(base_url, user), params: { confidential: true } + + expect_paginated_array_response(group_confidential_issue.id) + end + + it 'returns only public issues' do + get api(base_url, user), params: { confidential: false } + + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) + end + + it 'returns an array of labeled group issues' do + get api(base_url, user), params: { labels: group_label.title } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['labels']).to eq([group_label.title]) + end + + it 'returns an array of labeled group issues with labels param as array' do + get api(base_url, user), params: { labels: [group_label.title] } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['labels']).to eq([group_label.title]) + end + + it 'returns an array of labeled group issues where all labels match' do + get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" } + + expect_paginated_array_response([]) + end + + it 'returns an array of labeled group issues where all labels match with labels param as array' do + get api(base_url, user), params: { labels: [group_label.title, 'foo', 'bar'] } + + expect_paginated_array_response([]) + end + + it 'returns issues matching given search string for title' do + get api(base_url, user), params: { search: group_issue.title } + + expect_paginated_array_response(group_issue.id) + end + + it 'returns issues matching given search string for description' do + get api(base_url, user), params: { search: group_issue.description } + + expect_paginated_array_response(group_issue.id) + end + + context 'with labeled issues' do + let(:label_b) { create(:label, title: 'foo', project: group_project) } + let(:label_c) { create(:label, title: 'bar', project: group_project) } + + before do + create(:label_link, label: label_b, target: group_issue) + create(:label_link, label: label_c, target: group_issue) + + get api(base_url, user), params: params + end + + let(:issue) { group_issue } + let(:label) { group_label } + + it_behaves_like 'labeled issues with labels and label_name params' + end + + it 'returns an array of issues found by iids' do + get api(base_url, user), params: { iids: [group_issue.iid] } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['id']).to eq(group_issue.id) + end + + it 'returns an empty array if iid does not exist' do + get api(base_url, user), params: { iids: [0] } + + expect_paginated_array_response([]) + end + + it 'returns an empty array if no group issue matches labels' do + get api(base_url, user), params: { labels: 'foo,bar' } + + expect_paginated_array_response([]) + end + + it 'returns an array of group issues with any label' do + get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['id']).to eq(group_issue.id) + end + + it 'returns an array of group issues with any label with labels param as array' do + get api(base_url, user), params: { labels: [IssuesFinder::FILTER_ANY] } + + expect_paginated_array_response(group_issue.id) + expect(json_response.first['id']).to eq(group_issue.id) + end + + it 'returns an array of group issues with no label' do + get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE } + + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id]) + end + + it 'returns an array of group issues with no label with labels param as array' do + get api(base_url, user), params: { labels: [IssuesFinder::FILTER_NONE] } + + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id]) + end + + it 'returns an empty array if no issue matches milestone' do + get api(base_url, user), params: { milestone: group_empty_milestone.title } + + expect_paginated_array_response([]) + end + + it 'returns an empty array if milestone does not exist' do + get api(base_url, user), params: { milestone: 'foo' } + + expect_paginated_array_response([]) + end + + it 'returns an array of issues in given milestone' do + get api(base_url, user), params: { state: :opened, milestone: group_milestone.title } + + expect_paginated_array_response(group_issue.id) + end + + it 'returns an array of issues matching state in milestone' do + get api(base_url, user), params: { milestone: group_milestone.title, state: :closed } + + expect_paginated_array_response(group_closed_issue.id) + end + + it 'returns an array of issues with no milestone' do + get api(base_url, user), params: { milestone: no_milestone_title } + + expect(response).to have_gitlab_http_status(200) + + expect_paginated_array_response(group_confidential_issue.id) + end + + context 'without sort params' do + it 'sorts by created_at descending by default' do + get api(base_url, user) + + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) + end + + context 'with 2 issues with same created_at' do + let!(:group_issue2) do + create :issue, + author: user, + assignees: [user], + project: group_project, + milestone: group_milestone, + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description, + created_at: group_issue.created_at + end + + it 'page breaks first page correctly' do + get api("#{base_url}?per_page=3", user) + + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue2.id]) + end + + it 'page breaks second page correctly' do + get api("#{base_url}?per_page=3&page=2", user) + + expect_paginated_array_response([group_issue.id]) + end + end + end + + it 'sorts ascending when requested' do + get api("#{base_url}?sort=asc", user) + + expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id]) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}?order_by=updated_at", user) + + group_issue.touch(:updated_at) + + expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id]) + end + + it 'sorts by updated_at ascending when requested' do + get api(base_url, user), params: { order_by: :updated_at, sort: :asc } + + expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: group_milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: group_milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: group_milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'group issues statistics' + end + + context 'sort does not affect statistics ' do + let(:params) { { state: :opened, order_by: 'updated_at' } } + let(:counts) { { all: 3, closed: 1, opened: 2 } } + + it_behaves_like 'group issues statistics' + end + end + + context 'filtering by assignee_username' do + let(:another_assignee) { create(:assignee) } + let!(:issue1) { create(:issue, author: user2, project: group_project, created_at: 3.days.ago) } + let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) } + let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) } + + it 'returns issues with multiple assignees' do + get api("/groups/#{group.id}/issues", user), params: { assignee_username: [assignee.username, another_assignee.username] } + + expect_paginated_array_response([issue3.id, group_confidential_issue.id]) + end + end + end + end +end diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb new file mode 100644 index 00000000000..8771889f7a4 --- /dev/null +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -0,0 +1,783 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Issues do + set(:user) { create(:user) } + set(:project) do + create(:project, :public, creator_id: user.id, namespace: user.namespace) + end + + let(:user2) { create(:user) } + let(:non_member) { create(:user) } + set(:guest) { create(:user) } + set(:author) { create(:author) } + set(:assignee) { create(:assignee) } + let(:admin) { create(:user, :admin) } + let(:issue_title) { 'foo' } + let(:issue_description) { 'closed' } + let!(:closed_issue) do + create :closed_issue, + author: user, + assignees: [user], + project: project, + state: :closed, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 3.hours.ago, + closed_at: 1.hour.ago + end + let!(:confidential_issue) do + create :issue, + :confidential, + project: project, + author: author, + assignees: [assignee], + created_at: generate(:past_time), + updated_at: 2.hours.ago + end + let!(:issue) do + create :issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + end + set(:label) do + create(:label, title: 'label', color: '#FFAABB', project: project) + end + let!(:label_link) { create(:label_link, label: label, target: issue) } + let(:milestone) { create(:milestone, title: '1.0.0', project: project) } + set(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end + let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } + + let(:no_milestone_title) { 'None' } + let(:any_milestone_title) { 'Any' } + + before(:all) do + project.add_reporter(user) + project.add_guest(guest) + end + + before do + stub_licensed_features(multiple_issue_assignees: false, issue_weights: false) + end + + shared_examples 'project issues statistics' do + it 'returns project issues statistics' do + get api("/issues_statistics", user), params: params + + expect(response).to have_gitlab_http_status(200) + expect(json_response['statistics']).not_to be_nil + expect(json_response['statistics']['counts']['all']).to eq counts[:all] + expect(json_response['statistics']['counts']['closed']).to eq counts[:closed] + expect(json_response['statistics']['counts']['opened']).to eq counts[:opened] + end + end + + describe "GET /projects/:id/issues" do + let(:base_url) { "/projects/#{project.id}" } + + context 'when unauthenticated' do + it 'returns public project issues' do + get api("/projects/#{project.id}/issues") + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'sort does not affect statistics ' do + let(:params) { { state: :opened, order_by: 'updated_at' } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + end + end + + it 'avoids N+1 queries' do + get api("/projects/#{project.id}/issues", user) + + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get api("/projects/#{project.id}/issues", user) + end.count + + create_list(:issue, 3, project: project) + + expect do + get api("/projects/#{project.id}/issues", user) + end.not_to exceed_all_query_limit(control_count) + end + + it 'returns 404 when project does not exist' do + get api('/projects/1000/issues', non_member) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 on private projects for other users' do + private_project = create(:project, :private) + create(:issue, project: private_project) + + get api("/projects/#{private_project.id}/issues", non_member) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns no issues when user has access to project but not issues' do + restricted_project = create(:project, :public, :issues_private) + create(:issue, project: restricted_project) + + get api("/projects/#{restricted_project.id}/issues", non_member) + + expect_paginated_array_response([]) + end + + it 'returns project issues without confidential issues for non project members' do + get api("#{base_url}/issues", non_member) + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns project issues without confidential issues for project members with guest role' do + get api("#{base_url}/issues", guest) + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns project confidential issues for author' do + get api("#{base_url}/issues", author) + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + end + + it 'returns only confidential issues' do + get api("#{base_url}/issues", author), params: { confidential: true } + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns only public issues' do + get api("#{base_url}/issues", author), params: { confidential: false } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns project confidential issues for assignee' do + get api("#{base_url}/issues", assignee) + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + end + + it 'returns project issues with confidential issues for project members' do + get api("#{base_url}/issues", user) + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + end + + it 'returns project confidential issues for admin' do + get api("#{base_url}/issues", admin) + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + end + + it 'returns an array of labeled project issues' do + get api("#{base_url}/issues", user), params: { labels: label.title } + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of labeled project issues with labels param as array' do + get api("#{base_url}/issues", user), params: { labels: [label.title] } + + expect_paginated_array_response(issue.id) + end + + context 'with labeled issues' do + let(:label_b) { create(:label, title: 'foo', project: project) } + let(:label_c) { create(:label, title: 'bar', project: project) } + + before do + create(:label_link, label: label_b, target: issue) + create(:label_link, label: label_c, target: issue) + + get api('/issues', user), params: params + end + + it_behaves_like 'labeled issues with labels and label_name params' + end + + it 'returns issues matching given search string for title' do + get api("#{base_url}/issues?search=#{issue.title}", user) + + expect_paginated_array_response(issue.id) + end + + it 'returns issues matching given search string for description' do + get api("#{base_url}/issues?search=#{issue.description}", user) + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of issues found by iids' do + get api("#{base_url}/issues", user), params: { iids: [issue.iid] } + + expect_paginated_array_response(issue.id) + end + + it 'returns an empty array if iid does not exist' do + get api("#{base_url}/issues", user), params: { iids: [0] } + + expect_paginated_array_response([]) + end + + it 'returns an empty array if not all labels matches' do + get api("#{base_url}/issues?labels=#{label.title},foo", user) + + expect_paginated_array_response([]) + end + + it 'returns an array of project issues with any label' do + get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY } + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of project issues with any label with labels param as array' do + get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_ANY] } + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of project issues with no label' do + get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE } + + expect_paginated_array_response([confidential_issue.id, closed_issue.id]) + end + + it 'returns an array of project issues with no label with labels param as array' do + get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_NONE] } + + expect_paginated_array_response([confidential_issue.id, closed_issue.id]) + end + + it 'returns an empty array if no project issue matches labels' do + get api("#{base_url}/issues", user), params: { labels: 'foo,bar' } + + expect_paginated_array_response([]) + end + + it 'returns an empty array if no issue matches milestone' do + get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title } + + expect_paginated_array_response([]) + end + + it 'returns an empty array if milestone does not exist' do + get api("#{base_url}/issues", user), params: { milestone: :foo } + + expect_paginated_array_response([]) + end + + it 'returns an array of issues in given milestone' do + get api("#{base_url}/issues", user), params: { milestone: milestone.title } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns an array of issues matching state in milestone' do + get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of issues with no milestone' do + get api("#{base_url}/issues", user), params: { milestone: no_milestone_title } + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns an array of issues with any milestone' do + get api("#{base_url}/issues", user), params: { milestone: any_milestone_title } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + context 'without sort params' do + it 'sorts by created_at descending by default' do + get api("#{base_url}/issues", user) + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + end + + context 'with 2 issues with same created_at' do + let!(:closed_issue2) do + create :closed_issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: closed_issue.created_at, + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + end + + it 'page breaks first page correctly' do + get api("#{base_url}/issues?per_page=3", user) + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue2.id]) + end + + it 'page breaks second page correctly' do + get api("#{base_url}/issues?per_page=3&page=2", user) + + expect_paginated_array_response([closed_issue.id]) + end + end + end + + it 'sorts ascending when requested' do + get api("#{base_url}/issues", user), params: { sort: :asc } + + expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}/issues", user), params: { order_by: :updated_at } + + issue.touch(:updated_at) + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + end + + it 'sorts by updated_at ascending when requested' do + get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc } + + expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + + context 'sort does not affect statistics ' do + let(:params) { { state: :opened, order_by: 'updated_at' } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'project issues statistics' + end + end + + context 'filtering by assignee_username' do + let(:another_assignee) { create(:assignee) } + let!(:issue1) { create(:issue, author: user2, project: project, created_at: 3.days.ago) } + let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } + let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } + + it 'returns issues with multiple assignees' do + get api("#{base_url}/issues", user), params: { assignee_username: [assignee.username, another_assignee.username] } + + expect_paginated_array_response([confidential_issue.id, issue3.id]) + end + end + end + + describe 'GET /projects/:id/issues/:issue_iid' do + context 'when unauthenticated' do + it 'returns public issues' do + get api("/projects/#{project.id}/issues/#{issue.iid}") + + expect(response).to have_gitlab_http_status(200) + end + end + + it 'exposes known attributes' do + get api("/projects/#{project.id}/issues/#{issue.iid}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(issue.id) + expect(json_response['iid']).to eq(issue.iid) + expect(json_response['project_id']).to eq(issue.project.id) + expect(json_response['title']).to eq(issue.title) + expect(json_response['description']).to eq(issue.description) + expect(json_response['state']).to eq(issue.state) + expect(json_response['closed_at']).to be_falsy + expect(json_response['created_at']).to be_present + expect(json_response['updated_at']).to be_present + expect(json_response['labels']).to eq(issue.label_names) + expect(json_response['milestone']).to be_a Hash + expect(json_response['assignees']).to be_a Array + expect(json_response['assignee']).to be_a Hash + expect(json_response['author']).to be_a Hash + expect(json_response['confidential']).to be_falsy + end + + it 'exposes the closed_at attribute' do + get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['closed_at']).to be_present + end + + context 'links exposure' do + it 'exposes related resources full URIs' do + get api("/projects/#{project.id}/issues/#{issue.iid}", user) + + links = json_response['_links'] + + expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}") + expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes") + expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji") + expect(links['project']).to end_with("/api/v4/projects/#{project.id}") + end + end + + it 'returns a project issue by internal id' do + get api("/projects/#{project.id}/issues/#{issue.iid}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(issue.title) + expect(json_response['iid']).to eq(issue.iid) + end + + it 'returns 404 if issue id not found' do + get api("/projects/#{project.id}/issues/54321", user) + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 if the issue ID is used' do + get api("/projects/#{project.id}/issues/#{issue.id}", user) + + expect(response).to have_gitlab_http_status(404) + end + + context 'confidential issues' do + it 'returns 404 for non project members' do + get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 for project members with guest role' do + get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns confidential issue for project members' do + get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + + it 'returns confidential issue for author' do + get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + + it 'returns confidential issue for assignee' do + get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + + it 'returns confidential issue for admin' do + get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + end + end + + describe 'GET :id/issues/:issue_iid/closed_by' do + let(:merge_request) do + create(:merge_request, + :simple, + author: user, + source_project: project, + target_project: project, + description: "closes #{issue.to_reference}") + end + + before do + create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) + end + + context 'when unauthenticated' do + it 'return public project issues' do + get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by") + + expect_paginated_array_response(merge_request.id) + end + end + + it 'returns merge requests that will close issue on merge' do + get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user) + + expect_paginated_array_response(merge_request.id) + end + + context 'when no merge requests will close issue' do + it 'returns empty array' do + get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user) + + expect_paginated_array_response([]) + end + end + + it "returns 404 when issue doesn't exists" do + get api("/projects/#{project.id}/issues/0/closed_by", user) + + expect(response).to have_gitlab_http_status(404) + 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: issue.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(related_mr.id) + 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(related_mr.id) + end + + it 'returns merge requests cross-project wide' do + project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace) + merge_request = create_referencing_mr(user, project2, issue) + + get_related_merge_requests(project.id, issue.iid, user) + + expect_paginated_array_response([related_mr.id, merge_request.id]) + end + + it 'does not generate references to projects with no access' do + private_project = create(:project, :private) + create_referencing_mr(private_project.creator, private_project, issue) + + get_related_merge_requests(project.id, issue.iid, user) + + expect_paginated_array_response(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([]) + end + end + + it "returns 404 when issue doesn't exists" do + get_related_merge_requests(project.id, 0, 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) } + + context 'when unauthenticated' do + it 'returns unauthorized' do + get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail") + + expect(response).to have_gitlab_http_status(401) + end + end + + it 'exposes known attributes' do + get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) + expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) + expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) + end + + it 'returns unauthorized for non-admin users' do + get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user) + + expect(response).to have_gitlab_http_status(403) + end + end + + describe 'GET projects/:id/issues/:issue_iid/participants' do + it_behaves_like 'issuable participants endpoint' do + let(:entity) { issue } + end + + it 'returns 404 if the issue is confidential' do + post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/participants", non_member) + + expect(response).to have_gitlab_http_status(404) + end + end +end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb new file mode 100644 index 00000000000..6f916212672 --- /dev/null +++ b/spec/requests/api/issues/issues_spec.rb @@ -0,0 +1,750 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Issues do + set(:user) { create(:user) } + set(:project) do + create(:project, :public, creator_id: user.id, namespace: user.namespace) + end + + let(:user2) { create(:user) } + let(:non_member) { create(:user) } + set(:guest) { create(:user) } + set(:author) { create(:author) } + set(:assignee) { create(:assignee) } + let(:admin) { create(:user, :admin) } + let(:issue_title) { 'foo' } + let(:issue_description) { 'closed' } + let!(:closed_issue) do + create :closed_issue, + author: user, + assignees: [user], + project: project, + state: :closed, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 3.hours.ago, + closed_at: 1.hour.ago + end + let!(:confidential_issue) do + create :issue, + :confidential, + project: project, + author: author, + assignees: [assignee], + created_at: generate(:past_time), + updated_at: 2.hours.ago + end + let!(:issue) do + create :issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + end + set(:label) do + create(:label, title: 'label', color: '#FFAABB', project: project) + end + let!(:label_link) { create(:label_link, label: label, target: issue) } + let(:milestone) { create(:milestone, title: '1.0.0', project: project) } + set(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end + let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } + + let(:no_milestone_title) { 'None' } + let(:any_milestone_title) { 'Any' } + + before(:all) do + project.add_reporter(user) + project.add_guest(guest) + end + + before do + stub_licensed_features(multiple_issue_assignees: false, issue_weights: false) + end + + shared_examples 'issues statistics' do + it 'returns issues statistics' do + get api("/issues_statistics", user), params: params + + expect(response).to have_gitlab_http_status(200) + expect(json_response['statistics']).not_to be_nil + expect(json_response['statistics']['counts']['all']).to eq counts[:all] + expect(json_response['statistics']['counts']['closed']).to eq counts[:closed] + expect(json_response['statistics']['counts']['opened']).to eq counts[:opened] + end + end + + describe 'GET /issues' do + context 'when unauthenticated' do + it 'returns an array of all issues' do + get api('/issues'), params: { scope: 'all' } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + end + + it 'returns authentication error without any scope' do + get api('/issues') + + expect(response).to have_http_status(401) + end + + it 'returns authentication error when scope is assigned-to-me' do + get api('/issues'), params: { scope: 'assigned-to-me' } + + expect(response).to have_http_status(401) + end + + it 'returns authentication error when scope is created-by-me' do + get api('/issues'), params: { scope: 'created-by-me' } + + expect(response).to have_http_status(401) + end + + it 'returns an array of issues matching state in milestone' do + get api('/issues'), params: { milestone: 'foo', scope: 'all' } + + expect(response).to have_http_status(200) + expect_paginated_array_response([]) + end + + it 'returns an array of issues matching state in milestone' do + get api('/issues'), params: { milestone: milestone.title, scope: 'all' } + + expect(response).to have_http_status(200) + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns authentication error without any scope' do + get api('/issues_statistics') + + expect(response).to have_http_status(401) + end + + it 'returns authentication error when scope is assigned-to-me' do + get api('/issues_statistics'), params: { scope: 'assigned-to-me' } + + expect(response).to have_http_status(401) + end + + it 'returns authentication error when scope is created-by-me' do + get api('/issues_statistics'), params: { scope: 'created-by-me' } + + expect(response).to have_http_status(401) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'sort does not affect statistics ' do + let(:params) { { state: :opened, order_by: 'updated_at' } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + end + end + + context 'when authenticated' do + it 'returns an array of issues' do + get api('/issues', user) + + expect_paginated_array_response([issue.id, closed_issue.id]) + expect(json_response.first['title']).to eq(issue.title) + expect(json_response.last).to have_key('web_url') + end + + it 'returns an array of closed issues' do + get api('/issues', user), params: { state: :closed } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of opened issues' do + get api('/issues', user), params: { state: :opened } + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of all issues' do + get api('/issues', user), params: { state: :all } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns issues assigned to me' do + issue2 = create(:issue, assignees: [user2], project: project) + + get api('/issues', user2), params: { scope: 'assigned_to_me' } + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues assigned to me (kebab-case)' do + issue2 = create(:issue, assignees: [user2], project: project) + + get api('/issues', user2), params: { scope: 'assigned-to-me' } + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues authored by the given author id' do + issue2 = create(:issue, author: user2, project: project) + + get api('/issues', user), params: { author_id: user2.id, scope: 'all' } + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues assigned to the given assignee id' do + issue2 = create(:issue, assignees: [user2], project: project) + + get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' } + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues authored by the given author id and assigned to the given assignee id' do + issue2 = create(:issue, author: user2, assignees: [user2], project: project) + + get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' } + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues with no assignee' do + issue2 = create(:issue, author: user2, project: project) + + get api('/issues', user), params: { assignee_id: 0, scope: 'all' } + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues with no assignee' do + issue2 = create(:issue, author: user2, project: project) + + get api('/issues', user), params: { assignee_id: 'None', scope: 'all' } + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues with any assignee' do + # This issue without assignee should not be returned + create(:issue, author: user2, project: project) + + get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' } + + expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + end + + it 'returns only confidential issues' do + get api('/issues', user), params: { confidential: true, scope: 'all' } + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns only public issues' do + get api('/issues', user), params: { confidential: false } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns issues reacted by the authenticated user' do + issue2 = create(:issue, project: project, author: user, assignees: [user]) + create(:award_emoji, awardable: issue2, user: user2, name: 'star') + create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup') + + get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' } + + expect_paginated_array_response([issue2.id, issue.id]) + end + + it 'returns issues not reacted by the authenticated user' do + issue2 = create(:issue, project: project, author: user, assignees: [user]) + create(:award_emoji, awardable: issue2, user: user2, name: 'star') + + get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns issues matching given search string for title' do + get api('/issues', user), params: { search: issue.title } + + expect_paginated_array_response(issue.id) + end + + it 'returns issues matching given search string for title and scoped in title' do + get api('/issues', user), params: { search: issue.title, in: 'title' } + + expect_paginated_array_response(issue.id) + end + + it 'returns an empty array if no issue matches given search string for title and scoped in description' do + get api('/issues', user), params: { search: issue.title, in: 'description' } + + expect_paginated_array_response([]) + end + + it 'returns issues matching given search string for description' do + get api('/issues', user), params: { search: issue.description } + + expect_paginated_array_response(issue.id) + end + + context 'filtering before a specific date' do + let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) } + + it 'returns issues created before a specific date' do + get api('/issues?created_before=2000-01-02T00:00:00.060Z', user) + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues updated before a specific date' do + get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user) + + expect_paginated_array_response(issue2.id) + end + end + + context 'filtering after a specific date' do + let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) } + + it 'returns issues created after a specific date' do + get api("/issues?created_after=#{issue2.created_at}", user) + + expect_paginated_array_response(issue2.id) + end + + it 'returns issues updated after a specific date' do + get api("/issues?updated_after=#{issue2.updated_at}", user) + + expect_paginated_array_response(issue2.id) + end + end + + context 'filter by labels or label_name param' do + it 'returns an array of labeled issues' do + get api('/issues', user), params: { labels: label.title } + + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label.title]) + end + + it 'returns an array of labeled issues with labels param as array' do + get api('/issues', user), params: { labels: [label.title] } + + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label.title]) + end + + context 'with labeled issues' do + let(:label_b) { create(:label, title: 'foo', project: project) } + let(:label_c) { create(:label, title: 'bar', project: project) } + + before do + create(:label_link, label: label_b, target: issue) + create(:label_link, label: label_c, target: issue) + + get api('/issues', user), params: params + end + + it_behaves_like 'labeled issues with labels and label_name params' + end + + it 'returns an empty array if no issue matches labels' do + get api('/issues', user), params: { labels: 'foo,bar' } + + expect_paginated_array_response([]) + end + + it 'returns an empty array if no issue matches labels with labels param as array' do + get api('/issues', user), params: { labels: %w(foo bar) } + + expect_paginated_array_response([]) + end + + it 'returns an array of labeled issues matching given state' do + get api('/issues', user), params: { labels: label.title, state: :opened } + + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label.title]) + expect(json_response.first['state']).to eq('opened') + end + + it 'returns an array of labeled issues matching given state with labels param as array' do + get api('/issues', user), params: { labels: [label.title], state: :opened } + + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label.title]) + expect(json_response.first['state']).to eq('opened') + end + + it 'returns an empty array if no issue matches labels and state filters' do + get api('/issues', user), params: { labels: label.title, state: :closed } + + expect_paginated_array_response([]) + end + + it 'returns an array of issues with any label' do + get api('/issues', user), params: { labels: IssuesFinder::FILTER_ANY } + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of issues with any label with labels param as array' do + get api('/issues', user), params: { labels: [IssuesFinder::FILTER_ANY] } + + expect_paginated_array_response(issue.id) + end + + it 'returns an array of issues with no label' do + get api('/issues', user), params: { labels: IssuesFinder::FILTER_NONE } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of issues with no label with labels param as array' do + get api('/issues', user), params: { labels: [IssuesFinder::FILTER_NONE] } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of issues with no label when using the legacy No+Label filter' do + get api('/issues', user), params: { labels: 'No Label' } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of issues with no label when using the legacy No+Label filter with labels param as array' do + get api('/issues', user), params: { labels: ['No Label'] } + + expect_paginated_array_response(closed_issue.id) + end + end + + it 'returns an empty array if no issue matches milestone' do + get api("/issues?milestone=#{empty_milestone.title}", user) + + expect_paginated_array_response([]) + end + + it 'returns an empty array if milestone does not exist' do + get api('/issues?milestone=foo', user) + + expect_paginated_array_response([]) + end + + it 'returns an array of issues in given milestone' do + get api("/issues?milestone=#{milestone.title}", user) + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns an array of issues in given milestone_title param' do + get api("/issues?milestone_title=#{milestone.title}", user) + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'returns an array of issues matching state in milestone' do + get api("/issues?milestone=#{milestone.title}&state=closed", user) + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an array of issues with no milestone' do + get api("/issues?milestone=#{no_milestone_title}", author) + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns an array of issues with no milestone using milestone_title param' do + get api("/issues?milestone_title=#{no_milestone_title}", author) + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns an array of issues found by iids' do + get api('/issues', user), params: { iids: [closed_issue.iid] } + + expect_paginated_array_response(closed_issue.id) + end + + it 'returns an empty array if iid does not exist' do + get api('/issues', user), params: { iids: [0] } + + expect_paginated_array_response([]) + end + + context 'without sort params' do + it 'sorts by created_at descending by default' do + get api('/issues', user) + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + context 'with 2 issues with same created_at' do + let!(:closed_issue2) do + create :closed_issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: closed_issue.created_at, + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + end + + it 'page breaks first page correctly' do + get api('/issues?per_page=2', user) + + expect_paginated_array_response([issue.id, closed_issue2.id]) + end + + it 'page breaks second page correctly' do + get api('/issues?per_page=2&page=2', user) + + expect_paginated_array_response([closed_issue.id]) + end + end + end + + it 'sorts ascending when requested' do + get api('/issues?sort=asc', user) + + expect_paginated_array_response([closed_issue.id, issue.id]) + end + + it 'sorts by updated_at descending when requested' do + get api('/issues?order_by=updated_at', user) + + issue.touch(:updated_at) + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + + it 'sorts by updated_at ascending when requested' do + get api('/issues?order_by=updated_at&sort=asc', user) + + issue.touch(:updated_at) + + expect_paginated_array_response([closed_issue.id, issue.id]) + end + + it 'matches V4 response schema' do + get api('/issues', user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/issues') + end + + it 'returns a related merge request count of 0 if there are no related merge requests' do + get api('/issues', user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/issues') + expect(json_response.first).to include('merge_requests_count' => 0) + end + + it 'returns a related merge request count > 0 if there are related merge requests' do + create(:merge_requests_closing_issues, issue: issue) + + get api('/issues', user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/issues') + expect(json_response.first).to include('merge_requests_count' => 1) + end + + context 'issues_statistics' do + context 'no state is treated as all state' do + let(:params) { {} } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'statistics when all state is passed' do + let(:params) { { state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'closed state is treated as all state' do + let(:params) { { state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'opened state is treated as all state' do + let(:params) { { state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and no state treated as all state' do + let(:params) { { milestone: milestone.title } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and all state' do + let(:params) { { milestone: milestone.title, state: :all } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and closed state treated as all state' do + let(:params) { { milestone: milestone.title, state: :closed } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'when filtering by milestone and opened state treated as all state' do + let(:params) { { milestone: milestone.title, state: :opened } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + + context 'sort does not affect statistics ' do + let(:params) { { state: :opened, order_by: 'updated_at' } } + let(:counts) { { all: 2, closed: 1, opened: 1 } } + + it_behaves_like 'issues statistics' + end + end + + context 'filtering by assignee_username' do + let(:another_assignee) { create(:assignee) } + let!(:issue1) { create(:issue, author: user2, project: project, created_at: 3.days.ago) } + let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } + let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } + + it 'returns issues with multiple assignees' do + get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } + + expect(issue3.reload.assignees).to eq([assignee, another_assignee]) + expect_paginated_array_response([confidential_issue.id, issue3.id]) + end + end + end + end + + describe 'DELETE /projects/:id/issues/:issue_iid' do + it 'rejects a non member from deleting an issue' do + delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member) + expect(response).to have_gitlab_http_status(403) + end + + it 'rejects a developer from deleting an issue' do + delete api("/projects/#{project.id}/issues/#{issue.iid}", author) + expect(response).to have_gitlab_http_status(403) + end + + context 'when the user is project owner' do + let(:owner) { create(:user) } + let(:project) { create(:project, namespace: owner.namespace) } + + it 'deletes the issue if an admin requests it' do + delete api("/projects/#{project.id}/issues/#{issue.iid}", owner) + + expect(response).to have_gitlab_http_status(204) + end + + it_behaves_like '412 response' do + let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) } + end + end + + context 'when issue does not exist' do + it 'returns 404 when trying to move an issue' do + delete api("/projects/#{project.id}/issues/123", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + it 'returns 404 when using the issue ID instead of IID' do + delete api("/projects/#{project.id}/issues/#{issue.id}", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe 'time tracking endpoints' do + let(:issuable) { issue } + + include_examples 'time tracking endpoints', 'issue' + end +end diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb new file mode 100644 index 00000000000..b74e8867310 --- /dev/null +++ b/spec/requests/api/issues/post_projects_issues_spec.rb @@ -0,0 +1,549 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Issues do + set(:user) { create(:user) } + set(:project) do + create(:project, :public, creator_id: user.id, namespace: user.namespace) + end + + let(:user2) { create(:user) } + let(:non_member) { create(:user) } + set(:guest) { create(:user) } + set(:author) { create(:author) } + set(:assignee) { create(:assignee) } + let(:admin) { create(:user, :admin) } + let(:issue_title) { 'foo' } + let(:issue_description) { 'closed' } + let!(:closed_issue) do + create :closed_issue, + author: user, + assignees: [user], + project: project, + state: :closed, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 3.hours.ago, + closed_at: 1.hour.ago + end + let!(:confidential_issue) do + create :issue, + :confidential, + project: project, + author: author, + assignees: [assignee], + created_at: generate(:past_time), + updated_at: 2.hours.ago + end + let!(:issue) do + create :issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + end + set(:label) do + create(:label, title: 'label', color: '#FFAABB', project: project) + end + let!(:label_link) { create(:label_link, label: label, target: issue) } + let(:milestone) { create(:milestone, title: '1.0.0', project: project) } + set(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end + let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } + + let(:no_milestone_title) { 'None' } + let(:any_milestone_title) { 'Any' } + + before(:all) do + project.add_reporter(user) + project.add_guest(guest) + end + + before do + stub_licensed_features(multiple_issue_assignees: false, issue_weights: false) + end + + describe 'POST /projects/:id/issues' do + context 'support for deprecated assignee_id' do + it 'creates a new project issue' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', assignee_id: user2.id } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['assignee']['name']).to eq(user2.name) + expect(json_response['assignees'].first['name']).to eq(user2.name) + end + + it 'creates a new project issue when assignee_id is empty' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', assignee_id: '' } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['assignee']).to be_nil + end + end + + context 'single assignee restrictions' do + it 'creates a new project issue with no more than one assignee' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', assignee_ids: [user2.id, guest.id] } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['assignees'].count).to eq(1) + end + end + + context 'user does not have permissions to create issue' do + let(:not_member) { create(:user) } + + before do + project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE) + end + + it 'renders 403' do + post api("/projects/#{project.id}/issues", not_member), params: { title: 'new issue' } + + expect(response).to have_gitlab_http_status(403) + end + end + + context 'an internal ID is provided' do + context 'by an admin' do + it 'sets the internal ID on the new issue' do + post api("/projects/#{project.id}/issues", admin), + params: { title: 'new issue', iid: 9001 } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['iid']).to eq 9001 + end + end + + context 'by an owner' do + it 'sets the internal ID on the new issue' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', iid: 9001 } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['iid']).to eq 9001 + end + end + + context 'by a group owner' do + let(:group) { create(:group) } + let(:group_project) { create(:project, :public, namespace: group) } + + it 'sets the internal ID on the new issue' do + group.add_owner(user2) + post api("/projects/#{group_project.id}/issues", user2), + params: { title: 'new issue', iid: 9001 } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['iid']).to eq 9001 + end + end + + context 'by another user' do + it 'ignores the given internal ID' do + post api("/projects/#{project.id}/issues", user2), + params: { title: 'new issue', iid: 9001 } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['iid']).not_to eq 9001 + end + end + end + + it 'creates a new project issue' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['description']).to be_nil + expect(json_response['labels']).to eq(%w(label label2)) + expect(json_response['confidential']).to be_falsy + expect(json_response['assignee']['name']).to eq(user2.name) + expect(json_response['assignees'].first['name']).to eq(user2.name) + end + + it 'creates a new project issue with labels param as array' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['description']).to be_nil + expect(json_response['labels']).to eq(%w(label label2)) + expect(json_response['confidential']).to be_falsy + expect(json_response['assignee']['name']).to eq(user2.name) + expect(json_response['assignees'].first['name']).to eq(user2.name) + end + + it 'creates a new confidential project issue' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', confidential: true } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['confidential']).to be_truthy + end + + it 'creates a new confidential project issue with a different param' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', confidential: 'y' } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['confidential']).to be_truthy + end + + it 'creates a public issue when confidential param is false' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', confidential: false } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['confidential']).to be_falsy + end + + it 'creates a public issue when confidential param is invalid' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', confidential: 'foo' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('confidential is invalid') + end + + it 'returns a 400 bad request if title not given' do + post api("/projects/#{project.id}/issues", user), params: { labels: 'label, label2' } + expect(response).to have_gitlab_http_status(400) + end + + it 'allows special label names' do + post api("/projects/#{project.id}/issues", user), + params: { + title: 'new issue', + labels: 'label, label?, label&foo, ?, &' + } + expect(response.status).to eq(201) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + + it 'allows special label names with labels param as array' do + post api("/projects/#{project.id}/issues", user), + params: { + title: 'new issue', + labels: ['label', 'label?', 'label&foo, ?, &'] + } + expect(response.status).to eq(201) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + + it 'returns 400 if title is too long' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'g' * 256 } + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']['title']).to eq([ + 'is too long (maximum is 255 characters)' + ]) + end + + context 'resolving discussions' do + let(:discussion) { create(:diff_note_on_merge_request).to_discussion } + let(:merge_request) { discussion.noteable } + let(:project) { merge_request.source_project } + + before do + project.add_maintainer(user) + end + + context 'resolving all discussions in a merge request' do + before do + post api("/projects/#{project.id}/issues", user), + params: { + title: 'New Issue', + merge_request_to_resolve_discussions_of: merge_request.iid + } + end + + it_behaves_like 'creating an issue resolving discussions through the API' + end + + context 'resolving a single discussion' do + before do + post api("/projects/#{project.id}/issues", user), + params: { + title: 'New Issue', + merge_request_to_resolve_discussions_of: merge_request.iid, + discussion_to_resolve: discussion.id + } + end + + it_behaves_like 'creating an issue resolving discussions through the API' + end + end + + context 'with due date' do + it 'creates a new project issue' do + due_date = 2.weeks.from_now.strftime('%Y-%m-%d') + + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', due_date: due_date } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['description']).to be_nil + expect(json_response['due_date']).to eq(due_date) + end + end + + context 'setting created_at' do + let(:creation_time) { 2.weeks.ago } + let(:params) { { title: 'new issue', labels: 'label, label2', created_at: creation_time } } + + context 'by an admin' do + it 'sets the creation time on the new issue' do + post api("/projects/#{project.id}/issues", admin), params: params + + expect(response).to have_gitlab_http_status(201) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + end + end + + context 'by a project owner' do + it 'sets the creation time on the new issue' do + post api("/projects/#{project.id}/issues", user), params: params + + expect(response).to have_gitlab_http_status(201) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + end + end + + context 'by a group owner' do + it 'sets the creation time on the new issue' do + group = create(:group) + group_project = create(:project, :public, namespace: group) + group.add_owner(user2) + post api("/projects/#{group_project.id}/issues", user2), params: params + + expect(response).to have_gitlab_http_status(201) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + end + end + + context 'by another user' do + it 'ignores the given creation time' do + post api("/projects/#{project.id}/issues", user2), params: params + + expect(response).to have_gitlab_http_status(201) + expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time) + end + end + end + + context 'the user can only read the issue' do + it 'cannot create new labels' do + expect do + post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: 'label, label2' } + end.not_to change { project.labels.count } + end + + it 'cannot create new labels with labels param as array' do + expect do + post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: %w(label label2) } + end.not_to change { project.labels.count } + end + end + end + + describe 'POST /projects/:id/issues with spam filtering' do + before do + allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true) + allow_any_instance_of(AkismetService).to receive_messages(spam?: true) + end + + let(:params) do + { + title: 'new issue', + description: 'content here', + labels: 'label, label2' + } + end + + it 'does not create a new project issue' do + expect { post api("/projects/#{project.id}/issues", user), params: params }.not_to change(Issue, :count) + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq({ 'error' => 'Spam detected' }) + + spam_logs = SpamLog.all + expect(spam_logs.count).to eq(1) + expect(spam_logs[0].title).to eq('new issue') + expect(spam_logs[0].description).to eq('content here') + expect(spam_logs[0].user).to eq(user) + expect(spam_logs[0].noteable_type).to eq('Issue') + end + end + + describe '/projects/:id/issues/:issue_iid/move' do + let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) } + let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } + + it 'moves an issue' do + post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), + params: { to_project_id: target_project.id } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['project_id']).to eq(target_project.id) + end + + context 'when source and target projects are the same' do + it 'returns 400 when trying to move an issue' do + post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), + params: { to_project_id: project.id } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('Cannot move issue to project it originates from!') + end + end + + context 'when the user does not have the permission to move issues' do + it 'returns 400 when trying to move an issue' do + post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), + params: { to_project_id: target_project2.id } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') + end + end + + it 'moves the issue to another namespace if I am admin' do + post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin), + params: { to_project_id: target_project2.id } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['project_id']).to eq(target_project2.id) + end + + context 'when using the issue ID instead of iid' do + it 'returns 404 when trying to move an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/move", user), + params: { to_project_id: target_project.id } + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Issue Not Found') + end + end + + context 'when issue does not exist' do + it 'returns 404 when trying to move an issue' do + post api("/projects/#{project.id}/issues/123/move", user), + params: { to_project_id: target_project.id } + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Issue Not Found') + end + end + + context 'when source project does not exist' do + it 'returns 404 when trying to move an issue' do + post api("/projects/0/issues/#{issue.iid}/move", user), + params: { to_project_id: target_project.id } + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + end + + context 'when target project does not exist' do + it 'returns 404 when trying to move an issue' do + post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), + params: { to_project_id: 0 } + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe 'POST :id/issues/:issue_iid/subscribe' do + it 'subscribes to an issue' do + post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['subscribed']).to eq(true) + end + + it 'returns 304 if already subscribed' do + post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user) + + expect(response).to have_gitlab_http_status(304) + end + + it 'returns 404 if the issue is not found' do + post api("/projects/#{project.id}/issues/123/subscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 if the issue ID is used instead of the iid' do + post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 if the issue is confidential' do + post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe 'POST :id/issues/:issue_id/unsubscribe' do + it 'unsubscribes from an issue' do + post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['subscribed']).to eq(false) + end + + it 'returns 304 if not subscribed' do + post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user2) + + expect(response).to have_gitlab_http_status(304) + end + + it 'returns 404 if the issue is not found' do + post api("/projects/#{project.id}/issues/123/unsubscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 if using the issue ID instead of iid' do + post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 if the issue is confidential' do + post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member) + + expect(response).to have_gitlab_http_status(404) + end + end +end diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb new file mode 100644 index 00000000000..267cba93713 --- /dev/null +++ b/spec/requests/api/issues/put_projects_issues_spec.rb @@ -0,0 +1,392 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Issues do + set(:user) { create(:user) } + set(:project) do + create(:project, :public, creator_id: user.id, namespace: user.namespace) + end + + let(:user2) { create(:user) } + let(:non_member) { create(:user) } + set(:guest) { create(:user) } + set(:author) { create(:author) } + set(:assignee) { create(:assignee) } + let(:admin) { create(:user, :admin) } + let(:issue_title) { 'foo' } + let(:issue_description) { 'closed' } + let!(:closed_issue) do + create :closed_issue, + author: user, + assignees: [user], + project: project, + state: :closed, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 3.hours.ago, + closed_at: 1.hour.ago + end + let!(:confidential_issue) do + create :issue, + :confidential, + project: project, + author: author, + assignees: [assignee], + created_at: generate(:past_time), + updated_at: 2.hours.ago + end + let!(:issue) do + create :issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + end + set(:label) do + create(:label, title: 'label', color: '#FFAABB', project: project) + end + let!(:label_link) { create(:label_link, label: label, target: issue) } + let(:milestone) { create(:milestone, title: '1.0.0', project: project) } + set(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end + let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } + + let(:no_milestone_title) { 'None' } + let(:any_milestone_title) { 'Any' } + + before(:all) do + project.add_reporter(user) + project.add_guest(guest) + end + + before do + stub_licensed_features(multiple_issue_assignees: false, issue_weights: false) + end + + describe 'PUT /projects/:id/issues/:issue_iid to update only title' do + it 'updates a project issue' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(200) + + expect(json_response['title']).to eq('updated title') + end + + it 'returns 404 error if issue iid not found' do + put api("/projects/#{project.id}/issues/44444", user), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 error if issue id is used instead of the iid' do + put api("/projects/#{project.id}/issues/#{issue.id}", user), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(404) + end + + it 'allows special label names' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { + title: 'updated title', + labels: 'label, label?, label&foo, ?, &' + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + + it 'allows special label names with labels param as array' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { + title: 'updated title', + labels: ['label', 'label?', 'label&foo, ?, &'] + } + + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + + context 'confidential issues' do + it 'returns 403 for non project members' do + put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(403) + end + + it 'returns 403 for project members with guest role' do + put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(403) + end + + it 'updates a confidential issue for project members' do + put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq('updated title') + end + + it 'updates a confidential issue for author' do + put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq('updated title') + end + + it 'updates a confidential issue for admin' do + put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq('updated title') + end + + it 'sets an issue to confidential' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { confidential: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['confidential']).to be_truthy + end + + it 'makes a confidential issue public' do + put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), + params: { confidential: false } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['confidential']).to be_falsy + end + + it 'does not update a confidential issue with wrong confidential flag' do + put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), + params: { confidential: 'foo' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('confidential is invalid') + end + end + end + + describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do + let(:params) do + { + title: 'updated title', + description: 'content here', + labels: 'label, label2' + } + end + + it 'does not create a new project issue' do + allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true) + allow_any_instance_of(AkismetService).to receive_messages(spam?: true) + + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: params + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq({ 'error' => 'Spam detected' }) + + spam_logs = SpamLog.all + expect(spam_logs.count).to eq(1) + expect(spam_logs[0].title).to eq('updated title') + expect(spam_logs[0].description).to eq('content here') + expect(spam_logs[0].user).to eq(user) + expect(spam_logs[0].noteable_type).to eq('Issue') + end + end + + describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do + context 'support for deprecated assignee_id' do + it 'removes assignee' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { assignee_id: 0 } + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['assignee']).to be_nil + end + + it 'updates an issue with new assignee' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { assignee_id: user2.id } + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['assignee']['name']).to eq(user2.name) + end + end + + it 'removes assignee' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { assignee_ids: [0] } + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['assignees']).to be_empty + end + + it 'updates an issue with new assignee' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { assignee_ids: [user2.id] } + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['assignees'].first['name']).to eq(user2.name) + end + + context 'single assignee restrictions' do + it 'updates an issue with several assignees but only one has been applied' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { assignee_ids: [user2.id, guest.id] } + + expect(response).to have_gitlab_http_status(200) + + expect(json_response['assignees'].size).to eq(1) + end + end + end + + describe 'PUT /projects/:id/issues/:issue_iid to update labels' do + let!(:label) { create(:label, title: 'dummy', project: project) } + let!(:label_link) { create(:label_link, label: label, target: issue) } + + it 'does not update labels if not present' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { title: 'updated title' } + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to eq([label.title]) + end + + it 'removes all labels and touches the record' do + Timecop.travel(1.minute.from_now) do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: '' } + end + + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to eq([]) + expect(json_response['updated_at']).to be > Time.now + end + + it 'removes all labels and touches the record with labels param as array' do + Timecop.travel(1.minute.from_now) do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: [''] } + end + + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to eq([]) + expect(json_response['updated_at']).to be > Time.now + end + + it 'updates labels and touches the record' do + Timecop.travel(1.minute.from_now) do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: 'foo,bar' } + end + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to include 'foo' + expect(json_response['labels']).to include 'bar' + expect(json_response['updated_at']).to be > Time.now + end + + it 'updates labels and touches the record with labels param as array' do + Timecop.travel(1.minute.from_now) do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: %w(foo bar) } + end + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to include 'foo' + expect(json_response['labels']).to include 'bar' + expect(json_response['updated_at']).to be > Time.now + end + + it 'allows special label names' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' } + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label:foo' + expect(json_response['labels']).to include 'label-bar' + expect(json_response['labels']).to include 'label_bar' + expect(json_response['labels']).to include 'label/bar' + expect(json_response['labels']).to include 'label?bar' + expect(json_response['labels']).to include 'label&bar' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + + it 'allows special label names with labels param as array' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] } + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label:foo' + expect(json_response['labels']).to include 'label-bar' + expect(json_response['labels']).to include 'label_bar' + expect(json_response['labels']).to include 'label/bar' + expect(json_response['labels']).to include 'label?bar' + expect(json_response['labels']).to include 'label&bar' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' + end + + it 'returns 400 if title is too long' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { title: 'g' * 256 } + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']['title']).to eq([ + 'is too long (maximum is 255 characters)' + ]) + end + end + + describe 'PUT /projects/:id/issues/:issue_iid to update state and label' do + it 'updates a project issue' do + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: 'label2', state_event: 'close' } + expect(response).to have_gitlab_http_status(200) + + expect(json_response['labels']).to include 'label2' + expect(json_response['state']).to eq 'closed' + end + + it 'reopens a project isssue' do + put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), params: { state_event: 'reopen' } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['state']).to eq 'opened' + end + + context 'when an admin or owner makes the request' do + it 'accepts the update date to be set' do + update_time = 2.weeks.ago + put api("/projects/#{project.id}/issues/#{issue.iid}", user), + params: { labels: 'label3', state_event: 'close', updated_at: update_time } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['labels']).to include 'label3' + expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) + end + end + end + + describe 'PUT /projects/:id/issues/:issue_iid to update due date' do + it 'creates a new project issue' do + due_date = 2.weeks.from_now.strftime('%Y-%m-%d') + + put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { due_date: due_date } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['due_date']).to eq(due_date) + end + end +end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb deleted file mode 100644 index 0fa34688371..00000000000 --- a/spec/requests/api/issues_spec.rb +++ /dev/null @@ -1,2265 +0,0 @@ -require 'spec_helper' - -describe API::Issues do - set(:user) { create(:user) } - set(:project) do - create(:project, :public, creator_id: user.id, namespace: user.namespace) - end - - let(:user2) { create(:user) } - let(:non_member) { create(:user) } - set(:guest) { create(:user) } - set(:author) { create(:author) } - set(:assignee) { create(:assignee) } - let(:admin) { create(:user, :admin) } - let(:issue_title) { 'foo' } - let(:issue_description) { 'closed' } - let!(:closed_issue) do - create :closed_issue, - author: user, - assignees: [user], - project: project, - state: :closed, - milestone: milestone, - created_at: generate(:past_time), - updated_at: 3.hours.ago, - closed_at: 1.hour.ago - end - let!(:confidential_issue) do - create :issue, - :confidential, - project: project, - author: author, - assignees: [assignee], - created_at: generate(:past_time), - updated_at: 2.hours.ago - end - let!(:issue) do - create :issue, - author: user, - assignees: [user], - project: project, - milestone: milestone, - created_at: generate(:past_time), - updated_at: 1.hour.ago, - title: issue_title, - description: issue_description - end - set(:label) do - create(:label, title: 'label', color: '#FFAABB', project: project) - end - let!(:label_link) { create(:label_link, label: label, target: issue) } - let(:milestone) { create(:milestone, title: '1.0.0', project: project) } - set(:empty_milestone) do - create(:milestone, title: '2.0.0', project: project) - end - let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } - - let(:no_milestone_title) { "None" } - let(:any_milestone_title) { "Any" } - - before(:all) do - project.add_reporter(user) - project.add_guest(guest) - end - - describe "GET /issues" do - context "when unauthenticated" do - it "returns an array of all issues" do - get api("/issues"), params: { scope: 'all' } - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - end - - it "returns authentication error without any scope" do - get api("/issues") - - expect(response).to have_http_status(401) - end - - it "returns authentication error when scope is assigned-to-me" do - get api("/issues"), params: { scope: 'assigned-to-me' } - - expect(response).to have_http_status(401) - end - - it "returns authentication error when scope is created-by-me" do - get api("/issues"), params: { scope: 'created-by-me' } - - expect(response).to have_http_status(401) - end - end - - context "when authenticated" do - it "returns an array of issues" do - get api("/issues", user) - - expect_paginated_array_response([issue.id, closed_issue.id]) - expect(json_response.first['title']).to eq(issue.title) - expect(json_response.last).to have_key('web_url') - end - - it 'returns an array of closed issues' do - get api('/issues', user), params: { state: :closed } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an array of opened issues' do - get api('/issues', user), params: { state: :opened } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of all issues' do - get api('/issues', user), params: { state: :all } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns issues assigned to me' do - issue2 = create(:issue, assignees: [user2], project: project) - - get api('/issues', user2), params: { scope: 'assigned_to_me' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues assigned to me (kebab-case)' do - issue2 = create(:issue, assignees: [user2], project: project) - - get api('/issues', user2), params: { scope: 'assigned-to-me' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues authored by the given author id' do - issue2 = create(:issue, author: user2, project: project) - - get api('/issues', user), params: { author_id: user2.id, scope: 'all' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues assigned to the given assignee id' do - issue2 = create(:issue, assignees: [user2], project: project) - - get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues authored by the given author id and assigned to the given assignee id' do - issue2 = create(:issue, author: user2, assignees: [user2], project: project) - - get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues with no assignee' do - issue2 = create(:issue, author: user2, project: project) - - get api('/issues', user), params: { assignee_id: 0, scope: 'all' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues with no assignee' do - issue2 = create(:issue, author: user2, project: project) - - get api('/issues', user), params: { assignee_id: 'None', scope: 'all' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues with any assignee' do - # This issue without assignee should not be returned - create(:issue, author: user2, project: project) - - get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' } - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) - end - - it 'returns only confidential issues' do - get api('/issues', user), params: { confidential: true, scope: 'all' } - - expect_paginated_array_response(confidential_issue.id) - end - - it 'returns only public issues' do - get api('/issues', user), params: { confidential: false } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns issues reacted by the authenticated user' do - issue2 = create(:issue, project: project, author: user, assignees: [user]) - create(:award_emoji, awardable: issue2, user: user2, name: 'star') - create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup') - - get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' } - - expect_paginated_array_response([issue2.id, issue.id]) - end - - it 'returns issues not reacted by the authenticated user' do - issue2 = create(:issue, project: project, author: user, assignees: [user]) - create(:award_emoji, awardable: issue2, user: user2, name: 'star') - - get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns issues matching given search string for title' do - get api("/issues", user), params: { search: issue.title } - - expect_paginated_array_response(issue.id) - end - - it 'returns issues matching given search string for title and scoped in title' do - get api("/issues", user), params: { search: issue.title, in: 'title' } - - expect_paginated_array_response(issue.id) - end - - it 'returns an empty array if no issue matches given search string for title and scoped in description' do - get api("/issues", user), params: { search: issue.title, in: 'description' } - - expect_paginated_array_response([]) - end - - it 'returns issues matching given search string for description' do - get api("/issues", user), params: { search: issue.description } - - expect_paginated_array_response(issue.id) - end - - context 'filtering before a specific date' do - let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) } - - it 'returns issues created before a specific date' do - get api('/issues?created_before=2000-01-02T00:00:00.060Z', user) - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues updated before a specific date' do - get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user) - - expect_paginated_array_response(issue2.id) - end - end - - context 'filtering after a specific date' do - let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) } - - it 'returns issues created after a specific date' do - get api("/issues?created_after=#{issue2.created_at}", user) - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues updated after a specific date' do - get api("/issues?updated_after=#{issue2.updated_at}", user) - - expect_paginated_array_response(issue2.id) - end - end - - it 'returns an array of labeled issues' do - get api('/issues', user), params: { labels: label.title } - - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label.title]) - end - - it 'returns an array of labeled issues with labels param as array' do - get api('/issues', user), params: { labels: [label.title] } - - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label.title]) - end - - it 'returns an array of labeled issues when all labels matches' do - label_b = create(:label, title: 'foo', project: project) - label_c = create(:label, title: 'bar', project: project) - - create(:label_link, label: label_b, target: issue) - create(:label_link, label: label_c, target: issue) - - get api('/issues', user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } - - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) - end - - it 'returns an array of labeled issues when all labels matches with labels param as array' do - label_b = create(:label, title: 'foo', project: project) - label_c = create(:label, title: 'bar', project: project) - - create(:label_link, label: label_b, target: issue) - create(:label_link, label: label_c, target: issue) - - get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] } - - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) - end - - it 'returns an empty array if no issue matches labels' do - get api('/issues', user), params: { labels: 'foo,bar' } - - expect_paginated_array_response([]) - end - - it 'returns an empty array if no issue matches labels with labels param as array' do - get api('/issues', user), params: { labels: %w(foo bar) } - - expect_paginated_array_response([]) - end - - it 'returns an array of labeled issues matching given state' do - get api('/issues', user), params: { labels: label.title, state: :opened } - - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label.title]) - expect(json_response.first['state']).to eq('opened') - end - - it 'returns an array of labeled issues matching given state with labels param as array' do - get api('/issues', user), params: { labels: [label.title], state: :opened } - - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label.title]) - expect(json_response.first['state']).to eq('opened') - end - - it 'returns an empty array if no issue matches labels and state filters' do - get api('/issues', user), params: { labels: label.title, state: :closed } - - expect_paginated_array_response([]) - end - - it 'returns an array of issues with any label' do - get api('/issues', user), params: { labels: IssuesFinder::FILTER_ANY } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of issues with any label with labels param as array' do - get api('/issues', user), params: { labels: [IssuesFinder::FILTER_ANY] } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of issues with no label' do - get api('/issues', user), params: { labels: IssuesFinder::FILTER_NONE } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an array of issues with no label with labels param as array' do - get api('/issues', user), params: { labels: [IssuesFinder::FILTER_NONE] } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an array of issues with no label when using the legacy No+Label filter' do - get api('/issues', user), params: { labels: 'No Label' } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an array of issues with no label when using the legacy No+Label filter with labels param as array' do - get api('/issues', user), params: { labels: ['No Label'] } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an empty array if no issue matches milestone' do - get api("/issues?milestone=#{empty_milestone.title}", user) - - expect_paginated_array_response([]) - end - - it 'returns an empty array if milestone does not exist' do - get api("/issues?milestone=foo", user) - - expect_paginated_array_response([]) - end - - it 'returns an array of issues in given milestone' do - get api("/issues?milestone=#{milestone.title}", user) - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns an array of issues matching state in milestone' do - get api("/issues?milestone=#{milestone.title}"\ - '&state=closed', user) - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an array of issues with no milestone' do - get api("/issues?milestone=#{no_milestone_title}", author) - - expect_paginated_array_response(confidential_issue.id) - end - - it 'returns an array of issues found by iids' do - get api('/issues', user), params: { iids: [closed_issue.iid] } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an empty array if iid does not exist' do - get api("/issues", user), params: { iids: [0] } - - expect_paginated_array_response([]) - end - - context 'without sort params' do - it 'sorts by created_at descending by default' do - get api('/issues', user) - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - context 'with 2 issues with same created_at' do - let!(:closed_issue2) do - create :closed_issue, - author: user, - assignees: [user], - project: project, - milestone: milestone, - created_at: closed_issue.created_at, - updated_at: 1.hour.ago, - title: issue_title, - description: issue_description - end - - it 'page breaks first page correctly' do - get api('/issues?per_page=2', user) - - expect_paginated_array_response([issue.id, closed_issue2.id]) - end - - it 'page breaks second page correctly' do - get api('/issues?per_page=2&page=2', user) - - expect_paginated_array_response([closed_issue.id]) - end - end - end - - it 'sorts ascending when requested' do - get api('/issues?sort=asc', user) - - expect_paginated_array_response([closed_issue.id, issue.id]) - end - - it 'sorts by updated_at descending when requested' do - get api('/issues?order_by=updated_at', user) - - issue.touch(:updated_at) - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'sorts by updated_at ascending when requested' do - get api('/issues?order_by=updated_at&sort=asc', user) - - issue.touch(:updated_at) - - expect_paginated_array_response([closed_issue.id, issue.id]) - end - - it 'matches V4 response schema' do - get api('/issues', user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/issues') - end - - it 'returns a related merge request count of 0 if there are no related merge requests' do - get api('/issues', user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/issues') - expect(json_response.first).to include('merge_requests_count' => 0) - end - - it 'returns a related merge request count > 0 if there are related merge requests' do - create(:merge_requests_closing_issues, issue: issue) - - get api('/issues', user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/issues') - expect(json_response.first).to include('merge_requests_count' => 1) - end - end - end - - describe "GET /groups/:id/issues" do - let!(:group) { create(:group) } - let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } - let!(:group_closed_issue) do - create :closed_issue, - author: user, - assignees: [user], - project: group_project, - state: :closed, - milestone: group_milestone, - updated_at: 3.hours.ago, - created_at: 1.day.ago - end - let!(:group_confidential_issue) do - create :issue, - :confidential, - project: group_project, - author: author, - assignees: [assignee], - updated_at: 2.hours.ago, - created_at: 2.days.ago - end - let!(:group_issue) do - create :issue, - author: user, - assignees: [user], - project: group_project, - milestone: group_milestone, - updated_at: 1.hour.ago, - title: issue_title, - description: issue_description, - created_at: 5.days.ago - end - let!(:group_label) do - create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) - end - let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) } - let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) } - let!(:group_empty_milestone) do - create(:milestone, title: '4.0.0', project: group_project) - end - let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) } - - let(:base_url) { "/groups/#{group.id}/issues" } - - context 'when group has subgroups', :nested_groups do - let(:subgroup_1) { create(:group, parent: group) } - let(:subgroup_2) { create(:group, parent: subgroup_1) } - - let(:subgroup_1_project) { create(:project, namespace: subgroup_1) } - let(:subgroup_2_project) { create(:project, namespace: subgroup_2) } - - let!(:issue_1) { create(:issue, project: subgroup_1_project) } - let!(:issue_2) { create(:issue, project: subgroup_2_project) } - - before do - group.add_developer(user) - end - - it 'also returns subgroups projects issues' do - get api(base_url, user) - - expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id]) - end - end - - context 'when user is unauthenticated' do - it 'lists all issues in public projects' do - get api(base_url) - - expect_paginated_array_response([group_closed_issue.id, group_issue.id]) - end - end - - context 'when user is a group member' do - before do - group_project.add_reporter(user) - end - - it 'returns all group issues (including opened and closed)' do - get api(base_url, admin) - - expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) - end - - it 'returns group issues without confidential issues for non project members' do - get api(base_url, non_member), params: { state: :opened } - - expect_paginated_array_response(group_issue.id) - end - - it 'returns group confidential issues for author' do - get api(base_url, author), params: { state: :opened } - - expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) - end - - it 'returns group confidential issues for assignee' do - get api(base_url, assignee), params: { state: :opened } - - expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) - end - - it 'returns group issues with confidential issues for project members' do - get api(base_url, user), params: { state: :opened } - - expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) - end - - it 'returns group confidential issues for admin' do - get api(base_url, admin), params: { state: :opened } - - expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) - end - - it 'returns only confidential issues' do - get api(base_url, user), params: { confidential: true } - - expect_paginated_array_response(group_confidential_issue.id) - end - - it 'returns only public issues' do - get api(base_url, user), params: { confidential: false } - - expect_paginated_array_response([group_closed_issue.id, group_issue.id]) - end - - it 'returns an array of labeled group issues' do - get api(base_url, user), params: { labels: group_label.title } - - expect_paginated_array_response(group_issue.id) - expect(json_response.first['labels']).to eq([group_label.title]) - end - - it 'returns an array of labeled group issues with labels param as array' do - get api(base_url, user), params: { labels: [group_label.title] } - - expect_paginated_array_response(group_issue.id) - expect(json_response.first['labels']).to eq([group_label.title]) - end - - it 'returns an array of labeled group issues where all labels match' do - get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" } - - expect_paginated_array_response([]) - end - - it 'returns an array of labeled group issues where all labels match with labels param as array' do - get api(base_url, user), params: { labels: [group_label.title, 'foo', 'bar'] } - - expect_paginated_array_response([]) - end - - it 'returns issues matching given search string for title' do - get api(base_url, user), params: { search: group_issue.title } - - expect_paginated_array_response(group_issue.id) - end - - it 'returns issues matching given search string for description' do - get api(base_url, user), params: { search: group_issue.description } - - expect_paginated_array_response(group_issue.id) - end - - it 'returns an array of labeled issues when all labels matches' do - label_b = create(:label, title: 'foo', project: group_project) - label_c = create(:label, title: 'bar', project: group_project) - - create(:label_link, label: label_b, target: group_issue) - create(:label_link, label: label_c, target: group_issue) - - get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" } - - expect_paginated_array_response(group_issue.id) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) - end - - it 'returns an array of labeled issues when all labels matches with labels param as array' do - label_b = create(:label, title: 'foo', project: group_project) - label_c = create(:label, title: 'bar', project: group_project) - - create(:label_link, label: label_b, target: group_issue) - create(:label_link, label: label_c, target: group_issue) - - get api(base_url, user), params: { labels: [group_label.title, label_b.title, label_c.title] } - - expect_paginated_array_response(group_issue.id) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) - end - - it 'returns an array of issues found by iids' do - get api(base_url, user), params: { iids: [group_issue.iid] } - - expect_paginated_array_response(group_issue.id) - expect(json_response.first['id']).to eq(group_issue.id) - end - - it 'returns an empty array if iid does not exist' do - get api(base_url, user), params: { iids: [0] } - - expect_paginated_array_response([]) - end - - it 'returns an empty array if no group issue matches labels' do - get api(base_url, user), params: { labels: 'foo,bar' } - - expect_paginated_array_response([]) - end - - it 'returns an array of group issues with any label' do - get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY } - - expect_paginated_array_response(group_issue.id) - expect(json_response.first['id']).to eq(group_issue.id) - end - - it 'returns an array of group issues with any label with labels param as array' do - get api(base_url, user), params: { labels: [IssuesFinder::FILTER_ANY] } - - expect_paginated_array_response(group_issue.id) - expect(json_response.first['id']).to eq(group_issue.id) - end - - it 'returns an array of group issues with no label' do - get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE } - - expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id]) - end - - it 'returns an array of group issues with no label with labels param as array' do - get api(base_url, user), params: { labels: [IssuesFinder::FILTER_NONE] } - - expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id]) - end - - it 'returns an empty array if no issue matches milestone' do - get api(base_url, user), params: { milestone: group_empty_milestone.title } - - expect_paginated_array_response([]) - end - - it 'returns an empty array if milestone does not exist' do - get api(base_url, user), params: { milestone: 'foo' } - - expect_paginated_array_response([]) - end - - it 'returns an array of issues in given milestone' do - get api(base_url, user), params: { state: :opened, milestone: group_milestone.title } - - expect_paginated_array_response(group_issue.id) - end - - it 'returns an array of issues matching state in milestone' do - get api(base_url, user), params: { milestone: group_milestone.title, state: :closed } - - expect_paginated_array_response(group_closed_issue.id) - end - - it 'returns an array of issues with no milestone' do - get api(base_url, user), params: { milestone: no_milestone_title } - - expect(response).to have_gitlab_http_status(200) - - expect_paginated_array_response(group_confidential_issue.id) - end - - context 'without sort params' do - it 'sorts by created_at descending by default' do - get api(base_url, user) - - expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) - end - - context 'with 2 issues with same created_at' do - let!(:group_issue2) do - create :issue, - author: user, - assignees: [user], - project: group_project, - milestone: group_milestone, - updated_at: 1.hour.ago, - title: issue_title, - description: issue_description, - created_at: group_issue.created_at - end - - it 'page breaks first page correctly' do - get api("#{base_url}?per_page=3", user) - - expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue2.id]) - end - - it 'page breaks second page correctly' do - get api("#{base_url}?per_page=3&page=2", user) - - expect_paginated_array_response([group_issue.id]) - end - end - end - - it 'sorts ascending when requested' do - get api("#{base_url}?sort=asc", user) - - expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id]) - end - - it 'sorts by updated_at descending when requested' do - get api("#{base_url}?order_by=updated_at", user) - - group_issue.touch(:updated_at) - - expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id]) - end - - it 'sorts by updated_at ascending when requested' do - get api(base_url, user), params: { order_by: :updated_at, sort: :asc } - - expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) - end - end - end - - describe "GET /projects/:id/issues" do - let(:base_url) { "/projects/#{project.id}" } - - context 'when unauthenticated' do - it 'returns public project issues' do - get api("/projects/#{project.id}/issues") - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - end - - it 'avoids N+1 queries' do - get api("/projects/#{project.id}/issues", user) - - control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do - get api("/projects/#{project.id}/issues", user) - end.count - - create_list(:issue, 3, project: project) - - expect do - get api("/projects/#{project.id}/issues", user) - end.not_to exceed_all_query_limit(control_count) - end - - it 'returns 404 when project does not exist' do - get api('/projects/1000/issues', non_member) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 on private projects for other users" do - private_project = create(:project, :private) - create(:issue, project: private_project) - - get api("/projects/#{private_project.id}/issues", non_member) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns no issues when user has access to project but not issues' do - restricted_project = create(:project, :public, :issues_private) - create(:issue, project: restricted_project) - - get api("/projects/#{restricted_project.id}/issues", non_member) - - expect_paginated_array_response([]) - end - - it 'returns project issues without confidential issues for non project members' do - get api("#{base_url}/issues", non_member) - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns project issues without confidential issues for project members with guest role' do - get api("#{base_url}/issues", guest) - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns project confidential issues for author' do - get api("#{base_url}/issues", author) - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) - end - - it 'returns only confidential issues' do - get api("#{base_url}/issues", author), params: { confidential: true } - - expect_paginated_array_response(confidential_issue.id) - end - - it 'returns only public issues' do - get api("#{base_url}/issues", author), params: { confidential: false } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns project confidential issues for assignee' do - get api("#{base_url}/issues", assignee) - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) - end - - it 'returns project issues with confidential issues for project members' do - get api("#{base_url}/issues", user) - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) - end - - it 'returns project confidential issues for admin' do - get api("#{base_url}/issues", admin) - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) - end - - it 'returns an array of labeled project issues' do - get api("#{base_url}/issues", user), params: { labels: label.title } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of labeled project issues with labels param as array' do - get api("#{base_url}/issues", user), params: { labels: [label.title] } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of labeled issues when all labels matches' do - label_b = create(:label, title: 'foo', project: project) - label_c = create(:label, title: 'bar', project: project) - - create(:label_link, label: label_b, target: issue) - create(:label_link, label: label_c, target: issue) - - get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of labeled issues when all labels matches with labels param as array' do - label_b = create(:label, title: 'foo', project: project) - label_c = create(:label, title: 'bar', project: project) - - create(:label_link, label: label_b, target: issue) - create(:label_link, label: label_c, target: issue) - - get api("#{base_url}/issues", user), params: { labels: [label.title, label_b.title, label_c.title] } - - expect_paginated_array_response(issue.id) - end - - it 'returns issues matching given search string for title' do - get api("#{base_url}/issues?search=#{issue.title}", user) - - expect_paginated_array_response(issue.id) - end - - it 'returns issues matching given search string for description' do - get api("#{base_url}/issues?search=#{issue.description}", user) - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of issues found by iids' do - get api("#{base_url}/issues", user), params: { iids: [issue.iid] } - - expect_paginated_array_response(issue.id) - end - - it 'returns an empty array if iid does not exist' do - get api("#{base_url}/issues", user), params: { iids: [0] } - - expect_paginated_array_response([]) - end - - it 'returns an empty array if not all labels matches' do - get api("#{base_url}/issues?labels=#{label.title},foo", user) - - expect_paginated_array_response([]) - end - - it 'returns an array of project issues with any label' do - get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of project issues with any label with labels param as array' do - get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_ANY] } - - expect_paginated_array_response(issue.id) - end - - it 'returns an array of project issues with no label' do - get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE } - - expect_paginated_array_response([confidential_issue.id, closed_issue.id]) - end - - it 'returns an array of project issues with no label with labels param as array' do - get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_NONE] } - - expect_paginated_array_response([confidential_issue.id, closed_issue.id]) - end - - it 'returns an empty array if no project issue matches labels' do - get api("#{base_url}/issues", user), params: { labels: 'foo,bar' } - - expect_paginated_array_response([]) - end - - it 'returns an empty array if no issue matches milestone' do - get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title } - - expect_paginated_array_response([]) - end - - it 'returns an empty array if milestone does not exist' do - get api("#{base_url}/issues", user), params: { milestone: :foo } - - expect_paginated_array_response([]) - end - - it 'returns an array of issues in given milestone' do - get api("#{base_url}/issues", user), params: { milestone: milestone.title } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - it 'returns an array of issues matching state in milestone' do - get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an array of issues with no milestone' do - get api("#{base_url}/issues", user), params: { milestone: no_milestone_title } - - expect_paginated_array_response(confidential_issue.id) - end - - it 'returns an array of issues with any milestone' do - get api("#{base_url}/issues", user), params: { milestone: any_milestone_title } - - expect_paginated_array_response([issue.id, closed_issue.id]) - end - - context 'without sort params' do - it 'sorts by created_at descending by default' do - get api("#{base_url}/issues", user) - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) - end - - context 'with 2 issues with same created_at' do - let!(:closed_issue2) do - create :closed_issue, - author: user, - assignees: [user], - project: project, - milestone: milestone, - created_at: closed_issue.created_at, - updated_at: 1.hour.ago, - title: issue_title, - description: issue_description - end - - it 'page breaks first page correctly' do - get api("#{base_url}/issues?per_page=3", user) - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue2.id]) - end - - it 'page breaks second page correctly' do - get api("#{base_url}/issues?per_page=3&page=2", user) - - expect_paginated_array_response([closed_issue.id]) - end - end - end - - it 'sorts ascending when requested' do - get api("#{base_url}/issues", user), params: { sort: :asc } - - expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) - end - - it 'sorts by updated_at descending when requested' do - get api("#{base_url}/issues", user), params: { order_by: :updated_at } - - issue.touch(:updated_at) - - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) - end - - it 'sorts by updated_at ascending when requested' do - get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc } - - expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) - end - end - - describe "GET /projects/:id/issues/:issue_iid" do - context 'when unauthenticated' do - it 'returns public issues' do - get api("/projects/#{project.id}/issues/#{issue.iid}") - - expect(response).to have_gitlab_http_status(200) - end - end - - it 'exposes known attributes' do - get api("/projects/#{project.id}/issues/#{issue.iid}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(issue.id) - expect(json_response['iid']).to eq(issue.iid) - expect(json_response['project_id']).to eq(issue.project.id) - expect(json_response['title']).to eq(issue.title) - expect(json_response['description']).to eq(issue.description) - expect(json_response['state']).to eq(issue.state) - expect(json_response['closed_at']).to be_falsy - expect(json_response['created_at']).to be_present - expect(json_response['updated_at']).to be_present - expect(json_response['labels']).to eq(issue.label_names) - expect(json_response['milestone']).to be_a Hash - expect(json_response['assignees']).to be_a Array - expect(json_response['assignee']).to be_a Hash - expect(json_response['author']).to be_a Hash - expect(json_response['confidential']).to be_falsy - end - - it "exposes the 'closed_at' attribute" do - get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['closed_at']).to be_present - end - - context 'links exposure' do - it 'exposes related resources full URIs' do - get api("/projects/#{project.id}/issues/#{issue.iid}", user) - - links = json_response['_links'] - - expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}") - expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes") - expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji") - expect(links['project']).to end_with("/api/v4/projects/#{project.id}") - end - end - - it "returns a project issue by internal id" do - get api("/projects/#{project.id}/issues/#{issue.iid}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(issue.title) - expect(json_response['iid']).to eq(issue.iid) - end - - it "returns 404 if issue id not found" do - get api("/projects/#{project.id}/issues/54321", user) - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 if the issue ID is used" do - get api("/projects/#{project.id}/issues/#{issue.id}", user) - - expect(response).to have_gitlab_http_status(404) - end - - context 'confidential issues' do - it "returns 404 for non project members" do - get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 for project members with guest role" do - get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns confidential issue for project members" do - get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(confidential_issue.title) - expect(json_response['iid']).to eq(confidential_issue.iid) - end - - it "returns confidential issue for author" do - get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(confidential_issue.title) - expect(json_response['iid']).to eq(confidential_issue.iid) - end - - it "returns confidential issue for assignee" do - get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(confidential_issue.title) - expect(json_response['iid']).to eq(confidential_issue.iid) - end - - it "returns confidential issue for admin" do - get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(confidential_issue.title) - expect(json_response['iid']).to eq(confidential_issue.iid) - end - end - end - - describe "POST /projects/:id/issues" do - context 'support for deprecated assignee_id' do - it 'creates a new project issue' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', assignee_id: user2.id } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['assignee']['name']).to eq(user2.name) - expect(json_response['assignees'].first['name']).to eq(user2.name) - end - - it 'creates a new project issue when assignee_id is empty' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', assignee_id: '' } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['assignee']).to be_nil - end - end - - context 'single assignee restrictions' do - it 'creates a new project issue with no more than one assignee' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', assignee_ids: [user2.id, guest.id] } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['assignees'].count).to eq(1) - end - end - - context 'user does not have permissions to create issue' do - let(:not_member) { create(:user) } - - before do - project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE) - end - - it 'renders 403' do - post api("/projects/#{project.id}/issues", not_member), params: { title: 'new issue' } - - expect(response).to have_gitlab_http_status(403) - end - end - - context 'an internal ID is provided' do - context 'by an admin' do - it 'sets the internal ID on the new issue' do - post api("/projects/#{project.id}/issues", admin), - params: { title: 'new issue', iid: 9001 } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['iid']).to eq 9001 - end - end - - context 'by an owner' do - it 'sets the internal ID on the new issue' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', iid: 9001 } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['iid']).to eq 9001 - end - end - - context 'by a group owner' do - let(:group) { create(:group) } - let(:group_project) { create(:project, :public, namespace: group) } - - it 'sets the internal ID on the new issue' do - group.add_owner(user2) - post api("/projects/#{group_project.id}/issues", user2), - params: { title: 'new issue', iid: 9001 } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['iid']).to eq 9001 - end - end - - context 'by another user' do - it 'ignores the given internal ID' do - post api("/projects/#{project.id}/issues", user2), - params: { title: 'new issue', iid: 9001 } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['iid']).not_to eq 9001 - end - end - end - - it 'creates a new project issue' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['description']).to be_nil - expect(json_response['labels']).to eq(%w(label label2)) - expect(json_response['confidential']).to be_falsy - expect(json_response['assignee']['name']).to eq(user2.name) - expect(json_response['assignees'].first['name']).to eq(user2.name) - end - - it 'creates a new project issue with labels param as array' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['description']).to be_nil - expect(json_response['labels']).to eq(%w(label label2)) - expect(json_response['confidential']).to be_falsy - expect(json_response['assignee']['name']).to eq(user2.name) - expect(json_response['assignees'].first['name']).to eq(user2.name) - end - - it 'creates a new confidential project issue' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', confidential: true } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['confidential']).to be_truthy - end - - it 'creates a new confidential project issue with a different param' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', confidential: 'y' } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['confidential']).to be_truthy - end - - it 'creates a public issue when confidential param is false' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', confidential: false } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['confidential']).to be_falsy - end - - it 'creates a public issue when confidential param is invalid' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', confidential: 'foo' } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('confidential is invalid') - end - - it "returns a 400 bad request if title not given" do - post api("/projects/#{project.id}/issues", user), params: { labels: 'label, label2' } - expect(response).to have_gitlab_http_status(400) - end - - it 'allows special label names' do - post api("/projects/#{project.id}/issues", user), - params: { - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' - } - expect(response.status).to eq(201) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'allows special label names with labels param as array' do - post api("/projects/#{project.id}/issues", user), - params: { - title: 'new issue', - labels: ['label', 'label?', 'label&foo, ?, &'] - } - expect(response.status).to eq(201) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'returns 400 if title is too long' do - post api("/projects/#{project.id}/issues", user), - params: { title: 'g' * 256 } - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['title']).to eq([ - 'is too long (maximum is 255 characters)' - ]) - end - - context 'resolving discussions' do - let(:discussion) { create(:diff_note_on_merge_request).to_discussion } - let(:merge_request) { discussion.noteable } - let(:project) { merge_request.source_project } - - before do - project.add_maintainer(user) - end - - context 'resolving all discussions in a merge request' do - before do - post api("/projects/#{project.id}/issues", user), - params: { - title: 'New Issue', - merge_request_to_resolve_discussions_of: merge_request.iid - } - end - - it_behaves_like 'creating an issue resolving discussions through the API' - end - - context 'resolving a single discussion' do - before do - post api("/projects/#{project.id}/issues", user), - params: { - title: 'New Issue', - merge_request_to_resolve_discussions_of: merge_request.iid, - discussion_to_resolve: discussion.id - } - end - - it_behaves_like 'creating an issue resolving discussions through the API' - end - end - - context 'with due date' do - it 'creates a new project issue' do - due_date = 2.weeks.from_now.strftime('%Y-%m-%d') - - post api("/projects/#{project.id}/issues", user), - params: { title: 'new issue', due_date: due_date } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('new issue') - expect(json_response['description']).to be_nil - expect(json_response['due_date']).to eq(due_date) - end - end - - context 'setting created_at' do - let(:creation_time) { 2.weeks.ago } - let(:params) { { title: 'new issue', labels: 'label, label2', created_at: creation_time } } - - context 'by an admin' do - before do - post api("/projects/#{project.id}/issues", admin), params: params - end - - it 'sets the creation time on the new issue' do - expect(response).to have_gitlab_http_status(201) - expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) - end - - it 'sets the system notes timestamp based on creation time' do - issue = Issue.find(json_response['id']) - - expect(issue.resource_label_events.last.created_at).to be_like_time(creation_time) - end - end - - context 'by a project owner' do - it 'sets the creation time on the new issue' do - post api("/projects/#{project.id}/issues", user), params: params - - expect(response).to have_gitlab_http_status(201) - expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) - end - end - - context 'by a group owner' do - it 'sets the creation time on the new issue' do - group = create(:group) - group_project = create(:project, :public, namespace: group) - group.add_owner(user2) - post api("/projects/#{group_project.id}/issues", user2), params: params - - expect(response).to have_gitlab_http_status(201) - expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) - end - end - - context 'by another user' do - it 'ignores the given creation time' do - post api("/projects/#{project.id}/issues", user2), params: params - - expect(response).to have_gitlab_http_status(201) - expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time) - end - end - end - - context 'the user can only read the issue' do - it 'cannot create new labels' do - expect do - post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: 'label, label2' } - end.not_to change { project.labels.count } - end - - it 'cannot create new labels with labels param as array' do - expect do - post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: %w(label label2) } - end.not_to change { project.labels.count } - end - end - end - - describe 'POST /projects/:id/issues with spam filtering' do - before do - allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true) - allow_any_instance_of(AkismetService).to receive_messages(spam?: true) - end - - let(:params) do - { - title: 'new issue', - description: 'content here', - labels: 'label, label2' - } - end - - it "does not create a new project issue" do - expect { post api("/projects/#{project.id}/issues", user), params: params }.not_to change(Issue, :count) - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq({ "error" => "Spam detected" }) - - spam_logs = SpamLog.all - expect(spam_logs.count).to eq(1) - expect(spam_logs[0].title).to eq('new issue') - expect(spam_logs[0].description).to eq('content here') - expect(spam_logs[0].user).to eq(user) - expect(spam_logs[0].noteable_type).to eq('Issue') - end - end - - describe "PUT /projects/:id/issues/:issue_iid to update only title" do - it "updates a project issue" do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(200) - - expect(json_response['title']).to eq('updated title') - end - - it "returns 404 error if issue iid not found" do - put api("/projects/#{project.id}/issues/44444", user), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 error if issue id is used instead of the iid" do - put api("/projects/#{project.id}/issues/#{issue.id}", user), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(404) - end - - it 'allows special label names' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { - title: 'updated title', - labels: 'label, label?, label&foo, ?, &' - } - - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'allows special label names with labels param as array' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { - title: 'updated title', - labels: ['label', 'label?', 'label&foo, ?, &'] - } - - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label' - expect(json_response['labels']).to include 'label?' - expect(json_response['labels']).to include 'label&foo' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - context 'confidential issues' do - it "returns 403 for non project members" do - put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(403) - end - - it "returns 403 for project members with guest role" do - put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(403) - end - - it "updates a confidential issue for project members" do - put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it "updates a confidential issue for author" do - put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it "updates a confidential issue for admin" do - put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('updated title') - end - - it 'sets an issue to confidential' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { confidential: true } - - expect(response).to have_gitlab_http_status(200) - expect(json_response['confidential']).to be_truthy - end - - it 'makes a confidential issue public' do - put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), - params: { confidential: false } - - expect(response).to have_gitlab_http_status(200) - expect(json_response['confidential']).to be_falsy - end - - it 'does not update a confidential issue with wrong confidential flag' do - put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), - params: { confidential: 'foo' } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['error']).to eq('confidential is invalid') - end - end - end - - describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do - let(:params) do - { - title: 'updated title', - description: 'content here', - labels: 'label, label2' - } - end - - it "does not create a new project issue" do - allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true) - allow_any_instance_of(AkismetService).to receive_messages(spam?: true) - - put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: params - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq({ "error" => "Spam detected" }) - - spam_logs = SpamLog.all - expect(spam_logs.count).to eq(1) - expect(spam_logs[0].title).to eq('updated title') - expect(spam_logs[0].description).to eq('content here') - expect(spam_logs[0].user).to eq(user) - expect(spam_logs[0].noteable_type).to eq('Issue') - end - end - - describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do - context 'support for deprecated assignee_id' do - it 'removes assignee' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { assignee_id: 0 } - - expect(response).to have_gitlab_http_status(200) - - expect(json_response['assignee']).to be_nil - end - - it 'updates an issue with new assignee' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { assignee_id: user2.id } - - expect(response).to have_gitlab_http_status(200) - - expect(json_response['assignee']['name']).to eq(user2.name) - end - end - - it 'removes assignee' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { assignee_ids: [0] } - - expect(response).to have_gitlab_http_status(200) - - expect(json_response['assignees']).to be_empty - end - - it 'updates an issue with new assignee' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { assignee_ids: [user2.id] } - - expect(response).to have_gitlab_http_status(200) - - expect(json_response['assignees'].first['name']).to eq(user2.name) - end - - context 'single assignee restrictions' do - it 'updates an issue with several assignees but only one has been applied' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { assignee_ids: [user2.id, guest.id] } - - expect(response).to have_gitlab_http_status(200) - - expect(json_response['assignees'].size).to eq(1) - end - end - end - - describe 'PUT /projects/:id/issues/:issue_iid to update labels' do - let!(:label) { create(:label, title: 'dummy', project: project) } - let!(:label_link) { create(:label_link, label: label, target: issue) } - - it 'does not update labels if not present' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { title: 'updated title' } - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to eq([label.title]) - end - - it 'removes all labels and touches the record' do - Timecop.travel(1.minute.from_now) do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: '' } - end - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to eq([]) - expect(json_response['updated_at']).to be > Time.now - end - - it 'removes all labels and touches the record with labels param as array' do - Timecop.travel(1.minute.from_now) do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: [''] } - end - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to eq([]) - expect(json_response['updated_at']).to be > Time.now - end - - it 'updates labels and touches the record' do - Timecop.travel(1.minute.from_now) do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { labels: 'foo,bar' } - end - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to include 'foo' - expect(json_response['labels']).to include 'bar' - expect(json_response['updated_at']).to be > Time.now - end - - it 'updates labels and touches the record with labels param as array' do - Timecop.travel(1.minute.from_now) do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { labels: %w(foo bar) } - end - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to include 'foo' - expect(json_response['labels']).to include 'bar' - expect(json_response['updated_at']).to be > Time.now - end - - it 'allows special label names' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' } - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label:foo' - expect(json_response['labels']).to include 'label-bar' - expect(json_response['labels']).to include 'label_bar' - expect(json_response['labels']).to include 'label/bar' - expect(json_response['labels']).to include 'label?bar' - expect(json_response['labels']).to include 'label&bar' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'allows special label names with labels param as array' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] } - expect(response.status).to eq(200) - expect(json_response['labels']).to include 'label:foo' - expect(json_response['labels']).to include 'label-bar' - expect(json_response['labels']).to include 'label_bar' - expect(json_response['labels']).to include 'label/bar' - expect(json_response['labels']).to include 'label?bar' - expect(json_response['labels']).to include 'label&bar' - expect(json_response['labels']).to include '?' - expect(json_response['labels']).to include '&' - end - - it 'returns 400 if title is too long' do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { title: 'g' * 256 } - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']['title']).to eq([ - 'is too long (maximum is 255 characters)' - ]) - end - end - - describe "PUT /projects/:id/issues/:issue_iid to update state and label" do - it "updates a project issue" do - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { labels: 'label2', state_event: "close" } - expect(response).to have_gitlab_http_status(200) - - expect(json_response['labels']).to include 'label2' - expect(json_response['state']).to eq "closed" - end - - it 'reopens a project isssue' do - put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), params: { state_event: 'reopen' } - - expect(response).to have_gitlab_http_status(200) - expect(json_response['state']).to eq 'opened' - end - - context 'when an admin or owner makes the request' do - it 'accepts the update date to be set' do - update_time = 2.weeks.ago - put api("/projects/#{project.id}/issues/#{issue.iid}", user), - params: { labels: 'label3', state_event: 'close', updated_at: update_time } - - expect(response).to have_gitlab_http_status(200) - expect(json_response['labels']).to include 'label3' - expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) - end - end - end - - describe 'PUT /projects/:id/issues/:issue_iid to update due date' do - it 'creates a new project issue' do - due_date = 2.weeks.from_now.strftime('%Y-%m-%d') - - put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { due_date: due_date } - - expect(response).to have_gitlab_http_status(200) - expect(json_response['due_date']).to eq(due_date) - end - end - - describe "DELETE /projects/:id/issues/:issue_iid" do - it "rejects a non member from deleting an issue" do - delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member) - expect(response).to have_gitlab_http_status(403) - end - - it "rejects a developer from deleting an issue" do - delete api("/projects/#{project.id}/issues/#{issue.iid}", author) - expect(response).to have_gitlab_http_status(403) - end - - context "when the user is project owner" do - let(:owner) { create(:user) } - let(:project) { create(:project, namespace: owner.namespace) } - - it "deletes the issue if an admin requests it" do - delete api("/projects/#{project.id}/issues/#{issue.iid}", owner) - - expect(response).to have_gitlab_http_status(204) - end - - it_behaves_like '412 response' do - let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) } - end - end - - context 'when issue does not exist' do - it 'returns 404 when trying to move an issue' do - delete api("/projects/#{project.id}/issues/123", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - it 'returns 404 when using the issue ID instead of IID' do - delete api("/projects/#{project.id}/issues/#{issue.id}", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe '/projects/:id/issues/:issue_iid/move' do - let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) } - let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } - - it 'moves an issue' do - post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - params: { to_project_id: target_project.id } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['project_id']).to eq(target_project.id) - end - - context 'when source and target projects are the same' do - it 'returns 400 when trying to move an issue' do - post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - params: { to_project_id: project.id } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Cannot move issue to project it originates from!') - end - end - - context 'when the user does not have the permission to move issues' do - it 'returns 400 when trying to move an issue' do - post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - params: { to_project_id: target_project2.id } - - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') - end - end - - it 'moves the issue to another namespace if I am admin' do - post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin), - params: { to_project_id: target_project2.id } - - expect(response).to have_gitlab_http_status(201) - expect(json_response['project_id']).to eq(target_project2.id) - end - - context 'when using the issue ID instead of iid' do - it 'returns 404 when trying to move an issue' do - post api("/projects/#{project.id}/issues/#{issue.id}/move", user), - params: { to_project_id: target_project.id } - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Issue Not Found') - end - end - - context 'when issue does not exist' do - it 'returns 404 when trying to move an issue' do - post api("/projects/#{project.id}/issues/123/move", user), - params: { to_project_id: target_project.id } - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Issue Not Found') - end - end - - context 'when source project does not exist' do - it 'returns 404 when trying to move an issue' do - post api("/projects/0/issues/#{issue.iid}/move", user), - params: { to_project_id: target_project.id } - - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end - end - - context 'when target project does not exist' do - it 'returns 404 when trying to move an issue' do - post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), - params: { to_project_id: 0 } - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'POST :id/issues/:issue_iid/subscribe' do - it 'subscribes to an issue' do - post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['subscribed']).to eq(true) - end - - it 'returns 304 if already subscribed' do - post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user) - - expect(response).to have_gitlab_http_status(304) - end - - it 'returns 404 if the issue is not found' do - post api("/projects/#{project.id}/issues/123/subscribe", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 404 if the issue ID is used instead of the iid' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 404 if the issue is confidential' do - post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'POST :id/issues/:issue_id/unsubscribe' do - it 'unsubscribes from an issue' do - post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user) - - expect(response).to have_gitlab_http_status(201) - expect(json_response['subscribed']).to eq(false) - end - - it 'returns 304 if not subscribed' do - post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user2) - - expect(response).to have_gitlab_http_status(304) - end - - it 'returns 404 if the issue is not found' do - post api("/projects/#{project.id}/issues/123/unsubscribe", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 404 if using the issue ID instead of iid' do - post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns 404 if the issue is confidential' do - post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'time tracking endpoints' do - let(:issuable) { issue } - - include_examples 'time tracking endpoints', 'issue' - end - - describe 'GET :id/issues/:issue_iid/closed_by' do - let(:merge_request) do - create(:merge_request, - :simple, - author: user, - source_project: project, - target_project: project, - description: "closes #{issue.to_reference}") - end - - before do - create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) - end - - context 'when unauthenticated' do - it 'return public project issues' do - get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by") - - expect_paginated_array_response(merge_request.id) - end - end - - it 'returns merge requests that will close issue on merge' do - get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user) - - expect_paginated_array_response(merge_request.id) - end - - context 'when no merge requests will close issue' do - it 'returns empty array' do - get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user) - - expect_paginated_array_response([]) - end - end - - it "returns 404 when issue doesn't exists" do - get api("/projects/#{project.id}/issues/0/closed_by", user) - - expect(response).to have_gitlab_http_status(404) - 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: issue.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(related_mr.id) - 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(related_mr.id) - end - - it 'returns merge requests cross-project wide' do - project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace) - merge_request = create_referencing_mr(user, project2, issue) - - get_related_merge_requests(project.id, issue.iid, user) - - expect_paginated_array_response([related_mr.id, merge_request.id]) - end - - it 'does not generate references to projects with no access' do - private_project = create(:project, :private) - create_referencing_mr(private_project.creator, private_project, issue) - - get_related_merge_requests(project.id, issue.iid, user) - - expect_paginated_array_response(related_mr.id) - end - - context 'merge request closes an issue' do - let!(:closing_issue_mr_rel) do - create(:merge_requests_closing_issues, issue: issue, merge_request: related_mr) - end - - it 'returns closing MR only once' do - get_related_merge_requests(project.id, issue.iid, user) - - expect_paginated_array_response([related_mr.id]) - end - end - - context 'no merge request mentioned a issue' do - it 'returns empty array' do - get_related_merge_requests(project.id, closed_issue.iid, user) - - expect_paginated_array_response([]) - end - end - - it "returns 404 when issue doesn't exists" do - get_related_merge_requests(project.id, 0, 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) } - - context 'when unauthenticated' do - it "returns unauthorized" do - get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail") - - expect(response).to have_gitlab_http_status(401) - end - end - - it 'exposes known attributes' do - get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) - expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) - expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) - end - - it "returns unauthorized for non-admin users" do - get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user) - - expect(response).to have_gitlab_http_status(403) - end - end - - describe 'GET projects/:id/issues/:issue_iid/participants' do - it_behaves_like 'issuable participants endpoint' do - let(:entity) { issue } - end - - it 'returns 404 if the issue is confidential' do - post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/participants", non_member) - - expect(response).to have_gitlab_http_status(404) - end - end -end diff --git a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb new file mode 100644 index 00000000000..2454b1a9761 --- /dev/null +++ b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +shared_examples 'labeled issues with labels and label_name params' do + shared_examples 'returns label names' do + it 'returns label names' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) + end + end + + shared_examples 'returns basic label entity' do + it 'returns basic label entity' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title]) + expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic') + end + end + + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } } + + it_behaves_like 'returns label names' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title] } } + + it_behaves_like 'returns label names' + end + + context 'array of labeled issues when all labels match the label_name param' do + let(:params) { { label_name: "#{label.title},#{label_b.title},#{label_c.title}" } } + + it_behaves_like 'returns label names' + end + + context 'array of labeled issues when all labels match with label_name param as array' do + let(:params) { { label_name: [label.title, label_b.title, label_c.title] } } + + it_behaves_like 'returns label names' + end + + context 'with labels data' do + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } } + + it_behaves_like 'returns basic label entity' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_data: true } } + + it_behaves_like 'returns basic label entity' + end + + context 'array of labeled issues when all labels match the label_name param' do + let(:params) { { label_name: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } } + + it_behaves_like 'returns basic label entity' + end + + context 'array of labeled issues when all labels match with label_name param as array' do + let(:params) { { label_name: [label.title, label_b.title, label_c.title], with_labels_data: true } } + + it_behaves_like 'returns basic label entity' + end + end +end -- cgit v1.2.1 From a4fbf39eca4518598e893f6f1b81b8b69927c6f9 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Fri, 19 Apr 2019 10:55:36 +0300 Subject: Move issue details to from IssueBasic to Issue entity Cleanup IssueBasic entity to keep it basic and move extra attributes to Issue entity which contains more details --- spec/fixtures/api/schemas/public_api/v4/issue.json | 6 ++---- spec/requests/api/issues/get_project_issues_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/fixtures/api/schemas/public_api/v4/issue.json b/spec/fixtures/api/schemas/public_api/v4/issue.json index 455941f05da..147f53239e0 100644 --- a/spec/fixtures/api/schemas/public_api/v4/issue.json +++ b/spec/fixtures/api/schemas/public_api/v4/issue.json @@ -14,7 +14,7 @@ "labels": { "type": "array", "items": { - "$ref": "../../entities/label.json" + "type": "string" } }, "milestone": { @@ -79,8 +79,6 @@ "due_date": { "type": ["date", "null"] }, "confidential": { "type": "boolean" }, "web_url": { "type": "uri" }, - "has_tasks": {"type": "boolean"}, - "task_status": {"type": "string"}, "time_stats": { "time_estimate": { "type": "integer" }, "total_time_spent": { "type": "integer" }, @@ -93,6 +91,6 @@ "state", "created_at", "updated_at", "labels", "milestone", "assignees", "author", "user_notes_count", "upvotes", "downvotes", "due_date", "confidential", - "has_tasks", "web_url" + "web_url" ] } diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index 8771889f7a4..afd5bd2257f 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -160,12 +160,12 @@ describe API::Issues do it 'avoids N+1 queries' do get api("/projects/#{project.id}/issues", user) + create_list(:issue, 3, project: project) + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do get api("/projects/#{project.id}/issues", user) end.count - create_list(:issue, 3, project: project) - expect do get api("/projects/#{project.id}/issues", user) end.not_to exceed_all_query_limit(control_count) -- cgit v1.2.1 From f117c032ac6c414e6c1dfeab98184363c1f61608 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 24 Apr 2019 16:08:14 +0300 Subject: Add params validations and remove extra params support Remove label_name and milestone_title params support Add mutually_exclusive validation for author_id and author_username Add mutually_exclusive validation for assignee_id and assignee_username Add validation to allow single value for asignee_username on CE Add separate issue_stats_params helper for statistics params and reuse in issues_params. --- spec/requests/api/issues/get_group_issues_spec.rb | 26 +++++++++++++++++++-- .../requests/api/issues/get_project_issues_spec.rb | 26 +++++++++++++++++++-- spec/requests/api/issues/issues_spec.rb | 27 +++++++++++++++++++--- .../requests/api/issues_shared_example_spec.rb | 26 +-------------------- 4 files changed, 73 insertions(+), 32 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb index aafe0f56dfb..8b02cf56e9f 100644 --- a/spec/requests/api/issues/get_group_issues_spec.rb +++ b/spec/requests/api/issues/get_group_issues_spec.rb @@ -619,11 +619,33 @@ describe API::Issues do let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) } - it 'returns issues with multiple assignees' do - get api("/groups/#{group.id}/issues", user), params: { assignee_username: [assignee.username, another_assignee.username] } + it 'returns issues with by assignee_username' do + get api(base_url, user), params: { assignee_username: [assignee.username], scope: 'all' } + expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([issue3.id, group_confidential_issue.id]) end + + it 'returns issues by assignee_username as string' do + get api(base_url, user), params: { assignee_username: assignee.username, scope: 'all' } + + expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) + expect_paginated_array_response([issue3.id, group_confidential_issue.id]) + end + + it 'returns error when multiple assignees are passed' do + get api(base_url, user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response["error"]).to include("allows one value, but found 2") + end + + it 'returns error when assignee_username and assignee_id are passed together' do + get api(base_url, user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response["error"]).to include("mutually exclusive") + end end end end diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index afd5bd2257f..a07d7673345 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -490,11 +490,33 @@ describe API::Issues do let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } - it 'returns issues with multiple assignees' do - get api("#{base_url}/issues", user), params: { assignee_username: [assignee.username, another_assignee.username] } + it 'returns issues by assignee_username' do + get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' } + expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([confidential_issue.id, issue3.id]) end + + it 'returns issues by assignee_username as string' do + get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' } + + expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) + expect_paginated_array_response([confidential_issue.id, issue3.id]) + end + + it 'returns error when multiple assignees are passed' do + get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response["error"]).to include("allows one value, but found 2") + end + + it 'returns error when assignee_username and assignee_id are passed together' do + get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response["error"]).to include("mutually exclusive") + end end end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 6f916212672..24c53d9c68f 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -691,12 +691,33 @@ describe API::Issues do let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } - it 'returns issues with multiple assignees' do - get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } + it 'returns issues with by assignee_username' do + get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' } + + expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) + expect_paginated_array_response([confidential_issue.id, issue3.id]) + end + + it 'returns issues by assignee_username as string' do + get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' } - expect(issue3.reload.assignees).to eq([assignee, another_assignee]) + expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([confidential_issue.id, issue3.id]) end + + it 'returns error when multiple assignees are passed' do + get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response["error"]).to include("allows one value, but found 2") + end + + it 'returns error when assignee_username and assignee_id are passed together' do + get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response["error"]).to include("mutually exclusive") + end end end end diff --git a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb index 2454b1a9761..d1c8e2208a3 100644 --- a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb +++ b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb @@ -28,19 +28,7 @@ shared_examples 'labeled issues with labels and label_name params' do it_behaves_like 'returns label names' end - context 'array of labeled issues when all labels match the label_name param' do - let(:params) { { label_name: "#{label.title},#{label_b.title},#{label_c.title}" } } - - it_behaves_like 'returns label names' - end - - context 'array of labeled issues when all labels match with label_name param as array' do - let(:params) { { label_name: [label.title, label_b.title, label_c.title] } } - - it_behaves_like 'returns label names' - end - - context 'with labels data' do + context 'when with_labels_data provided' do context 'array of labeled issues when all labels match' do let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } } @@ -52,17 +40,5 @@ shared_examples 'labeled issues with labels and label_name params' do it_behaves_like 'returns basic label entity' end - - context 'array of labeled issues when all labels match the label_name param' do - let(:params) { { label_name: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } } - - it_behaves_like 'returns basic label entity' - end - - context 'array of labeled issues when all labels match with label_name param as array' do - let(:params) { { label_name: [label.title, label_b.title, label_c.title], with_labels_data: true } } - - it_behaves_like 'returns basic label entity' - end end end -- cgit v1.2.1 From 79f05f8a7e5d038d62f85f1a3f242932bbd981b3 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Tue, 14 May 2019 15:01:49 +0700 Subject: Sessions can be listed for a given user Adds ActiveSession#session_ids_for_user for listing session IDs for a given user, and adds ActiveSession#list_sessions for listing session data directly. --- spec/models/active_session_spec.rb | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'spec') diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index b523f393ece..2762eaeccd3 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -88,6 +88,52 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do end end + describe '.list_sessions' do + it 'uses the ActiveSession lookup to return original sessions' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' })) + + redis.sadd( + "session:lookup:user:gitlab:#{user.id}", + %w[ + 6919a6f1bb119dd7396fadc38fd18d0d + 59822c7d9fcdfa03725eff41782ad97d + ] + ) + end + + expect(ActiveSession.list_sessions(user)).to eq [{ _csrf_token: 'abcd' }] + end + end + + describe '.session_ids_for_user' do + it 'uses the user lookup table to return session ids' do + session_ids = ['59822c7d9fcdfa03725eff41782ad97d'] + + Gitlab::Redis::SharedState.with do |redis| + redis.sadd("session:lookup:user:gitlab:#{user.id}", session_ids) + end + + expect(ActiveSession.session_ids_for_user(user)).to eq(session_ids) + end + end + + describe '.sessions_from_ids' do + it 'uses the ActiveSession lookup to return original sessions' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' })) + end + + expect(ActiveSession.sessions_from_ids(['6919a6f1bb119dd7396fadc38fd18d0d'])).to eq [{ _csrf_token: 'abcd' }] + end + + it 'avoids a redis lookup for an empty array' do + expect(Gitlab::Redis::SharedState).not_to receive(:with) + + expect(ActiveSession.sessions_from_ids([])).to eq([]) + end + end + describe '.set' do it 'sets a new redis entry for the user session and a lookup entry' do ActiveSession.set(user, request) -- cgit v1.2.1 From bedb9a3e6d644e4d8c71f92038ce31c9383e0887 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Mon, 13 May 2019 15:50:23 +0700 Subject: Avoid passing not_found_or_authorized_proc around Since this needs to be called on every find_routable!(Project, ... we can instead move it to a RoutableActions check. --- .../concerns/project_unauthorized_spec.rb | 2 +- spec/controllers/concerns/routable_actions_spec.rb | 37 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/concerns/project_unauthorized_spec.rb b/spec/controllers/concerns/project_unauthorized_spec.rb index 57ac00cf4dd..5834b1ef37f 100644 --- a/spec/controllers/concerns/project_unauthorized_spec.rb +++ b/spec/controllers/concerns/project_unauthorized_spec.rb @@ -12,7 +12,7 @@ describe ProjectUnauthorized do render_views - describe '#project_unauthorized_proc' do + describe '.on_routable_not_found' do controller(::Projects::ApplicationController) do def show head :ok diff --git a/spec/controllers/concerns/routable_actions_spec.rb b/spec/controllers/concerns/routable_actions_spec.rb index f6f2b8a280d..59d48c68b9c 100644 --- a/spec/controllers/concerns/routable_actions_spec.rb +++ b/spec/controllers/concerns/routable_actions_spec.rb @@ -116,4 +116,41 @@ describe RoutableActions do end end end + + describe '#perform_not_found_actions' do + let(:routable) { create(:project) } + + before do + sign_in(create(:user)) + end + + it 'performs multiple checks' do + last_check_called = false + checks = [proc {}, proc { last_check_called = true }] + allow(subject).to receive(:not_found_actions).and_return(checks) + + get_routable(routable) + + expect(last_check_called).to eq(true) + end + + it 'performs checks in the context of the controller' do + check = lambda { |routable| redirect_to routable } + allow(subject).to receive(:not_found_actions).and_return([check]) + + get_routable(routable) + + expect(response.location).to end_with(routable.to_param) + end + + it 'skips checks once one has resulted in a render/redirect' do + first_check = proc { render plain: 'first' } + second_check = proc { render plain: 'second' } + allow(subject).to receive(:not_found_actions).and_return([first_check, second_check]) + + get_routable(routable) + + expect(response.body).to eq('first') + end + end end -- cgit v1.2.1 From 1eb3eca3137b33c1cf84779933480ea33a342fd6 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 15 May 2019 14:18:54 +0200 Subject: Fix loadJSONFixtures wrapper in Jest --- spec/frontend/test_setup.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index c57e0e7cfc6..f0d62a3c41c 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -3,7 +3,7 @@ import * as jqueryMatchers from 'custom-jquery-matchers'; import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; import { initializeTestTimeout } from './helpers/timeout'; -import { getJSONFixture, loadHTMLFixture, setHTMLFixture } from './helpers/fixtures'; +import { loadHTMLFixture, setHTMLFixture } from './helpers/fixtures'; process.on('unhandledRejection', global.promiseRejectionHandler); @@ -46,9 +46,12 @@ Object.defineProperty(global.Element.prototype, 'innerText', { // convenience wrapper for migration from Karma Object.assign(global, { loadFixtures: loadHTMLFixture, - loadJSONFixtures: getJSONFixture, - preloadFixtures() {}, setFixtures: setHTMLFixture, + + // The following functions fill the fixtures cache in Karma. + // This is not necessary in Jest because we make no Ajax request. + loadJSONFixtures() {}, + preloadFixtures() {}, }); // custom-jquery-matchers was written for an old Jest version, we need to make it compatible -- cgit v1.2.1 From 0705acd66c58c09322f54875f33868414d8a8639 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 11 Mar 2019 18:19:34 +0000 Subject: Fix flyout nav on small viewports Decouples sidebar collapsed style from behavior. --- spec/javascripts/fly_out_nav_spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js index 7ef44f29c5b..4772f754937 100644 --- a/spec/javascripts/fly_out_nav_spec.js +++ b/spec/javascripts/fly_out_nav_spec.js @@ -14,6 +14,7 @@ import { setSidebar, subItemsMouseLeave, } from '~/fly_out_nav'; +import { SIDEBAR_COLLAPSED_CLASS } from '~/contextual_sidebar'; import bp from '~/breakpoints'; describe('Fly out sidebar navigation', () => { @@ -219,7 +220,7 @@ describe('Fly out sidebar navigation', () => { it('shows collapsed only sub-items if icon only sidebar', () => { const subItems = el.querySelector('.sidebar-sub-level-items'); const sidebar = document.createElement('div'); - sidebar.classList.add('sidebar-collapsed-desktop'); + sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS); subItems.classList.add('is-fly-out-only'); setSidebar(sidebar); @@ -296,7 +297,7 @@ describe('Fly out sidebar navigation', () => { it('returns true when active & collapsed sidebar', () => { const sidebar = document.createElement('div'); - sidebar.classList.add('sidebar-collapsed-desktop'); + sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS); el.classList.add('active'); setSidebar(sidebar); -- cgit v1.2.1 From 5632079c3684562aea890123182bd43648149b55 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 14 May 2019 14:37:15 -0500 Subject: Properly clear the merge error upon rebase failure If `merge_error` is ever set after a failure, it is never cleared, even after a subsequent, successful rebase. We now clear this field whenever a successful rebase occurs. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/56139 --- .../services/merge_requests/rebase_service_spec.rb | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'spec') diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index a443e4588d9..0c4433453ac 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -38,6 +38,32 @@ describe MergeRequests::RebaseService do end end + shared_examples 'sequence of failure and success' do + it 'properly clears the error message' do + allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong') + + service.execute(merge_request) + + expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR + + allow(repository).to receive(:gitaly_operation_client).and_call_original + + service.execute(merge_request) + + expect(merge_request.reload.merge_error).to eq nil + end + end + + it_behaves_like 'sequence of failure and success' + + context 'with deprecated step rebase feature' do + before do + allow(Feature).to receive(:disabled?).with(:two_step_rebase, anything).and_return(true) + end + + it_behaves_like 'sequence of failure and success' + end + context 'when unexpected error occurs' do before do allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong') -- cgit v1.2.1 From 3c33724e2e182436a2d8b44ef71d0bdac37c585b Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Thu, 16 May 2019 09:32:25 +0000 Subject: Add Let's Encrypt client Part of adding Let's Encrypt certificates for pages domains Add acme-client gem Client is being initialized by private key stored in secrets.yml Let's Encrypt account is being created lazily. If it's already created, Acme::Client just gets account_kid by calling new_account method Make Let's Encrypt client an instance Wrap order and challenge classes --- spec/lib/gitlab/lets_encrypt/challenge_spec.rb | 29 ++++++ spec/lib/gitlab/lets_encrypt/client_spec.rb | 120 +++++++++++++++++++++++++ spec/lib/gitlab/lets_encrypt/order_spec.rb | 39 ++++++++ spec/support/helpers/lets_encrypt_helpers.rb | 19 ++++ 4 files changed, 207 insertions(+) create mode 100644 spec/lib/gitlab/lets_encrypt/challenge_spec.rb create mode 100644 spec/lib/gitlab/lets_encrypt/client_spec.rb create mode 100644 spec/lib/gitlab/lets_encrypt/order_spec.rb create mode 100644 spec/support/helpers/lets_encrypt_helpers.rb (limited to 'spec') diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb new file mode 100644 index 00000000000..74622f356de --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::LetsEncrypt::Challenge do + delegated_methods = { + url: 'https://example.com/', + status: 'pending', + token: 'tokenvalue', + file_content: 'hereisfilecontent', + request_validation: true + } + + let(:acme_challenge) do + acme_challenge = instance_double('Acme::Client::Resources::Challenge') + allow(acme_challenge).to receive_messages(delegated_methods) + acme_challenge + end + + let(:challenge) { described_class.new(acme_challenge) } + + delegated_methods.each do |method, value| + describe "##{method}" do + it 'delegates to Acme::Client::Resources::Challenge' do + expect(challenge.public_send(method)).to eq(value) + end + end + end +end diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb new file mode 100644 index 00000000000..16a16acfd25 --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::LetsEncrypt::Client do + include LetsEncryptHelpers + + let(:client) { described_class.new } + + before do + stub_application_setting( + lets_encrypt_notification_email: 'myemail@test.example.com', + lets_encrypt_terms_of_service_accepted: true + ) + end + + let!(:stub_client) { stub_lets_encrypt_client } + + shared_examples 'ensures account registration' do + it 'ensures account registration' do + subject + + expect(stub_client).to have_received(:new_account).with( + contact: 'mailto:myemail@test.example.com', + terms_of_service_agreed: true + ) + end + + context 'when acme integration is disabled' do + before do + stub_application_setting(lets_encrypt_terms_of_service_accepted: false) + end + + it 'raises error' do + expect do + subject + end.to raise_error('Acme integration is disabled') + end + end + end + + describe '#new_order' do + subject(:new_order) { client.new_order('example.com') } + + before do + order_double = instance_double('Acme::Order') + allow(stub_client).to receive(:new_order).and_return(order_double) + end + + include_examples 'ensures account registration' + + it 'returns order' do + is_expected.to be_a(::Gitlab::LetsEncrypt::Order) + end + end + + describe '#load_order' do + let(:url) { 'https://example.com/order' } + subject { client.load_order(url) } + + before do + acme_order = instance_double('Acme::Client::Resources::Order') + allow(stub_client).to receive(:order).with(url: url).and_return(acme_order) + end + + include_examples 'ensures account registration' + + it 'loads order' do + is_expected.to be_a(::Gitlab::LetsEncrypt::Order) + end + end + + describe '#load_challenge' do + let(:url) { 'https://example.com/challenge' } + subject { client.load_challenge(url) } + + before do + acme_challenge = instance_double('Acme::Client::Resources::Challenge') + allow(stub_client).to receive(:challenge).with(url: url).and_return(acme_challenge) + end + + include_examples 'ensures account registration' + + it 'loads challenge' do + is_expected.to be_a(::Gitlab::LetsEncrypt::Challenge) + end + end + + describe '#enabled?' do + subject { client.enabled? } + + context 'when terms of service are accepted' do + it { is_expected.to eq(true) } + + context 'when feature flag is disabled' do + before do + stub_feature_flags(pages_auto_ssl: false) + end + + it { is_expected.to eq(false) } + end + end + + context 'when terms of service are not accepted' do + before do + stub_application_setting(lets_encrypt_terms_of_service_accepted: false) + end + + it { is_expected.to eq(false) } + end + end + + describe '#terms_of_service_url' do + subject { client.terms_of_service_url } + + it 'returns valid url' do + is_expected.to eq("https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf") + end + end +end diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb new file mode 100644 index 00000000000..ee7058baf8d --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::LetsEncrypt::Order do + delegated_methods = { + url: 'https://example.com/', + status: 'valid' + } + + let(:acme_order) do + acme_order = instance_double('Acme::Client::Resources::Order') + allow(acme_order).to receive_messages(delegated_methods) + acme_order + end + + let(:order) { described_class.new(acme_order) } + + delegated_methods.each do |method, value| + describe "##{method}" do + it 'delegates to Acme::Client::Resources::Order' do + expect(order.public_send(method)).to eq(value) + end + end + end + + describe '#new_challenge' do + before do + challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01') + authorization = instance_double('Acme::Client::Resources::Authorization') + allow(authorization).to receive(:http).and_return(challenge) + allow(acme_order).to receive(:authorizations).and_return([authorization]) + end + + it 'returns challenge' do + expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge) + end + end +end diff --git a/spec/support/helpers/lets_encrypt_helpers.rb b/spec/support/helpers/lets_encrypt_helpers.rb new file mode 100644 index 00000000000..7f0886b451c --- /dev/null +++ b/spec/support/helpers/lets_encrypt_helpers.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module LetsEncryptHelpers + def stub_lets_encrypt_client + client = instance_double('Acme::Client') + + allow(client).to receive(:new_account) + allow(client).to receive(:terms_of_service).and_return( + "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + ) + + allow(Acme::Client).to receive(:new).with( + private_key: kind_of(OpenSSL::PKey::RSA), + directory: ::Gitlab::LetsEncrypt::Client::STAGING_DIRECTORY_URL + ).and_return(client) + + client + end +end -- cgit v1.2.1 From 22f8da985a8919eb350bbcacb89f176cecf986bc Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 16 May 2019 11:53:09 +0100 Subject: Fix a broken spec for EE --- spec/services/merge_requests/rebase_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index 0c4433453ac..7e2f03d1097 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -58,7 +58,7 @@ describe MergeRequests::RebaseService do context 'with deprecated step rebase feature' do before do - allow(Feature).to receive(:disabled?).with(:two_step_rebase, anything).and_return(true) + stub_feature_flags(two_step_rebase: false) end it_behaves_like 'sequence of failure and success' -- cgit v1.2.1 From 878e69299bde9875f14c82bc1429c4b0145a2c69 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 16 May 2019 06:40:18 -0500 Subject: Rename vue_component_helper to text_helper **Why?** This file only contains a few text based helper functions. It has nothing to do with Vue or Vue components. --- spec/frontend/helpers/text_helper.js | 18 ++++++++++++++++++ spec/frontend/helpers/vue_component_helper.js | 18 ------------------ spec/javascripts/diffs/components/commit_item_spec.js | 2 +- .../folder/environments_folder_view_spec.js | 2 +- .../components/frequent_items_list_item_spec.js | 2 +- spec/javascripts/helpers/text_helper.js | 18 ++++++++++++++++++ spec/javascripts/helpers/vue_component_helper.js | 18 ------------------ .../components/commit_sidebar/list_collapsed_spec.js | 2 +- spec/javascripts/ide/components/ide_review_spec.js | 2 +- .../ide/components/nav_dropdown_button_spec.js | 2 +- .../jobs/components/artifacts_block_spec.js | 2 +- spec/javascripts/jobs/components/sidebar_spec.js | 2 +- .../jobs/components/stages_dropdown_spec.js | 2 +- spec/javascripts/reports/components/modal_spec.js | 2 +- .../reports/components/test_issue_body_spec.js | 2 +- .../components/mr_widget_pipeline_spec.js | 2 +- .../components/states/mr_widget_conflicts_spec.js | 2 +- .../states/mr_widget_pipeline_blocked_spec.js | 2 +- .../states/mr_widget_pipeline_failed_spec.js | 2 +- .../components/states/mr_widget_sha_mismatch_spec.js | 2 +- .../project_selector/project_list_item_spec.js | 2 +- .../project_selector/project_selector_spec.js | 2 +- spec/javascripts/vue_shared/translate_spec.js | 2 +- 23 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 spec/frontend/helpers/text_helper.js delete mode 100644 spec/frontend/helpers/vue_component_helper.js create mode 100644 spec/javascripts/helpers/text_helper.js delete mode 100644 spec/javascripts/helpers/vue_component_helper.js (limited to 'spec') diff --git a/spec/frontend/helpers/text_helper.js b/spec/frontend/helpers/text_helper.js new file mode 100644 index 00000000000..e0fe18e5560 --- /dev/null +++ b/spec/frontend/helpers/text_helper.js @@ -0,0 +1,18 @@ +/** + * Replaces line break with an empty space + * @param {*} data + */ +export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' '); + +/** + * Removes line breaks, spaces and trims the given text + * @param {String} str + * @returns {String} + */ +export const trimText = str => + str + .replace(/\r?\n|\r/g, '') + .replace(/\s\s+/g, ' ') + .trim(); + +export const removeWhitespace = str => str.replace(/\s\s+/g, ' '); diff --git a/spec/frontend/helpers/vue_component_helper.js b/spec/frontend/helpers/vue_component_helper.js deleted file mode 100644 index e0fe18e5560..00000000000 --- a/spec/frontend/helpers/vue_component_helper.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Replaces line break with an empty space - * @param {*} data - */ -export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' '); - -/** - * Removes line breaks, spaces and trims the given text - * @param {String} str - * @returns {String} - */ -export const trimText = str => - str - .replace(/\r?\n|\r/g, '') - .replace(/\s\s+/g, ' ') - .trim(); - -export const removeWhitespace = str => str.replace(/\s\s+/g, ' '); diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js index 50e45f48af3..8fc9b10dd0b 100644 --- a/spec/javascripts/diffs/components/commit_item_spec.js +++ b/spec/javascripts/diffs/components/commit_item_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import { TEST_HOST } from 'spec/test_constants'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; import { getTimeago } from '~/lib/utils/datetime_utility'; import CommitItem from '~/diffs/components/commit_item.vue'; import getDiffWithCommit from '../mock_data/diff_with_commit'; diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index ff15067aeac..f1c323df4be 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { removeBreakLine, removeWhitespace } from 'spec/helpers/vue_component_helper'; +import { removeBreakLine, removeWhitespace } from 'spec/helpers/text_helper'; import { environmentsList } from '../mock_data'; describe('Environments Folder View', () => { diff --git a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js index f00bc2eeb6d..dce8e3be148 100644 --- a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js +++ b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; import { shallowMount } from '@vue/test-utils'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here const createComponent = () => { diff --git a/spec/javascripts/helpers/text_helper.js b/spec/javascripts/helpers/text_helper.js new file mode 100644 index 00000000000..e0fe18e5560 --- /dev/null +++ b/spec/javascripts/helpers/text_helper.js @@ -0,0 +1,18 @@ +/** + * Replaces line break with an empty space + * @param {*} data + */ +export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' '); + +/** + * Removes line breaks, spaces and trims the given text + * @param {String} str + * @returns {String} + */ +export const trimText = str => + str + .replace(/\r?\n|\r/g, '') + .replace(/\s\s+/g, ' ') + .trim(); + +export const removeWhitespace = str => str.replace(/\s\s+/g, ' '); diff --git a/spec/javascripts/helpers/vue_component_helper.js b/spec/javascripts/helpers/vue_component_helper.js deleted file mode 100644 index e0fe18e5560..00000000000 --- a/spec/javascripts/helpers/vue_component_helper.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Replaces line break with an empty space - * @param {*} data - */ -export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' '); - -/** - * Removes line breaks, spaces and trims the given text - * @param {String} str - * @returns {String} - */ -export const trimText = str => - str - .replace(/\r?\n|\r/g, '') - .replace(/\s\s+/g, ' ') - .trim(); - -export const removeWhitespace = str => str.replace(/\s\s+/g, ' '); diff --git a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js index 9af3c15a4e3..3c7d6192e2c 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js @@ -3,7 +3,7 @@ import store from '~/ide/stores'; import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file } from '../../helpers'; -import { removeWhitespace } from '../../../helpers/vue_component_helper'; +import { removeWhitespace } from '../../../helpers/text_helper'; describe('Multi-file editor commit sidebar list collapsed', () => { let vm; diff --git a/spec/javascripts/ide/components/ide_review_spec.js b/spec/javascripts/ide/components/ide_review_spec.js index b9ee22b7c1a..396c5d282d4 100644 --- a/spec/javascripts/ide/components/ide_review_spec.js +++ b/spec/javascripts/ide/components/ide_review_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import IdeReview from '~/ide/components/ide_review.vue'; import store from '~/ide/stores'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { trimText } from '../../helpers/vue_component_helper'; +import { trimText } from '../../helpers/text_helper'; import { resetStore, file } from '../helpers'; import { projectData } from '../mock_data'; diff --git a/spec/javascripts/ide/components/nav_dropdown_button_spec.js b/spec/javascripts/ide/components/nav_dropdown_button_spec.js index 0a58e260280..19b0071567a 100644 --- a/spec/javascripts/ide/components/nav_dropdown_button_spec.js +++ b/spec/javascripts/ide/components/nav_dropdown_button_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue'; import store from '~/ide/stores'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../helpers'; diff --git a/spec/javascripts/jobs/components/artifacts_block_spec.js b/spec/javascripts/jobs/components/artifacts_block_spec.js index 27d480ef2ea..58998d038e5 100644 --- a/spec/javascripts/jobs/components/artifacts_block_spec.js +++ b/spec/javascripts/jobs/components/artifacts_block_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import { getTimeago } from '~/lib/utils/datetime_utility'; import component from '~/jobs/components/artifacts_block.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; -import { trimText } from '../../helpers/vue_component_helper'; +import { trimText } from '../../helpers/text_helper'; describe('Artifacts block', () => { const Component = Vue.extend(component); diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js index 3a02351460c..26d9effcac5 100644 --- a/spec/javascripts/jobs/components/sidebar_spec.js +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -3,7 +3,7 @@ import sidebarDetailsBlock from '~/jobs/components/sidebar.vue'; import createStore from '~/jobs/store'; import job, { stages, jobsInStage } from '../mock_data'; import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { trimText } from '../../helpers/vue_component_helper'; +import { trimText } from '../../helpers/text_helper'; describe('Sidebar details block', () => { const SidebarComponent = Vue.extend(sidebarDetailsBlock); diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index eccb4e13d67..52bb5161123 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import component from '~/jobs/components/stages_dropdown.vue'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Stages Dropdown', () => { diff --git a/spec/javascripts/reports/components/modal_spec.js b/spec/javascripts/reports/components/modal_spec.js index 6b8471381de..d42c509e5b5 100644 --- a/spec/javascripts/reports/components/modal_spec.js +++ b/spec/javascripts/reports/components/modal_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import component from '~/reports/components/modal.vue'; import state from '~/reports/store/state'; import mountComponent from '../../helpers/vue_mount_component_helper'; -import { trimText } from '../../helpers/vue_component_helper'; +import { trimText } from '../../helpers/text_helper'; describe('Grouped Test Reports Modal', () => { const Component = Vue.extend(component); diff --git a/spec/javascripts/reports/components/test_issue_body_spec.js b/spec/javascripts/reports/components/test_issue_body_spec.js index 32baf904ad7..9c1cec4c9bc 100644 --- a/spec/javascripts/reports/components/test_issue_body_spec.js +++ b/spec/javascripts/reports/components/test_issue_body_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import component from '~/reports/components/test_issue_body.vue'; import createStore from '~/reports/store'; import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { trimText } from '../../helpers/vue_component_helper'; +import { trimText } from '../../helpers/text_helper'; import { issue } from '../mock_data/mock_data'; describe('Test Issue body', () => { 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 8ac6e6a7b44..75017d20473 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 @@ -1,7 +1,7 @@ import Vue from 'vue'; import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; import mockData from '../mock_data'; describe('MRWidgetPipeline', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index 0ddbdf67d8b..39b879612ae 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue'; -import { removeBreakLine } from 'spec/helpers/vue_component_helper'; +import { removeBreakLine } from 'spec/helpers/text_helper'; describe('MRWidgetConflicts', () => { let vm; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js index 477041fa383..1d2f3e41509 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { removeBreakLine } from 'spec/helpers/vue_component_helper'; +import { removeBreakLine } from 'spec/helpers/text_helper'; describe('MRWidgetPipelineBlocked', () => { let vm; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js index f7523a01963..3e4ce2c3696 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue'; -import { removeBreakLine } from 'spec/helpers/vue_component_helper'; +import { removeBreakLine } from 'spec/helpers/text_helper'; describe('PipelineFailed', () => { describe('template', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js index 36f8c7a9683..9324c83bf4b 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { removeBreakLine } from 'spec/helpers/vue_component_helper'; +import { removeBreakLine } from 'spec/helpers/text_helper'; describe('ShaMismatch', () => { let vm; diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js index 268ced38f40..47964a1702a 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js @@ -1,6 +1,6 @@ import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; const localVue = createLocalVue(); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js index 34c0cd435cd..8cf351a4af5 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue'; import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; import { shallowMount } from '@vue/test-utils'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; describe('ProjectSelector component', () => { let wrapper; diff --git a/spec/javascripts/vue_shared/translate_spec.js b/spec/javascripts/vue_shared/translate_spec.js index adb5ff682f0..0aaa4050cba 100644 --- a/spec/javascripts/vue_shared/translate_spec.js +++ b/spec/javascripts/vue_shared/translate_spec.js @@ -3,7 +3,7 @@ import Jed from 'jed'; import locale from '~/locale'; import Translate from '~/vue_shared/translate'; -import { trimText } from 'spec/helpers/vue_component_helper'; +import { trimText } from 'spec/helpers/text_helper'; describe('Vue translate filter', () => { let el; -- cgit v1.2.1 From 411f545ce6a88db8370e639989d9c8f6d3621cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zaj=C4=85c?= Date: Thu, 16 May 2019 11:59:02 +0000 Subject: Include MR information if possible when emailing notification of closing an issue --- spec/helpers/emails_helper_spec.rb | 39 ++++++++++++++++++++++++++ spec/services/issues/close_service_spec.rb | 44 ++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index 03b4c19ec22..0434af25866 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -1,6 +1,45 @@ require 'spec_helper' describe EmailsHelper do + describe 'closure_reason_text' do + context 'when given a MergeRequest' do + let(:merge_request) { create(:merge_request) } + let(:merge_request_presenter) { merge_request.present } + + context "and format is text" do + it "returns plain text" do + expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") + end + end + + context "and format is HTML" do + it "returns HTML" do + expect(closure_reason_text(merge_request, format: :html)).to eq(" via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}") + end + end + + context "and format is unknown" do + it "returns plain text" do + expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") + end + end + end + + context 'when given a String' do + let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" } + + it "returns plain text" do + expect(closure_reason_text(closed_via)).to eq(" via #{closed_via}") + end + end + + context 'when not given anything' do + it "returns empty string" do + expect(closure_reason_text(nil)).to eq("") + end + end + end + describe 'sanitize_name' do context 'when name contains a valid URL string' do it 'returns name with `.` replaced with `_` to prevent mail clients from auto-linking URLs' do diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index fce9eed8b08..6874a8a0929 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -3,11 +3,13 @@ require 'spec_helper' describe Issues::CloseService do - let(:user) { create(:user) } - let(:user2) { create(:user) } + let(:project) { create(:project, :repository) } + let(:user) { create(:user, email: "user@example.com") } + let(:user2) { create(:user, email: "user2@example.com") } let(:guest) { create(:user) } - let(:issue) { create(:issue, assignees: [user2], author: create(:user)) } - let(:project) { issue.project } + let(:issue) { create(:issue, title: "My issue", project: project, assignees: [user2], author: create(:user)) } + let(:closing_merge_request) { create(:merge_request, source_project: project) } + let(:closing_commit) { create(:commit, project: project) } let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) } before do @@ -39,7 +41,7 @@ describe Issues::CloseService do .and_return(true) expect(service).to receive(:close_issue) - .with(issue, commit: nil, notifications: true, system_note: true) + .with(issue, closed_via: nil, notifications: true, system_note: true) service.execute(issue) end @@ -57,6 +59,38 @@ describe Issues::CloseService do end describe '#close_issue' do + context "closed by a merge request" do + before do + perform_enqueued_jobs do + described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request) + end + end + + it 'mentions closure via a merge request' do + email = ActionMailer::Base.deliveries.last + + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(issue.title) + expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference)) + end + end + + context "closed by a commit" do + before do + perform_enqueued_jobs do + described_class.new(project, user).close_issue(issue, closed_via: closing_commit) + end + end + + it 'mentions closure via a commit' do + email = ActionMailer::Base.deliveries.last + + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(issue.title) + expect(email.body.parts.map(&:body)).to all(include(closing_commit.id)) + end + end + context "valid params" do before do perform_enqueued_jobs do -- cgit v1.2.1 From 9d3676846c860dcd7865b339d58588184b2f9e6c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 13 May 2019 17:04:09 +0100 Subject: Add :registrations_recaptcha feature flag Allows instance owners to toggle the recaptcha requirement on the user registration page by feature flag. Allows GitLab Growth team to measure reCAPTCHA's impact on registrations. --- spec/controllers/registrations_controller_spec.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 088c515c3a6..9a598790ff2 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -46,13 +46,17 @@ describe RegistrationsController do end context 'when reCAPTCHA is enabled' do + def fail_recaptcha + # Without this, `verify_recaptcha` arbitrarily returns true in test env + Recaptcha.configuration.skip_verify_env.delete('test') + end + before do stub_application_setting(recaptcha_enabled: true) end it 'displays an error when the reCAPTCHA is not solved' do - # Without this, `verify_recaptcha` arbitrarily returns true in test env - Recaptcha.configuration.skip_verify_env.delete('test') + fail_recaptcha post(:create, params: user_params) @@ -70,6 +74,17 @@ describe RegistrationsController do expect(flash[:notice]).to include 'Welcome! You have signed up successfully.' end + + it 'does not require reCAPTCHA if disabled by feature flag' do + stub_feature_flags(registrations_recaptcha: false) + fail_recaptcha + + post(:create, params: user_params) + + expect(controller).not_to receive(:verify_recaptcha) + expect(flash[:alert]).to be_nil + expect(flash[:notice]).to include 'Welcome! You have signed up successfully.' + end end context 'when terms are enforced' do -- cgit v1.2.1 From ec07641435e9d49686c3eefd9d698caf1b2115dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 16 May 2019 12:30:42 +0200 Subject: Fix an error in projects admin when statistics are missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../admin/admin_sees_project_statistics_spec.rb | 29 ++++++++++++++++++++++ .../admin/admin_sees_projects_statistics_spec.rb | 20 +++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 spec/features/admin/admin_sees_project_statistics_spec.rb create mode 100644 spec/features/admin/admin_sees_projects_statistics_spec.rb (limited to 'spec') diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb new file mode 100644 index 00000000000..95d1fc5b57a --- /dev/null +++ b/spec/features/admin/admin_sees_project_statistics_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Admin > Admin sees project statistics" do + let(:current_user) { create(:admin) } + + before do + sign_in(current_user) + + visit admin_project_path(project) + end + + context 'when project has statistics' do + let(:project) { create(:project, :repository) } + + it "shows project statistics" do + expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes build artifacts, 0 Bytes LFS)") + end + end + + context 'when project has no statistics' do + let(:project) { create(:project, :repository) { |project| project.statistics.destroy } } + + it "shows 'Storage: Unknown'" do + expect(page).to have_content("Storage: Unknown") + end + end +end diff --git a/spec/features/admin/admin_sees_projects_statistics_spec.rb b/spec/features/admin/admin_sees_projects_statistics_spec.rb new file mode 100644 index 00000000000..6a6f369ac7c --- /dev/null +++ b/spec/features/admin/admin_sees_projects_statistics_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Admin > Admin sees projects statistics" do + let(:current_user) { create(:admin) } + + before do + create(:project, :repository) + create(:project, :repository) { |project| project.statistics.destroy } + + sign_in(current_user) + + visit admin_projects_path + end + + it "shows project statistics for projects that have them" do + expect(page.all('.stats').map(&:text)).to contain_exactly("0 Bytes", "Unknown") + end +end -- cgit v1.2.1 From d2425716aa9f084d68fecae74688a3f6dac24ce2 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 16 May 2019 16:49:26 +0100 Subject: CE backport for "Use gitlab-elasticsearch-indexer in tests" --- spec/support/helpers/test_env.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index dc902d373b8..06b5ecdf150 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -135,7 +135,7 @@ module TestEnv def clean_gitlab_test_path Dir[TMP_TEST_PATH].each do |entry| - if File.basename(entry) =~ /\A(gitlab-(test|test_bare|test-fork|test-fork_bare))\z/ + unless test_dirs.include?(File.basename(entry)) FileUtils.rm_rf(entry) end end @@ -312,6 +312,18 @@ module TestEnv private + # These are directories that should be preserved at cleanup time + def test_dirs + @test_dirs ||= %w[ + gitaly + gitlab-shell + gitlab-test + gitlab-test_bare + gitlab-test-fork + gitlab-test-fork_bare + ] + end + def factory_repo_path @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name) end -- cgit v1.2.1 From 1be66c4a098290d72cb19b4c844a9bee4eff630b Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Thu, 9 May 2019 16:09:04 +0300 Subject: Hide issue title on unsubscribe for anonymous users --- .../sent_notifications_controller_spec.rb | 109 +++++++++++++++++++-- 1 file changed, 99 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 2b9df71aa3a..89857a9d21b 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -4,15 +4,31 @@ require 'rails_helper' describe SentNotificationsController do let(:user) { create(:user) } - let(:project) { create(:project) } - let(:sent_notification) { create(:sent_notification, project: project, noteable: issue, recipient: user) } + let(:project) { create(:project, :public) } + let(:private_project) { create(:project, :private) } + let(:sent_notification) { create(:sent_notification, project: target_project, noteable: noteable, recipient: user) } let(:issue) do - create(:issue, project: project, author: user) do |issue| - issue.subscriptions.create(user: user, project: project, subscribed: true) + create(:issue, project: target_project) do |issue| + issue.subscriptions.create(user: user, project: target_project, subscribed: true) end end + let(:confidential_issue) do + create(:issue, project: target_project, confidential: true) do |issue| + issue.subscriptions.create(user: user, project: target_project, subscribed: true) + end + end + + let(:merge_request) do + create(:merge_request, source_project: target_project, target_project: target_project) do |mr| + mr.subscriptions.create(user: user, project: target_project, subscribed: true) + end + end + + let(:noteable) { issue } + let(:target_project) { project } + describe 'GET unsubscribe' do context 'when the user is not logged in' do context 'when the force param is passed' do @@ -34,20 +50,93 @@ describe SentNotificationsController do end context 'when the force param is not passed' do + render_views + before do get(:unsubscribe, params: { id: sent_notification.reply_key }) end - it 'does not unsubscribe the user' do - expect(issue.subscribed?(user, project)).to be_truthy + shared_examples 'unsubscribing as anonymous' do + it 'does not unsubscribe the user' do + expect(noteable.subscribed?(user, target_project)).to be_truthy + end + + it 'does not set the flash message' do + expect(controller).not_to set_flash[:notice] + end + + it 'renders unsubscribe page' do + expect(response.status).to eq(200) + expect(response).to render_template :unsubscribe + end end - it 'does not set the flash message' do - expect(controller).not_to set_flash[:notice] + context 'when project is public' do + context 'when unsubscribing from issue' do + let(:noteable) { issue } + + it 'shows issue title' do + expect(response.body).to include(issue.title) + end + + it_behaves_like 'unsubscribing as anonymous' + end + + context 'when unsubscribing from confidential issue' do + let(:noteable) { confidential_issue } + + it 'does not show issue title' do + expect(response.body).not_to include(confidential_issue.title) + expect(response.body).to include(confidential_issue.to_reference) + end + + it_behaves_like 'unsubscribing as anonymous' + end + + context 'when unsubscribing from merge request' do + let(:noteable) { merge_request } + + it 'shows merge request title' do + expect(response.body).to include(merge_request.title) + end + + it_behaves_like 'unsubscribing as anonymous' + end end - it 'redirects to the login page' do - expect(response).to render_template :unsubscribe + context 'when project is not public' do + let(:target_project) { private_project } + + context 'when unsubscribing from issue' do + let(:noteable) { issue } + + it 'shows issue title' do + expect(response.body).not_to include(issue.title) + end + + it_behaves_like 'unsubscribing as anonymous' + end + + context 'when unsubscribing from confidential issue' do + let(:noteable) { confidential_issue } + + it 'does not show issue title' do + expect(response.body).not_to include(confidential_issue.title) + expect(response.body).to include(confidential_issue.to_reference) + end + + it_behaves_like 'unsubscribing as anonymous' + end + + context 'when unsubscribing from merge request' do + let(:noteable) { merge_request } + + it 'shows merge request title' do + expect(response.body).not_to include(merge_request.title) + end + + it_behaves_like 'unsubscribing as anonymous' + end end end end -- cgit v1.2.1 From fa5a6ae172584c5c33665a6efa4a6aa4efaea9ad Mon Sep 17 00:00:00 2001 From: Tiger Date: Thu, 11 Apr 2019 13:26:10 +1000 Subject: Stop configuring group clusters on creation Immediate configuration is not ideal for group and instance level clusters as projects that may never be deployed would still have Kubernetes namespaces and service accounts created for them. As of https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25586 we now create only the resources that are required for the project being deployed, at the time of deployment. --- spec/models/ci/build_spec.rb | 20 ++++++------------ spec/services/clusters/refresh_service_spec.rb | 26 ++++-------------------- spec/services/projects/create_service_spec.rb | 27 ------------------------- spec/services/projects/transfer_service_spec.rb | 27 ------------------------- spec/workers/cluster_configure_worker_spec.rb | 21 ++----------------- 5 files changed, 12 insertions(+), 109 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 9b489baf163..5f2e8aa0baa 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2925,26 +2925,18 @@ describe Ci::Build do subject { build.any_unmet_prerequisites? } + before do + allow(build).to receive(:prerequisites).and_return(prerequisites) + end + context 'build has prerequisites' do - before do - allow(build).to receive(:prerequisites).and_return([double]) - end + let(:prerequisites) { [double] } it { is_expected.to be_truthy } - - context 'and the ci_preparing_state feature is disabled' do - before do - stub_feature_flags(ci_preparing_state: false) - end - - it { is_expected.to be_falsey } - end end context 'build does not have prerequisites' do - before do - allow(build).to receive(:prerequisites).and_return([]) - end + let(:prerequisites) { [] } it { is_expected.to be_falsey } end diff --git a/spec/services/clusters/refresh_service_spec.rb b/spec/services/clusters/refresh_service_spec.rb index 94c35228955..5bc8a709941 100644 --- a/spec/services/clusters/refresh_service_spec.rb +++ b/spec/services/clusters/refresh_service_spec.rb @@ -93,32 +93,14 @@ describe Clusters::RefreshService do let(:group) { cluster.group } let(:project) { create(:project, group: group) } - context 'when ci_preparing_state feature flag is enabled' do - include_examples 'does not create a kubernetes namespace' - - context 'when project already has kubernetes namespace' do - before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) - end - - include_examples 'does not create a kubernetes namespace' - end - end + include_examples 'does not create a kubernetes namespace' - context 'when ci_preparing_state feature flag is disabled' do + context 'when project already has kubernetes namespace' do before do - stub_feature_flags(ci_preparing_state: false) + create(:cluster_kubernetes_namespace, project: project, cluster: cluster) end - include_examples 'creates a kubernetes namespace' - - context 'when project already has kubernetes namespace' do - before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) - end - - include_examples 'does not create a kubernetes namespace' - end + include_examples 'does not create a kubernetes namespace' end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index bd7a0c68766..f54f9200661 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -268,33 +268,6 @@ describe Projects::CreateService, '#execute' do end end - context 'when group has kubernetes cluster' do - let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) } - let(:group) { group_cluster.group } - - let(:token) { 'aaaa' } - let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService, execute: true) } - let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) } - - before do - group.add_owner(user) - - stub_feature_flags(ci_preparing_state: false) - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator) - expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher) - end - - it 'creates kubernetes namespace for the project' do - project = create_project(user, opts.merge!(namespace_id: group.id)) - - expect(project).to be_valid - - kubernetes_namespace = group_cluster.kubernetes_namespaces.first - expect(kubernetes_namespace).to be_present - expect(kubernetes_namespace.project).to eq(project) - end - end - context 'when there is an active service template' do before do create(:service, project: nil, template: true, active: true) diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 9acc3657fa9..a47c10d991a 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -73,33 +73,6 @@ describe Projects::TransferService do shard_name: project.repository_storage ) end - - context 'new group has a kubernetes cluster' do - let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) } - let(:group) { group_cluster.group } - - let(:token) { 'aaaa' } - let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService, execute: true) } - let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) } - - subject { transfer_project(project, user, group) } - - before do - stub_feature_flags(ci_preparing_state: false) - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator) - expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher) - end - - it 'creates kubernetes namespace for the project' do - subject - - expect(project.kubernetes_namespaces.count).to eq(1) - - kubernetes_namespace = group_cluster.kubernetes_namespaces.first - expect(kubernetes_namespace).to be_present - expect(kubernetes_namespace.project).to eq(project) - end - end end context 'when transfer fails' do diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb index daf014ac574..975088f3ee6 100644 --- a/spec/workers/cluster_configure_worker_spec.rb +++ b/spec/workers/cluster_configure_worker_spec.rb @@ -4,11 +4,6 @@ require 'spec_helper' describe ClusterConfigureWorker, '#perform' do let(:worker) { described_class.new } - let(:ci_preparing_state_enabled) { false } - - before do - stub_feature_flags(ci_preparing_state: ci_preparing_state_enabled) - end shared_examples 'configured cluster' do it 'creates a namespace' do @@ -33,26 +28,14 @@ describe ClusterConfigureWorker, '#perform' do context 'when group has a project' do let!(:project) { create(:project, group: group) } - it_behaves_like 'configured cluster' - - context 'ci_preparing_state feature is enabled' do - let(:ci_preparing_state_enabled) { true } - - it_behaves_like 'unconfigured cluster' - end + it_behaves_like 'unconfigured cluster' end context 'when group has project in a sub-group' do let!(:subgroup) { create(:group, parent: group) } let!(:project) { create(:project, group: subgroup) } - it_behaves_like 'configured cluster' - - context 'ci_preparing_state feature is enabled' do - let(:ci_preparing_state_enabled) { true } - - it_behaves_like 'unconfigured cluster' - end + it_behaves_like 'unconfigured cluster' end end -- cgit v1.2.1 From 515f3eb44ab6e11c124dc4a7cc34c94e765783d5 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Sun, 12 May 2019 18:47:15 -0500 Subject: Remove the mr_push_options feature flag `Gitlab::QueryLimiting.whitelist` has been moved from being inside the feature flag conditional check to the `process_mr_push_options` `Api::Internal` helper. https://gitlab.com/gitlab-org/gitlab-ce/issues/60250 --- spec/requests/api/internal_spec.rb | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index bae0302f3ff..fcbff19bd61 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -997,18 +997,6 @@ describe API::Internal do expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') end - - context 'when the feature is disabled' do - it 'does not invoke MergeRequests::PushOptionsHandlerService' do - stub_feature_flags(mr_push_options: false) - - expect(MergeRequests::PushOptionsHandlerService).not_to receive(:new) - - expect do - post api('/internal/post_receive'), params: valid_params - end.not_to change { MergeRequest.count } - end - end end context 'broadcast message exists' do -- cgit v1.2.1 From a4e9259d8a39c0f1777af025fd7255dafb5d0e44 Mon Sep 17 00:00:00 2001 From: Lee Tickett Date: Fri, 17 May 2019 06:10:08 +0000 Subject: #46469 - Added ref querystring parameter to project search to allow searching on branches other than master --- spec/requests/api/search_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 49672591b3b..7d61ec9c4d8 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -414,6 +414,13 @@ describe API::Search do expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(11) end + + it 'by ref' do + get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'This file is used in tests for ci_environments_status', ref: 'pages-deploy' } + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to eq(1) + end end end end -- cgit v1.2.1 From a1f44c1b4969847fa80d6c53d6bd70813d273d6c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 15 May 2019 09:26:18 -0700 Subject: Fix incorrect prefix used in new uploads for personal snippets https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24550 fixed the case where the wrong path on disk was being searched, but it inadvertently ommitted the `/uploads/-/system` prefix when rendering the Markdown for personal snippet uploads when they were stored directly in object storage. A personal snippet path is stored using FileUploader#upload_path. The format for the path: Local storage: :random_hex/:filename. Object storage: personal_snippet/:id/:random_hex/:filename. upload_paths represent the possible paths for a given identifier, which will vary depending on whether the file is stored in local or object storage. upload_path should match an element in upload_paths. base_dir represents the path seen by the user in Markdown, and it should always be prefixed with uploads/-/system. store_dirs represent the paths that are actually used on disk. For object storage, this should omit the prefix /uploads/-/system. For example, consider the requested path /uploads/-/system/personal_snippet/172/ff4ad5c2/file.png. For local storage: base_dir: uploads/-/system/personal_snippet/172 upload_path: ff4ad5c2/file.png upload_paths: ["ff4ad5c2/file.png", "personal_snippet/172/ff4ad5c2/file.png"]. store_dirs: {1=>"uploads/-/system/personal_snippet/172/ff4ad5c2", 2=>"personal_snippet/172/ff4ad5c2"} For object storage: upload_path: personal_snippet/172/ff4ad5c2/file.png Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/61671 --- spec/uploaders/personal_file_uploader_spec.rb | 60 ++++++++++++++++++--------- 1 file changed, 40 insertions(+), 20 deletions(-) (limited to 'spec') diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb index 97758f0243e..d9f0e2f3cb7 100644 --- a/spec/uploaders/personal_file_uploader_spec.rb +++ b/spec/uploaders/personal_file_uploader_spec.rb @@ -7,33 +7,19 @@ describe PersonalFileUploader do subject { uploader } - it_behaves_like 'builds correct paths', - store_dir: %r[uploads/-/system/personal_snippet/\d+], - upload_path: %r[\h+/\S+], - absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet\/\d+\/\h+\/\S+$] - - context "object_store is REMOTE" do + shared_examples '#base_dir' do before do - stub_uploads_object_storage + subject.instance_variable_set(:@secret, 'secret') end - include_context 'with storage', described_class::Store::REMOTE - - it_behaves_like 'builds correct paths', - store_dir: %r[\d+/\h+], - upload_path: %r[^personal_snippet\/\d+\/\h+\/] - end - - describe '#upload_paths' do - it 'builds correct paths for both local and remote storage' do - paths = uploader.upload_paths('test.jpg') + it 'is prefixed with uploads/-/system' do + allow(uploader).to receive(:file).and_return(double(extension: 'txt', filename: 'file_name')) - expect(paths.first).to match(%r[\h+\/test.jpg]) - expect(paths.second).to match(%r[^personal_snippet\/\d+\/\h+\/test.jpg]) + expect(described_class.base_dir(model)).to eq("uploads/-/system/personal_snippet/#{model.id}") end end - describe '#to_h' do + shared_examples '#to_h' do before do subject.instance_variable_set(:@secret, 'secret') end @@ -50,6 +36,40 @@ describe PersonalFileUploader do end end + describe '#upload_paths' do + it 'builds correct paths for both local and remote storage' do + paths = uploader.upload_paths('test.jpg') + + expect(paths.first).to match(%r[\h+\/test.jpg]) + expect(paths.second).to match(%r[^personal_snippet\/\d+\/\h+\/test.jpg]) + end + end + + context 'object_store is LOCAL' do + it_behaves_like 'builds correct paths', + store_dir: %r[uploads/-/system/personal_snippet/\d+/\h+], + upload_path: %r[\h+/\S+], + absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet\/\d+\/\h+\/\S+$] + + it_behaves_like '#base_dir' + it_behaves_like '#to_h' + end + + context "object_store is REMOTE" do + before do + stub_uploads_object_storage + end + + include_context 'with storage', described_class::Store::REMOTE + + it_behaves_like 'builds correct paths', + store_dir: %r[\d+/\h+], + upload_path: %r[^personal_snippet\/\d+\/\h+\/] + + it_behaves_like '#base_dir' + it_behaves_like '#to_h' + end + describe "#migrate!" do before do uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt')) -- cgit v1.2.1 From e938d241ea5b574e82ec44951f14ac391c6d52d1 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Fri, 17 May 2019 16:39:33 +0800 Subject: Include type to notes import / export --- spec/lib/gitlab/import_export/project_tree_saver_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index cfc3e0ce926..bc4f867e891 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -91,7 +91,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do end it 'has issue comments' do - expect(saved_project_json['issues'].first['notes']).not_to be_empty + notes = saved_project_json['issues'].first['notes'] + + expect(notes).not_to be_empty + expect(notes.first['type']).to eq('DiscussionNote') end it 'has issue assignees' do @@ -299,7 +302,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do create(:commit_status, project: project, pipeline: ci_build.pipeline) create(:milestone, project: project) - create(:note, noteable: issue, project: project) + create(:discussion_note, noteable: issue, project: project) create(:note, noteable: merge_request, project: project) create(:note, noteable: snippet, project: project) create(:note_on_commit, -- cgit v1.2.1 From a3f80f9f2e83298a0fe0cf6637871822bb1a8edb Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 17 May 2019 09:10:29 +0000 Subject: Add PoolRepository to the usage ping PoolRepository is a relatively new model of which the counts could help to further determine the priority of new features. Also gives some insight into the number of forks customers have. --- spec/lib/gitlab/usage_data_spec.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index aa975c8bb0b..e44463dd767 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -63,12 +63,7 @@ describe Gitlab::UsageData do end it "gathers usage counts" do - count_data = subject[:counts] - - expect(count_data[:boards]).to eq(1) - expect(count_data[:projects]).to eq(3) - - expect(count_data.keys).to include(*%i( + expected_keys = %i( assignee_lists boards ci_builds @@ -112,6 +107,7 @@ describe Gitlab::UsageData do milestone_lists milestones notes + pool_repositories projects projects_imported_from_github projects_jira_active @@ -132,7 +128,14 @@ describe Gitlab::UsageData do uploads web_hooks user_preferences - )) + ) + + count_data = subject[:counts] + + expect(count_data[:boards]).to eq(1) + expect(count_data[:projects]).to eq(3) + expect(count_data.keys).to include(*expected_keys) + expect(expected_keys - count_data.keys).to be_empty end it 'does not gather user preferences usage data when the feature is disabled' do @@ -211,7 +214,7 @@ describe Gitlab::UsageData do it "gathers license data" do expect(subject[:uuid]).to eq(Gitlab::CurrentSettings.uuid) expect(subject[:version]).to eq(Gitlab::VERSION) - expect(subject[:installation_type]).to eq(Gitlab::INSTALLATION_TYPE) + expect(subject[:installation_type]).to eq('gitlab-development-kit') expect(subject[:active_user_count]).to eq(User.active.count) expect(subject[:recorded_at]).to be_a(Time) end -- cgit v1.2.1 From 9012d309f13ec7595f2495d73549ded81432fb13 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Fri, 17 May 2019 10:08:20 +0000 Subject: Increase Jest timeout to 500ms --- spec/frontend/gfm_auto_complete_spec.js | 3 --- spec/frontend/notes/components/discussion_notes_spec.js | 3 --- spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js | 3 --- spec/frontend/test_setup.js | 2 +- 4 files changed, 1 insertion(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index 3886853f3c1..8af49fd47a2 100644 --- a/spec/frontend/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -7,11 +7,8 @@ import 'jquery.caret'; import 'at.js'; import { TEST_HOST } from 'helpers/test_constants'; -import { setTestTimeout } from 'helpers/timeout'; import { getJSONFixture } from 'helpers/fixtures'; -setTestTimeout(500); - const labelsFixture = getJSONFixture('autocomplete_sources/labels.json'); describe('GfmAutoComplete', () => { diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js index 7e037beca9d..c3204b3aaa0 100644 --- a/spec/frontend/notes/components/discussion_notes_spec.js +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -8,7 +8,6 @@ import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_sys import SystemNote from '~/vue_shared/components/notes/system_note.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import createStore from '~/notes/stores'; -import { setTestTimeout } from 'helpers/timeout'; import { noteableDataMock, discussionMock, @@ -18,8 +17,6 @@ import { const localVue = createLocalVue(); describe('DiscussionNotes', () => { - setTestTimeout(500); - let wrapper; const createComponent = props => { diff --git a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js index f163bdd9913..7e9aec84016 100644 --- a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js +++ b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js @@ -1,9 +1,6 @@ import $ from 'jquery'; import '~/lib/utils/text_utility'; import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; -import { setTestTimeout } from 'helpers/timeout'; - -setTestTimeout(500); describe('Abuse Reports', () => { const FIXTURE = 'abuse_reports/abuse_reports_list.html'; diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index f0d62a3c41c..c24f0bc4776 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -15,7 +15,7 @@ afterEach(() => }), ); -initializeTestTimeout(300); +initializeTestTimeout(500); // fail tests for unmocked requests beforeEach(done => { -- cgit v1.2.1 From 9ff6edf690423a284f4d0ad924ff2a9a4285eb50 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Fri, 17 May 2019 12:46:33 +0300 Subject: Review updates and cleanup * Cleaned issues and issues_statistics docs * Renamed param with_labels_data to with_labels_details * Added spec for N+1 check when retrieving labels from issue * Refactoed CheckAssigneesCount validation class --- spec/requests/api/issues/issues_spec.rb | 51 ++++++++++++++++------ .../requests/api/issues_shared_example_spec.rb | 6 +-- 2 files changed, 41 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 24c53d9c68f..9b9cc778fb3 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -122,25 +122,25 @@ describe API::Issues do expect_paginated_array_response([issue.id, closed_issue.id]) end - it 'returns authentication error without any scope' do - get api('/issues_statistics') + context 'issues_statistics' do + it 'returns authentication error without any scope' do + get api('/issues_statistics') - expect(response).to have_http_status(401) - end + expect(response).to have_http_status(401) + end - it 'returns authentication error when scope is assigned-to-me' do - get api('/issues_statistics'), params: { scope: 'assigned-to-me' } + it 'returns authentication error when scope is assigned_to_me' do + get api('/issues_statistics'), params: { scope: 'assigned_to_me' } - expect(response).to have_http_status(401) - end + expect(response).to have_http_status(401) + end - it 'returns authentication error when scope is created-by-me' do - get api('/issues_statistics'), params: { scope: 'created-by-me' } + it 'returns authentication error when scope is created_by_me' do + get api('/issues_statistics'), params: { scope: 'created_by_me' } - expect(response).to have_http_status(401) - end + expect(response).to have_http_status(401) + end - context 'issues_statistics' do context 'no state is treated as all state' do let(:params) { {} } let(:counts) { { all: 2, closed: 1, opened: 1 } } @@ -386,6 +386,31 @@ describe API::Issues do end context 'filter by labels or label_name param' do + context 'N+1' do + let(:label_b) { create(:label, title: 'foo', project: project) } + let(:label_c) { create(:label, title: 'bar', project: project) } + + before do + create(:label_link, label: label_b, target: issue) + create(:label_link, label: label_c, target: issue) + end + it 'tests N+1' do + control = ActiveRecord::QueryRecorder.new do + get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] } + end + + label_d = create(:label, title: 'dar', project: project) + label_e = create(:label, title: 'ear', project: project) + create(:label_link, label: label_d, target: issue) + create(:label_link, label: label_e, target: issue) + + expect do + get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] } + end.not_to exceed_query_limit(control) + expect(issue.labels.count).to eq(5) + end + end + it 'returns an array of labeled issues' do get api('/issues', user), params: { labels: label.title } diff --git a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb index d1c8e2208a3..1133e95e44e 100644 --- a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb +++ b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb @@ -28,15 +28,15 @@ shared_examples 'labeled issues with labels and label_name params' do it_behaves_like 'returns label names' end - context 'when with_labels_data provided' do + context 'when with_labels_details provided' do context 'array of labeled issues when all labels match' do - let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } } + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } } it_behaves_like 'returns basic label entity' end context 'array of labeled issues when all labels match with labels param as array' do - let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_data: true } } + let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } } it_behaves_like 'returns basic label entity' end -- cgit v1.2.1 From 2a4a732db94652a131aae0fc57801a8f09fabead Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Fri, 17 May 2019 14:05:15 -0300 Subject: Fix throttling issue in form dirty checking This commit fixes an issue that was causing the "Save changes" button to be incorrectly enabled or disabled when changes were made to a form. (Specifically, some of the subsections in the project settings pages.) --- .../dirty_submit/dirty_submit_form_spec.js | 111 +++++++++++++++------ 1 file changed, 83 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js index 95cc90dcb0f..b1017e0c4f0 100644 --- a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js +++ b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import DirtySubmitForm from '~/dirty_submit/dirty_submit_form'; import { getInputValue, setInputValue, createForm } from './helper'; @@ -13,46 +14,100 @@ function expectToToggleDisableOnDirtyUpdate(submit, input) { } describe('DirtySubmitForm', () => { - DirtySubmitForm.THROTTLE_DURATION = 0; + const originalThrottleDuration = DirtySubmitForm.THROTTLE_DURATION; - it('disables submit until there are changes', done => { - const { form, input, submit } = createForm(); + describe('submit button tests', () => { + beforeEach(() => { + DirtySubmitForm.THROTTLE_DURATION = 0; + }); - new DirtySubmitForm(form); // eslint-disable-line no-new + afterEach(() => { + DirtySubmitForm.THROTTLE_DURATION = originalThrottleDuration; + }); - return expectToToggleDisableOnDirtyUpdate(submit, input) - .then(done) - .catch(done.fail); - }); + it('disables submit until there are changes', done => { + const { form, input, submit } = createForm(); - 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 - new DirtySubmitForm(form); // eslint-disable-line no-new + return expectToToggleDisableOnDirtyUpdate(submit, input) + .then(done) + .catch(done.fail); + }); - 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 expectToToggleDisableOnDirtyUpdate(submit, input) + .then(done) + .catch(done.fail); + }); - it('disables submit until there are changes for radio inputs', done => { - const { form, input, submit } = createForm('radio'); + it('disables submit until there are changes for radio inputs', done => { + const { form, input, submit } = createForm('radio'); - new DirtySubmitForm(form); // eslint-disable-line no-new + new DirtySubmitForm(form); // eslint-disable-line no-new - return expectToToggleDisableOnDirtyUpdate(submit, input) - .then(done) - .catch(done.fail); + return expectToToggleDisableOnDirtyUpdate(submit, input) + .then(done) + .catch(done.fail); + }); + + it('disables submit until there are changes for checkbox inputs', done => { + const { form, input, submit } = createForm('checkbox'); + + new DirtySubmitForm(form); // eslint-disable-line no-new + + return expectToToggleDisableOnDirtyUpdate(submit, input) + .then(done) + .catch(done.fail); + }); }); - it('disables submit until there are changes for checkbox inputs', done => { - const { form, input, submit } = createForm('checkbox'); + describe('throttling tests', () => { + beforeEach(() => { + jasmine.clock().install(); + DirtySubmitForm.THROTTLE_DURATION = 100; + }); + + afterEach(() => { + jasmine.clock().uninstall(); + DirtySubmitForm.THROTTLE_DURATION = originalThrottleDuration; + }); + + it('throttles updates when rapid changes are made to a single form element', () => { + const { form, input } = createForm(); + const updateDirtyInputSpy = spyOn(new DirtySubmitForm(form), 'updateDirtyInput'); + + _.range(10).forEach(i => { + setInputValue(input, `change ${i}`, false); + }); + + jasmine.clock().tick(101); + + expect(updateDirtyInputSpy).toHaveBeenCalledTimes(2); + }); + + it('does not throttle updates when rapid changes are made to different form elements', () => { + const form = document.createElement('form'); + const range = _.range(10); + range.forEach(i => { + form.innerHTML += ``; + }); + + const updateDirtyInputSpy = spyOn(new DirtySubmitForm(form), 'updateDirtyInput'); + + range.forEach(i => { + const input = form.querySelector(`.js-input-${i}`); + setInputValue(input, `change`, false); + }); - new DirtySubmitForm(form); // eslint-disable-line no-new + jasmine.clock().tick(101); - return expectToToggleDisableOnDirtyUpdate(submit, input) - .then(done) - .catch(done.fail); + expect(updateDirtyInputSpy).toHaveBeenCalledTimes(range.length); + }); }); }); -- cgit v1.2.1 From 6c6e4ca495f56ec9df8e5e6ec744744404414f8c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 17 May 2019 14:43:58 -0700 Subject: Fix remote mirrors not updating after tag push Remote mirrors were only being updated after pushes to branches, not tags. This change consolidates the functionality into Git::BaseHooksService so that both tags and branches will now update remote mirrors. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/51240 --- spec/services/git/base_hooks_service_spec.rb | 90 ++++++++++++++++++++++++++ spec/services/git/branch_hooks_service_spec.rb | 6 ++ spec/services/git/branch_push_service_spec.rb | 66 ------------------- spec/services/git/tag_hooks_service_spec.rb | 6 ++ 4 files changed, 102 insertions(+), 66 deletions(-) create mode 100644 spec/services/git/base_hooks_service_spec.rb (limited to 'spec') diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb new file mode 100644 index 00000000000..4a2ec769116 --- /dev/null +++ b/spec/services/git/base_hooks_service_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Git::BaseHooksService do + include RepoHelpers + include GitHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:service) { described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) } + + let(:oldrev) { Gitlab::Git::BLANK_SHA } + let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0 + let(:ref) { 'refs/tags/v1.1.0' } + + describe 'with remote mirrors' do + class TestService < described_class + def commits + [] + end + end + + let(:project) { create(:project, :repository, :remote_mirror) } + + subject { TestService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) } + + before do + expect(subject).to receive(:execute_project_hooks) + end + + context 'when remote mirror feature is enabled' do + it 'fails stuck remote mirrors' do + allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) + expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) + + subject.execute + end + + it 'updates remote mirrors' do + expect(project).to receive(:update_remote_mirrors) + + subject.execute + end + end + + context 'when remote mirror feature is disabled' do + before do + stub_application_setting(mirror_available: false) + end + + context 'with remote mirrors global setting overridden' do + before do + project.remote_mirror_available_overridden = true + end + + it 'fails stuck remote mirrors' do + allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) + expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) + + subject.execute + end + + it 'updates remote mirrors' do + expect(project).to receive(:update_remote_mirrors) + + subject.execute + end + end + + context 'without remote mirrors global setting overridden' do + before do + project.remote_mirror_available_overridden = false + end + + it 'does not fails stuck remote mirrors' do + expect(project).not_to receive(:mark_stuck_remote_mirrors_as_failed!) + + subject.execute + end + + it 'does not updates remote mirrors' do + expect(project).not_to receive(:update_remote_mirrors) + + subject.execute + end + end + end + end +end diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb index 4895e762602..22faa996015 100644 --- a/spec/services/git/branch_hooks_service_spec.rb +++ b/spec/services/git/branch_hooks_service_spec.rb @@ -18,6 +18,12 @@ describe Git::BranchHooksService do described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) end + it 'update remote mirrors' do + expect(service).to receive(:update_remote_mirrors).and_call_original + + service.execute + end + describe "Git Push Data" do subject(:push_data) { service.execute } diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index ad21f710833..6e39fa6b3c0 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -17,72 +17,6 @@ describe Git::BranchPushService, services: true do project.add_maintainer(user) end - describe 'with remote mirrors' do - let(:project) { create(:project, :repository, :remote_mirror) } - - subject do - described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) - end - - context 'when remote mirror feature is enabled' do - it 'fails stuck remote mirrors' do - allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) - expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) - - subject.execute - end - - it 'updates remote mirrors' do - expect(project).to receive(:update_remote_mirrors) - - subject.execute - end - end - - context 'when remote mirror feature is disabled' do - before do - stub_application_setting(mirror_available: false) - end - - context 'with remote mirrors global setting overridden' do - before do - project.remote_mirror_available_overridden = true - end - - it 'fails stuck remote mirrors' do - allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors) - expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!) - - subject.execute - end - - it 'updates remote mirrors' do - expect(project).to receive(:update_remote_mirrors) - - subject.execute - end - end - - context 'without remote mirrors global setting overridden' do - before do - project.remote_mirror_available_overridden = false - end - - it 'does not fails stuck remote mirrors' do - expect(project).not_to receive(:mark_stuck_remote_mirrors_as_failed!) - - subject.execute - end - - it 'does not updates remote mirrors' do - expect(project).not_to receive(:update_remote_mirrors) - - subject.execute - end - end - end - end - describe 'Push branches' do subject do execute_service(project, user, oldrev, newrev, ref) diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb index f4c02932f98..f5938a5c708 100644 --- a/spec/services/git/tag_hooks_service_spec.rb +++ b/spec/services/git/tag_hooks_service_spec.rb @@ -18,6 +18,12 @@ describe Git::TagHooksService, :service do described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) end + it 'update remote mirrors' do + expect(service).to receive(:update_remote_mirrors).and_call_original + + service.execute + end + describe 'System hooks' do it 'Executes system hooks' do push_data = service.execute -- cgit v1.2.1 From 2aa024af61787186d881b772fcd04d190eb93aec Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Sun, 19 May 2019 19:55:04 +0000 Subject: Use component selectors in serverless frontend tests --- .../serverless/components/function_row_spec.js | 29 +++++++++++++--------- .../serverless/components/functions_spec.js | 9 ++++--- .../components/missing_prometheus_spec.js | 15 ++++++----- spec/frontend/serverless/components/url_spec.js | 8 +++--- 4 files changed, 37 insertions(+), 24 deletions(-) (limited to 'spec') diff --git a/spec/frontend/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js index 414fdc5cd82..979f98c4832 100644 --- a/spec/frontend/serverless/components/function_row_spec.js +++ b/spec/frontend/serverless/components/function_row_spec.js @@ -1,27 +1,32 @@ import functionRowComponent from '~/serverless/components/function_row.vue'; import { shallowMount } from '@vue/test-utils'; +import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; import { mockServerlessFunction } from '../mock_data'; -const createComponent = func => - shallowMount(functionRowComponent, { propsData: { func }, sync: false }).vm; - describe('functionRowComponent', () => { - it('Parses the function details correctly', () => { - const vm = createComponent(mockServerlessFunction); + let wrapper; - expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); - expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); - expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); + const createComponent = func => { + wrapper = shallowMount(functionRowComponent, { propsData: { func }, sync: false }); + }; - vm.$destroy(); + afterEach(() => { + wrapper.destroy(); + }); + + it('Parses the function details correctly', () => { + createComponent(mockServerlessFunction); + + expect(wrapper.find('b').text()).toBe(mockServerlessFunction.name); + expect(wrapper.find('span').text()).toBe(mockServerlessFunction.image); + expect(wrapper.find(Timeago).attributes('time')).not.toBe(null); }); it('handles clicks correctly', () => { - const vm = createComponent(mockServerlessFunction); + createComponent(mockServerlessFunction); + const { vm } = wrapper; expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row - - vm.$destroy(); }); }); diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js index 7af33ceaadc..6924fb9e91f 100644 --- a/spec/frontend/serverless/components/functions_spec.js +++ b/spec/frontend/serverless/components/functions_spec.js @@ -1,9 +1,12 @@ import Vuex from 'vuex'; +import { GlLoadingIcon } from '@gitlab/ui'; import AxiosMockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import functionsComponent from '~/serverless/components/functions.vue'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createStore } from '~/serverless/store'; +import EmptyState from '~/serverless/components/empty_state.vue'; +import EnvironmentRow from '~/serverless/components/environment_row.vue'; import { TEST_HOST } from 'helpers/test_constants'; import { mockServerlessFunctions } from '../mock_data'; @@ -43,7 +46,7 @@ describe('functionsComponent', () => { sync: false, }); - expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); + expect(component.find(EmptyState).exists()).toBe(true); }); it('should render a loading component', () => { @@ -60,7 +63,7 @@ describe('functionsComponent', () => { sync: false, }); - expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); + expect(component.find(GlLoadingIcon).exists()).toBe(true); }); it('should render empty state when there is no function data', () => { @@ -104,7 +107,7 @@ describe('functionsComponent', () => { component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); return component.vm.$nextTick().then(() => { - expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); + expect(component.find(EnvironmentRow).exists()).toBe(true); }); }); }); diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js index d0df6125290..5dbdccde2de 100644 --- a/spec/frontend/serverless/components/missing_prometheus_spec.js +++ b/spec/frontend/serverless/components/missing_prometheus_spec.js @@ -1,3 +1,4 @@ +import { GlButton } from '@gitlab/ui'; import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; import { shallowMount } from '@vue/test-utils'; @@ -9,27 +10,29 @@ const createComponent = missingData => missingData, }, sync: false, - }).vm; + }); describe('missingPrometheusComponent', () => { - let vm; + let wrapper; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('should render missing prometheus message', () => { - vm = createComponent(false); + wrapper = createComponent(false); + const { vm } = wrapper; expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( 'Function invocation metrics require Prometheus to be installed first.', ); - expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); + expect(wrapper.find(GlButton).attributes('variant')).toBe('success'); }); it('should render no prometheus data message', () => { - vm = createComponent(true); + wrapper = createComponent(true); + const { vm } = wrapper; expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( 'Invocation metrics loading or not available at this time.', diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js index d05a9bba103..706441e8a8b 100644 --- a/spec/frontend/serverless/components/url_spec.js +++ b/spec/frontend/serverless/components/url_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import urlComponent from '~/serverless/components/url.vue'; import { shallowMount } from '@vue/test-utils'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; const createComponent = uri => shallowMount(Vue.extend(urlComponent), { @@ -8,15 +9,16 @@ const createComponent = uri => uri, }, sync: false, - }).vm; + }); describe('urlComponent', () => { it('should render correctly', () => { const uri = 'http://testfunc.apps.example.com'; - const vm = createComponent(uri); + const wrapper = createComponent(uri); + const { vm } = wrapper; expect(vm.$el.classList.contains('clipboard-group')).toBe(true); - expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); + expect(wrapper.find(ClipboardButton).attributes('text')).toEqual(uri); expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); -- cgit v1.2.1 From 0398213d207fcc03cdb3f8dfd02a61f207a8eea9 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 3 May 2019 00:07:45 +1200 Subject: Use Environment#deployment_platform method instead As this method does have `deployment_platform(environment: self.name)`, which is what the form EE needs. Allows us to remove two overrides in EE. --- spec/models/environment_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index cfe7c7ef0b0..17246f238e0 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -592,9 +592,7 @@ describe Environment do shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do it 'returns the terminals from the deployment service' do - deployment_platform_target = Gitlab.ee? ? environment : project - - expect(deployment_platform_target.deployment_platform) + expect(environment.deployment_platform) .to receive(:terminals).with(environment) .and_return(:fake_terminals) -- cgit v1.2.1 From 404314c6193d94f106e687db9fc5bd71bab63835 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 20 May 2019 08:41:28 +0000 Subject: Setup Vue app for repository file listing Part of https://gitlab.com/gitlab-org/gitlab-ce/issues/61578 --- .../files_sort_submodules_with_folders_spec.rb | 2 ++ .../projects/files/user_browses_files_spec.rb | 1 + .../projects/files/user_browses_lfs_files_spec.rb | 2 ++ .../projects/files/user_creates_directory_spec.rb | 2 ++ .../projects/files/user_deletes_files_spec.rb | 2 ++ .../projects/files/user_edits_files_spec.rb | 1 + .../projects/files/user_replaces_files_spec.rb | 2 ++ .../projects/files/user_uploads_files_spec.rb | 2 ++ spec/features/projects/tree/tree_show_spec.rb | 1 + spec/features/projects_spec.rb | 1 + spec/frontend/repository/router_spec.js | 23 ++++++++++++++++++++++ spec/views/projects/tree/show.html.haml_spec.rb | 2 ++ 12 files changed, 41 insertions(+) create mode 100644 spec/frontend/repository/router_spec.js (limited to 'spec') diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb index b6dbf76bc9b..51c884201a6 100644 --- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb +++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb @@ -5,6 +5,8 @@ describe 'Projects > Files > User views files page' do let(:user) { project.owner } before do + stub_feature_flags(vue_file_list: false) + sign_in user visit project_tree_path(project, project.repository.root_ref) end diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index 66268355345..a5d849db8a3 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -11,6 +11,7 @@ describe "User browses files" do let(:user) { project.owner } before do + stub_feature_flags(vue_file_list: false) stub_feature_flags(csslab: false) sign_in(user) end diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb index d56476adb05..d5cb8f9212d 100644 --- a/spec/features/projects/files/user_browses_lfs_files_spec.rb +++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb @@ -5,6 +5,8 @@ describe 'Projects > Files > User browses LFS files' do let(:user) { project.owner } before do + stub_feature_flags(vue_file_list: false) + sign_in(user) end diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb index 847b5f0860f..e29e867492e 100644 --- a/spec/features/projects/files/user_creates_directory_spec.rb +++ b/spec/features/projects/files/user_creates_directory_spec.rb @@ -11,6 +11,8 @@ describe 'Projects > Files > User creates a directory', :js do let(:user) { create(:user) } before do + stub_feature_flags(vue_file_list: false) + project.add_developer(user) sign_in(user) visit project_tree_path(project, 'master') diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb index 614b11fa5c8..11ee87f245b 100644 --- a/spec/features/projects/files/user_deletes_files_spec.rb +++ b/spec/features/projects/files/user_deletes_files_spec.rb @@ -12,6 +12,8 @@ describe 'Projects > Files > User deletes files', :js do let(:user) { create(:user) } before do + stub_feature_flags(vue_file_list: false) + sign_in(user) end diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index 2de22582b2c..26efb5e6787 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -10,6 +10,7 @@ describe 'Projects > Files > User edits files', :js do before do stub_feature_flags(web_ide_default: false) + stub_feature_flags(vue_file_list: false) sign_in(user) end diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb index e3da28d73c3..bfd612e4cc8 100644 --- a/spec/features/projects/files/user_replaces_files_spec.rb +++ b/spec/features/projects/files/user_replaces_files_spec.rb @@ -14,6 +14,8 @@ describe 'Projects > Files > User replaces files', :js do let(:user) { create(:user) } before do + stub_feature_flags(vue_file_list: false) + sign_in(user) end diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb index af3fc528a20..25ff3fdf411 100644 --- a/spec/features/projects/files/user_uploads_files_spec.rb +++ b/spec/features/projects/files/user_uploads_files_spec.rb @@ -14,6 +14,8 @@ describe 'Projects > Files > User uploads files' do let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } before do + stub_feature_flags(vue_file_list: false) + project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb index 45e81e1c040..3ccea2db705 100644 --- a/spec/features/projects/tree/tree_show_spec.rb +++ b/spec/features/projects/tree/tree_show_spec.rb @@ -8,6 +8,7 @@ describe 'Projects tree', :js do let(:test_sha) { '7975be0116940bf2ad4321f79d02a55c5f7779aa' } before do + stub_feature_flags(vue_file_list: false) project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index ff4e6197746..4fe45311b2d 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -5,6 +5,7 @@ describe 'Project' do include MobileHelpers before do + stub_feature_flags(vue_file_list: false) stub_feature_flags(approval_rules: false) end diff --git a/spec/frontend/repository/router_spec.js b/spec/frontend/repository/router_spec.js new file mode 100644 index 00000000000..f61a0ccd1e6 --- /dev/null +++ b/spec/frontend/repository/router_spec.js @@ -0,0 +1,23 @@ +import IndexPage from '~/repository/pages/index.vue'; +import TreePage from '~/repository/pages/tree.vue'; +import createRouter from '~/repository/router'; + +describe('Repository router spec', () => { + it.each` + path | component | componentName + ${'/'} | ${IndexPage} | ${'IndexPage'} + ${'/tree/master'} | ${TreePage} | ${'TreePage'} + ${'/tree/master/app/assets'} | ${TreePage} | ${'TreePage'} + ${'/tree/123/app/assets'} | ${null} | ${'null'} + `('sets component as $componentName for path "$path"', ({ path, component }) => { + const router = createRouter('', 'master'); + + const componentsForRoute = router.getMatchedComponents(path); + + expect(componentsForRoute.length).toBe(component ? 1 : 0); + + if (component) { + expect(componentsForRoute).toContain(component); + } + }); +}); diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb index 3b098320ad7..5bb0173ab89 100644 --- a/spec/views/projects/tree/show.html.haml_spec.rb +++ b/spec/views/projects/tree/show.html.haml_spec.rb @@ -7,6 +7,8 @@ describe 'projects/tree/show' do let(:repository) { project.repository } before do + stub_feature_flags(vue_file_list: false) + assign(:project, project) assign(:repository, repository) assign(:lfs_blob_ids, []) -- cgit v1.2.1 From 6bec8cbdebdf598285aaf91440a6992a43d26927 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Mon, 20 May 2019 08:44:20 +0000 Subject: Remove remove_disabled_domains feature flag --- spec/workers/pages_domain_removal_cron_worker_spec.rb | 15 --------------- 1 file changed, 15 deletions(-) (limited to 'spec') diff --git a/spec/workers/pages_domain_removal_cron_worker_spec.rb b/spec/workers/pages_domain_removal_cron_worker_spec.rb index 0e1171e8491..2408ad54189 100644 --- a/spec/workers/pages_domain_removal_cron_worker_spec.rb +++ b/spec/workers/pages_domain_removal_cron_worker_spec.rb @@ -9,25 +9,10 @@ describe PagesDomainRemovalCronWorker do context 'when there is domain which should be removed' do let!(:domain_for_removal) { create(:pages_domain, :should_be_removed) } - before do - stub_feature_flags(remove_disabled_domains: true) - end - it 'removes domain' do expect { worker.perform }.to change { PagesDomain.count }.by(-1) expect(PagesDomain.exists?).to eq(false) end - - context 'when domain removal is disabled' do - before do - stub_feature_flags(remove_disabled_domains: false) - end - - it 'does not remove pages domain' do - expect { worker.perform }.not_to change { PagesDomain.count } - expect(PagesDomain.find_by(domain: domain_for_removal.domain)).to be_present - end - end end context 'where there is a domain which scheduled for removal in the future' do -- cgit v1.2.1 From a3014debbd9435c90a86be398d33b1cd3453f10c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 16 May 2019 09:18:52 +0100 Subject: Added table component for files listing Part of https://gitlab.com/gitlab-org/gitlab-ce/issues/61578 --- .../repository/components/table/index_spec.js | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 spec/frontend/repository/components/table/index_spec.js (limited to 'spec') diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js new file mode 100644 index 00000000000..6f52cffe077 --- /dev/null +++ b/spec/frontend/repository/components/table/index_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import Table from '~/repository/components/table/index.vue'; + +let vm; + +function factory(path, loading = false) { + vm = shallowMount(Table, { + propsData: { + path, + }, + mocks: { + $apollo: { + queries: { + files: { loading }, + }, + }, + }, + }); +} + +describe('Repository table component', () => { + afterEach(() => { + vm.destroy(); + }); + + it.each` + path | ref + ${'/'} | ${'master'} + ${'app/assets'} | ${'master'} + ${'/'} | ${'test'} + `('renders table caption for $ref in $path', ({ path, ref }) => { + factory(path); + + vm.setData({ ref }); + + expect(vm.find('caption').text()).toEqual( + `Files, directories, and submodules in the path ${path} for commit reference ${ref}`, + ); + }); + + it('renders loading icon', () => { + factory('/', true); + + expect(vm.find(GlLoadingIcon).exists()).toBe(true); + }); +}); -- cgit v1.2.1 From 3e417f4ba22ed53f808c3e0fdc5de0350d62d2c2 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 20 May 2019 11:05:26 +0000 Subject: Create empty Vuex store for issue boards --- spec/frontend/boards/stores/actions_spec.js | 67 ++++++++++++++++++++ spec/frontend/boards/stores/mutations_spec.js | 91 +++++++++++++++++++++++++++ spec/frontend/boards/stores/state_spec.js | 11 ++++ 3 files changed, 169 insertions(+) create mode 100644 spec/frontend/boards/stores/actions_spec.js create mode 100644 spec/frontend/boards/stores/mutations_spec.js create mode 100644 spec/frontend/boards/stores/state_spec.js (limited to 'spec') diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js new file mode 100644 index 00000000000..d23393db60d --- /dev/null +++ b/spec/frontend/boards/stores/actions_spec.js @@ -0,0 +1,67 @@ +import actions from '~/boards/stores/actions'; + +const expectNotImplemented = action => { + it('is not implemented', () => { + expect(action).toThrow(new Error('Not implemented!')); + }); +}; + +describe('setEndpoints', () => { + expectNotImplemented(actions.setEndpoints); +}); + +describe('fetchLists', () => { + expectNotImplemented(actions.fetchLists); +}); + +describe('generateDefaultLists', () => { + expectNotImplemented(actions.generateDefaultLists); +}); + +describe('createList', () => { + expectNotImplemented(actions.createList); +}); + +describe('updateList', () => { + expectNotImplemented(actions.updateList); +}); + +describe('deleteList', () => { + expectNotImplemented(actions.deleteList); +}); + +describe('fetchIssuesForList', () => { + expectNotImplemented(actions.fetchIssuesForList); +}); + +describe('moveIssue', () => { + expectNotImplemented(actions.moveIssue); +}); + +describe('createNewIssue', () => { + expectNotImplemented(actions.createNewIssue); +}); + +describe('fetchBacklog', () => { + expectNotImplemented(actions.fetchBacklog); +}); + +describe('bulkUpdateIssues', () => { + expectNotImplemented(actions.bulkUpdateIssues); +}); + +describe('fetchIssue', () => { + expectNotImplemented(actions.fetchIssue); +}); + +describe('toggleIssueSubscription', () => { + expectNotImplemented(actions.toggleIssueSubscription); +}); + +describe('showPage', () => { + expectNotImplemented(actions.showPage); +}); + +describe('toggleEmptyState', () => { + expectNotImplemented(actions.toggleEmptyState); +}); diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js new file mode 100644 index 00000000000..aa477766978 --- /dev/null +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -0,0 +1,91 @@ +import mutations from '~/boards/stores/mutations'; + +const expectNotImplemented = action => { + it('is not implemented', () => { + expect(action).toThrow(new Error('Not implemented!')); + }); +}; + +describe('SET_ENDPOINTS', () => { + expectNotImplemented(mutations.SET_ENDPOINTS); +}); + +describe('REQUEST_ADD_LIST', () => { + expectNotImplemented(mutations.REQUEST_ADD_LIST); +}); + +describe('RECEIVE_ADD_LIST_SUCCESS', () => { + expectNotImplemented(mutations.RECEIVE_ADD_LIST_SUCCESS); +}); + +describe('RECEIVE_ADD_LIST_ERROR', () => { + expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR); +}); + +describe('REQUEST_UPDATE_LIST', () => { + expectNotImplemented(mutations.REQUEST_UPDATE_LIST); +}); + +describe('RECEIVE_UPDATE_LIST_SUCCESS', () => { + expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_SUCCESS); +}); + +describe('RECEIVE_UPDATE_LIST_ERROR', () => { + expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_ERROR); +}); + +describe('REQUEST_REMOVE_LIST', () => { + expectNotImplemented(mutations.REQUEST_REMOVE_LIST); +}); + +describe('RECEIVE_REMOVE_LIST_SUCCESS', () => { + expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_SUCCESS); +}); + +describe('RECEIVE_REMOVE_LIST_ERROR', () => { + expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR); +}); + +describe('REQUEST_ADD_ISSUE', () => { + expectNotImplemented(mutations.REQUEST_ADD_ISSUE); +}); + +describe('RECEIVE_ADD_ISSUE_SUCCESS', () => { + expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_SUCCESS); +}); + +describe('RECEIVE_ADD_ISSUE_ERROR', () => { + expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_ERROR); +}); + +describe('REQUEST_MOVE_ISSUE', () => { + expectNotImplemented(mutations.REQUEST_MOVE_ISSUE); +}); + +describe('RECEIVE_MOVE_ISSUE_SUCCESS', () => { + expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_SUCCESS); +}); + +describe('RECEIVE_MOVE_ISSUE_ERROR', () => { + expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_ERROR); +}); + +describe('REQUEST_UPDATE_ISSUE', () => { + expectNotImplemented(mutations.REQUEST_UPDATE_ISSUE); +}); + +describe('RECEIVE_UPDATE_ISSUE_SUCCESS', () => { + expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_SUCCESS); +}); + +describe('RECEIVE_UPDATE_ISSUE_ERROR', () => { + expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR); +}); + +describe('SET_CURRENT_PAGE', () => { + expectNotImplemented(mutations.SET_CURRENT_PAGE); +}); + +describe('TOGGLE_EMPTY_STATE', () => { + expectNotImplemented(mutations.TOGGLE_EMPTY_STATE); +}); diff --git a/spec/frontend/boards/stores/state_spec.js b/spec/frontend/boards/stores/state_spec.js new file mode 100644 index 00000000000..35490a63567 --- /dev/null +++ b/spec/frontend/boards/stores/state_spec.js @@ -0,0 +1,11 @@ +import createState from '~/boards/stores/state'; + +describe('createState', () => { + it('is a function', () => { + expect(createState).toEqual(expect.any(Function)); + }); + + it('returns an object', () => { + expect(createState()).toEqual(expect.any(Object)); + }); +}); -- cgit v1.2.1 From 70decdc2acb4da895bdd63bb15b46014045af661 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 May 2019 15:21:37 +0300 Subject: Fix specs to match route changes Signed-off-by: Dmitriy Zaporozhets --- spec/presenters/merge_request_presenter_spec.rb | 4 +-- spec/routing/project_routing_spec.rb | 41 +++++++++++++------------ spec/services/system_note_service_spec.rb | 2 +- spec/views/help/index.html.haml_spec.rb | 2 +- 4 files changed, 25 insertions(+), 24 deletions(-) (limited to 'spec') diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 451dc88880c..0e1aed42cc5 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -403,7 +403,7 @@ describe MergeRequestPresenter do allow(resource).to receive(:source_branch_exists?) { true } is_expected - .to eq("/#{resource.source_project.full_path}/branches/#{resource.source_branch}") + .to eq("/#{resource.source_project.full_path}/-/branches/#{resource.source_branch}") end end @@ -426,7 +426,7 @@ describe MergeRequestPresenter do allow(resource).to receive(:target_branch_exists?) { true } is_expected - .to eq("/#{resource.source_project.full_path}/branches/#{resource.target_branch}") + .to eq("/#{resource.source_project.full_path}/-/branches/#{resource.target_branch}") end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index a0d01fc8263..174293fe70c 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -204,25 +204,25 @@ describe 'project routing' do describe Projects::BranchesController, 'routing' do it 'to #branches' do - expect(get('/gitlab/gitlabhq/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - expect(delete('/gitlab/gitlabhq/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') - expect(delete('/gitlab/gitlabhq/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') - expect(delete('/gitlab/gitlabhq/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') - expect(delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') + expect(get('/gitlab/gitlabhq/-/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/-/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/-/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/-/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/-/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/-/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/-/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') end end describe Projects::TagsController, 'routing' do it 'to #tags' do - expect(get('/gitlab/gitlabhq/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - expect(delete('/gitlab/gitlabhq/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') - expect(delete('/gitlab/gitlabhq/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') - expect(delete('/gitlab/gitlabhq/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') - expect(delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') + expect(get('/gitlab/gitlabhq/-/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/-/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/-/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/-/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/-/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/-/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/-/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') end end @@ -247,6 +247,7 @@ describe 'project routing' do it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :destroy] } let(:controller) { 'protected_branches' } + let(:controller_path) { '/-/protected_branches' } end end @@ -592,17 +593,17 @@ describe 'project routing' do describe Projects::NetworkController, 'routing' do it 'to #show' do - expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') - expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + expect(get('/gitlab/gitlabhq/-/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/-/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/-/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') end end describe Projects::GraphsController, 'routing' do it 'to #show' do - expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') - expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + expect(get('/gitlab/gitlabhq/-/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/-/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/-/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 51c5a803dbd..4d33c6f4094 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -132,7 +132,7 @@ describe SystemNoteService do end it 'sets the note text' do - link = "http://localhost/#{project.full_path}/tags/#{tag_name}" + link = "http://localhost/#{project.full_path}/-/tags/#{tag_name}" expect(subject.note).to eq "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})" end diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index 34e93d929a7..257991549a9 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -31,7 +31,7 @@ describe 'help/index' do render expect(rendered).to match '8.0.2' - expect(rendered).to have_link('8.0.2', href: %r{https://gitlab.com/gitlab-org/gitlab-(ce|ee)/tags/v8.0.2}) + expect(rendered).to have_link('8.0.2', href: %r{https://gitlab.com/gitlab-org/gitlab-(ce|ee)/-/tags/v8.0.2}) end it 'shows a link to the commit for pre-releases' do -- cgit v1.2.1 From 4d9c76e9e44119ee2578c94b2b98bb72e80e874b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 18 Apr 2019 22:08:04 +0200 Subject: Reorganize test jobs by level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/lib/quality/test_level_spec.rb | 105 +++++++++ spec/spec_helper.rb | 5 +- .../finders/assignees_filter_shared_examples.rb | 49 ++++ .../finders/assignees_filter_spec.rb | 49 ---- .../models/atomic_internal_id_shared_examples.rb | 113 ++++++++++ .../models/atomic_internal_id_spec.rb | 113 ---------- .../models/chat_service_shared_examples.rb | 248 +++++++++++++++++++++ .../shared_examples/models/chat_service_spec.rb | 248 --------------------- .../update_project_statistics_shared_examples.rb | 76 +++++++ .../models/update_project_statistics_spec.rb | 76 ------- .../requests/api/issues_shared_example_spec.rb | 44 ---- .../requests/api/issues_shared_examples.rb | 44 ++++ 12 files changed, 639 insertions(+), 531 deletions(-) create mode 100644 spec/lib/quality/test_level_spec.rb create mode 100644 spec/support/shared_examples/finders/assignees_filter_shared_examples.rb delete mode 100644 spec/support/shared_examples/finders/assignees_filter_spec.rb create mode 100644 spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb delete mode 100644 spec/support/shared_examples/models/atomic_internal_id_spec.rb create mode 100644 spec/support/shared_examples/models/chat_service_shared_examples.rb delete mode 100644 spec/support/shared_examples/models/chat_service_spec.rb create mode 100644 spec/support/shared_examples/models/update_project_statistics_shared_examples.rb delete mode 100644 spec/support/shared_examples/models/update_project_statistics_spec.rb delete mode 100644 spec/support/shared_examples/requests/api/issues_shared_example_spec.rb create mode 100644 spec/support/shared_examples/requests/api/issues_shared_examples.rb (limited to 'spec') diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb new file mode 100644 index 00000000000..3465c3a050b --- /dev/null +++ b/spec/lib/quality/test_level_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Quality::TestLevel do + describe '#pattern' do + context 'when level is unit' do + it 'returns a pattern' do + expect(subject.pattern(:unit)) + .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") + end + end + + context 'when level is integration' do + it 'returns a pattern' do + expect(subject.pattern(:integration)) + .to eq("spec/{controllers,mailers,requests}{,/**/}*_spec.rb") + end + end + + context 'when level is system' do + it 'returns a pattern' do + expect(subject.pattern(:system)) + .to eq("spec/{features}{,/**/}*_spec.rb") + end + end + + context 'with a prefix' do + it 'returns a pattern' do + expect(described_class.new('ee/').pattern(:system)) + .to eq("ee/spec/{features}{,/**/}*_spec.rb") + end + end + + describe 'performance' do + it 'memoizes the pattern for a given level' do + expect(subject.pattern(:system).object_id).to eq(subject.pattern(:system).object_id) + end + + it 'freezes the pattern for a given level' do + expect(subject.pattern(:system)).to be_frozen + end + end + end + + describe '#regexp' do + context 'when level is unit' do + it 'returns a regexp' do + expect(subject.regexp(:unit)) + .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) + end + end + + context 'when level is integration' do + it 'returns a regexp' do + expect(subject.regexp(:integration)) + .to eq(%r{spec/(controllers|mailers|requests)}) + end + end + + context 'when level is system' do + it 'returns a regexp' do + expect(subject.regexp(:system)) + .to eq(%r{spec/(features)}) + end + end + + context 'with a prefix' do + it 'returns a regexp' do + expect(described_class.new('ee/').regexp(:system)) + .to eq(%r{ee/spec/(features)}) + end + end + + describe 'performance' do + it 'memoizes the regexp for a given level' do + expect(subject.regexp(:system).object_id).to eq(subject.regexp(:system).object_id) + end + + it 'freezes the regexp for a given level' do + expect(subject.regexp(:system)).to be_frozen + end + end + end + + describe '#level_for' do + it 'returns the correct level for a unit test' do + expect(subject.level_for('spec/models/abuse_report_spec.rb')).to eq(:unit) + end + + it 'returns the correct level for an integration test' do + expect(subject.level_for('spec/mailers/abuse_report_mailer_spec.rb')).to eq(:integration) + end + + it 'returns the correct level for a system test' do + expect(subject.level_for('spec/features/abuse_report_spec.rb')).to eq(:system) + end + + it 'raises an error for an unknown level' do + expect { subject.level_for('spec/unknown/foo_spec.rb') } + .to raise_error(described_class::UnknownTestLevelError, + %r{Test level for spec/unknown/foo_spec.rb couldn't be set. Please rename the file properly or change the test level detection regexes in .+/lib/quality/test_level.rb.}) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 69589c9aa33..390a869d93f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,6 +44,8 @@ Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } +quality_level = Quality::TestLevel.new + RSpec.configure do |config| config.use_transactional_fixtures = false config.use_instantiated_fixtures = false @@ -55,9 +57,10 @@ RSpec.configure do |config| config.infer_spec_type_from_file_location! config.full_backtrace = !!ENV['CI'] - config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| + config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata| location = metadata[:location] + metadata[:level] = quality_level.level_for(location) metadata[:api] = true if location =~ %r{/spec/requests/api/} # do not overwrite type if it's already set diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb new file mode 100644 index 00000000000..782a2d97746 --- /dev/null +++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +shared_examples 'assignee ID filter' do + it 'returns issuables assigned to that user' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'assignee username filter' do + it 'returns issuables assigned to those users' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'no assignee filter' do + let(:params) { { assignee_id: 'None' } } + + it 'returns issuables not assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 0 + + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 'none' + + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'any assignee filter' do + context '' do + let(:params) { { assignee_id: 'Any' } } + + it 'returns issuables assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables assigned to any assignee' do + params[:assignee_id] = 'any' + + expect(issuables).to contain_exactly(*expected_issuables) + end + end +end diff --git a/spec/support/shared_examples/finders/assignees_filter_spec.rb b/spec/support/shared_examples/finders/assignees_filter_spec.rb deleted file mode 100644 index 782a2d97746..00000000000 --- a/spec/support/shared_examples/finders/assignees_filter_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'assignee ID filter' do - it 'returns issuables assigned to that user' do - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'assignee username filter' do - it 'returns issuables assigned to those users' do - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'no assignee filter' do - let(:params) { { assignee_id: 'None' } } - - it 'returns issuables not assigned to any assignee' do - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables not assigned to any assignee' do - params[:assignee_id] = 0 - - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables not assigned to any assignee' do - params[:assignee_id] = 'none' - - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'any assignee filter' do - context '' do - let(:params) { { assignee_id: 'Any' } } - - it 'returns issuables assigned to any assignee' do - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables assigned to any assignee' do - params[:assignee_id] = 'any' - - expect(issuables).to contain_exactly(*expected_issuables) - end - end -end diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb new file mode 100644 index 00000000000..a248f60d23e --- /dev/null +++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb @@ -0,0 +1,113 @@ +require 'spec_helper' + +shared_examples_for 'AtomicInternalId' do |validate_presence: true| + describe '.has_internal_id' do + describe 'Module inclusion' do + subject { described_class } + + it { is_expected.to include_module(AtomicInternalId) } + end + + describe 'Validation' do + before do + allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") + + instance.valid? + end + + context 'when presence validation is required' do + before do + skip unless validate_presence + end + + it 'validates presence' do + expect(instance.errors[internal_id_attribute]).to include("can't be blank") + end + end + + context 'when presence validation is not required' do + before do + skip if validate_presence + end + + it 'does not validate presence' do + expect(instance.errors[internal_id_attribute]).to be_empty + end + end + end + + describe 'Creating an instance' do + subject { instance.save! } + + it 'saves a new instance properly' do + expect { subject }.not_to raise_error + end + end + + describe 'internal id generation' do + subject { instance.save! } + + it 'calls InternalId.generate_next and sets internal id attribute' do + iid = rand(1..1000) + + expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid) + subject + expect(read_internal_id).to eq(iid) + end + + it 'does not overwrite an existing internal id' do + write_internal_id(4711) + + expect { subject }.not_to change { read_internal_id } + end + + context 'when the instance has an internal ID set' do + let(:internal_id) { 9001 } + + it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do + write_internal_id(internal_id) + + expect(InternalId) + .to receive(:track_greatest) + .with(instance, scope_attrs, usage, internal_id, any_args) + .and_return(internal_id) + subject + end + end + end + + describe "#reset_scope_internal_id_attribute" do + it 'rewinds the allocated IID' do + expect { ensure_scope_attribute! }.not_to raise_error + expect(read_internal_id).not_to be_nil + + expect(reset_scope_attribute).to be_nil + expect(read_internal_id).to be_nil + end + + it 'allocates the same IID' do + internal_id = ensure_scope_attribute! + reset_scope_attribute + expect(read_internal_id).to be_nil + + expect(ensure_scope_attribute!).to eq(internal_id) + end + end + + def ensure_scope_attribute! + instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!") + end + + def reset_scope_attribute + instance.public_send(:"reset_#{scope}_#{internal_id_attribute}") + end + + def read_internal_id + instance.public_send(internal_id_attribute) + end + + def write_internal_id(value) + instance.public_send(:"#{internal_id_attribute}=", value) + end + end +end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb deleted file mode 100644 index a248f60d23e..00000000000 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'spec_helper' - -shared_examples_for 'AtomicInternalId' do |validate_presence: true| - describe '.has_internal_id' do - describe 'Module inclusion' do - subject { described_class } - - it { is_expected.to include_module(AtomicInternalId) } - end - - describe 'Validation' do - before do - allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") - - instance.valid? - end - - context 'when presence validation is required' do - before do - skip unless validate_presence - end - - it 'validates presence' do - expect(instance.errors[internal_id_attribute]).to include("can't be blank") - end - end - - context 'when presence validation is not required' do - before do - skip if validate_presence - end - - it 'does not validate presence' do - expect(instance.errors[internal_id_attribute]).to be_empty - end - end - end - - describe 'Creating an instance' do - subject { instance.save! } - - it 'saves a new instance properly' do - expect { subject }.not_to raise_error - end - end - - describe 'internal id generation' do - subject { instance.save! } - - it 'calls InternalId.generate_next and sets internal id attribute' do - iid = rand(1..1000) - - expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid) - subject - expect(read_internal_id).to eq(iid) - end - - it 'does not overwrite an existing internal id' do - write_internal_id(4711) - - expect { subject }.not_to change { read_internal_id } - end - - context 'when the instance has an internal ID set' do - let(:internal_id) { 9001 } - - it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do - write_internal_id(internal_id) - - expect(InternalId) - .to receive(:track_greatest) - .with(instance, scope_attrs, usage, internal_id, any_args) - .and_return(internal_id) - subject - end - end - end - - describe "#reset_scope_internal_id_attribute" do - it 'rewinds the allocated IID' do - expect { ensure_scope_attribute! }.not_to raise_error - expect(read_internal_id).not_to be_nil - - expect(reset_scope_attribute).to be_nil - expect(read_internal_id).to be_nil - end - - it 'allocates the same IID' do - internal_id = ensure_scope_attribute! - reset_scope_attribute - expect(read_internal_id).to be_nil - - expect(ensure_scope_attribute!).to eq(internal_id) - end - end - - def ensure_scope_attribute! - instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!") - end - - def reset_scope_attribute - instance.public_send(:"reset_#{scope}_#{internal_id_attribute}") - end - - def read_internal_id - instance.public_send(internal_id_attribute) - end - - def write_internal_id(value) - instance.public_send(:"#{internal_id_attribute}=", value) - end - end -end diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb new file mode 100644 index 00000000000..0a302e7d030 --- /dev/null +++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb @@ -0,0 +1,248 @@ +require "spec_helper" + +shared_examples_for "chat service" do |service_name| + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Validations" do + context "when service is active" do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:webhook) } + it_behaves_like "issue tracker service URL attribute", :webhook + end + + context "when service is inactive" do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:webhook) } + end + end + + describe '.supported_events' do + it 'does not support deployment_events' do + expect(described_class.supported_events).not_to include('deployment') + end + end + + describe "#execute" do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:webhook_url) { "https://example.gitlab.com/" } + + before do + allow(subject).to receive_messages( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + end + + shared_examples "#{service_name} service" do + it "calls #{service_name} API" do + subject.execute(sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once + end + end + + context "with push events" do + let(:sample_data) do + Gitlab::DataBuilder::Push.build_sample(project, user) + end + + it_behaves_like "#{service_name} service" + + it "specifies the webhook when it is configured" do + expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) + + subject.execute(sample_data) + end + + context "with not default branch" do + let(:sample_data) do + Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch") + end + + context "when notify_only_default_branch enabled" do + before do + subject.notify_only_default_branch = true + end + + it "does not call the Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_default_branch disabled" do + before do + subject.notify_only_default_branch = false + end + + it_behaves_like "#{service_name} service" + end + end + end + + context "with issue events" do + let(:opts) { { title: "Awesome issue", description: "please fix" } } + let(:sample_data) do + service = Issues::CreateService.new(project, user, opts) + issue = service.execute + service.hook_data(issue, "open") + end + + it_behaves_like "#{service_name} service" + end + + context "with merge events" do + let(:opts) do + { + title: "Awesome merge_request", + description: "please fix", + source_branch: "feature", + target_branch: "master" + } + end + + let(:sample_data) do + service = MergeRequests::CreateService.new(project, user, opts) + merge_request = service.execute + service.hook_data(merge_request, "open") + end + + before do + project.add_developer(user) + end + + it_behaves_like "#{service_name} service" + end + + context "with wiki page events" do + let(:opts) do + { + title: "Awesome wiki_page", + content: "Some text describing some thing or another", + format: "md", + message: "user created page: Awesome wiki_page" + } + end + let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) } + let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } + + it_behaves_like "#{service_name} service" + end + + context "with note events" do + let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) } + + context "with commit comment" do + let(:note) do + create(:note_on_commit, + author: user, + project: project, + commit_id: project.repository.commit.id, + note: "a comment on a commit") + end + + it_behaves_like "#{service_name} service" + end + + context "with merge request comment" do + let(:note) do + create(:note_on_merge_request, project: project, note: "merge request note") + end + + it_behaves_like "#{service_name} service" + end + + context "with issue comment" do + let(:note) do + create(:note_on_issue, project: project, note: "issue note") + end + + it_behaves_like "#{service_name} service" + end + + context "with snippet comment" do + let(:note) do + create(:note_on_project_snippet, project: project, note: "snippet note") + end + + it_behaves_like "#{service_name} service" + end + end + + context "with pipeline events" do + let(:pipeline) do + create(:ci_pipeline, + project: project, status: status, + sha: project.commit.sha, ref: project.default_branch) + end + let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } + + context "with failed pipeline" do + let(:status) { "failed" } + + it_behaves_like "#{service_name} service" + end + + context "with succeeded pipeline" do + let(:status) { "success" } + + context "with default notify_only_broken_pipelines" do + it "does not call Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_broken_pipelines is false" do + before do + subject.notify_only_broken_pipelines = false + end + + it_behaves_like "#{service_name} service" + end + end + + context "with not default branch" do + let(:pipeline) do + create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") + end + + context "when notify_only_default_branch enabled" do + before do + subject.notify_only_default_branch = true + end + + it "does not call the Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_default_branch disabled" do + before do + subject.notify_only_default_branch = false + end + + it_behaves_like "#{service_name} service" + end + end + end + end +end diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_spec.rb deleted file mode 100644 index 0a302e7d030..00000000000 --- a/spec/support/shared_examples/models/chat_service_spec.rb +++ /dev/null @@ -1,248 +0,0 @@ -require "spec_helper" - -shared_examples_for "chat service" do |service_name| - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - - describe "Validations" do - context "when service is active" do - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:webhook) } - it_behaves_like "issue tracker service URL attribute", :webhook - end - - context "when service is inactive" do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:webhook) } - end - end - - describe '.supported_events' do - it 'does not support deployment_events' do - expect(described_class.supported_events).not_to include('deployment') - end - end - - describe "#execute" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:webhook_url) { "https://example.gitlab.com/" } - - before do - allow(subject).to receive_messages( - project: project, - project_id: project.id, - service_hook: true, - webhook: webhook_url - ) - - WebMock.stub_request(:post, webhook_url) - end - - shared_examples "#{service_name} service" do - it "calls #{service_name} API" do - subject.execute(sample_data) - - expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once - end - end - - context "with push events" do - let(:sample_data) do - Gitlab::DataBuilder::Push.build_sample(project, user) - end - - it_behaves_like "#{service_name} service" - - it "specifies the webhook when it is configured" do - expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) - - subject.execute(sample_data) - end - - context "with not default branch" do - let(:sample_data) do - Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch") - end - - context "when notify_only_default_branch enabled" do - before do - subject.notify_only_default_branch = true - end - - it "does not call the Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_default_branch disabled" do - before do - subject.notify_only_default_branch = false - end - - it_behaves_like "#{service_name} service" - end - end - end - - context "with issue events" do - let(:opts) { { title: "Awesome issue", description: "please fix" } } - let(:sample_data) do - service = Issues::CreateService.new(project, user, opts) - issue = service.execute - service.hook_data(issue, "open") - end - - it_behaves_like "#{service_name} service" - end - - context "with merge events" do - let(:opts) do - { - title: "Awesome merge_request", - description: "please fix", - source_branch: "feature", - target_branch: "master" - } - end - - let(:sample_data) do - service = MergeRequests::CreateService.new(project, user, opts) - merge_request = service.execute - service.hook_data(merge_request, "open") - end - - before do - project.add_developer(user) - end - - it_behaves_like "#{service_name} service" - end - - context "with wiki page events" do - let(:opts) do - { - title: "Awesome wiki_page", - content: "Some text describing some thing or another", - format: "md", - message: "user created page: Awesome wiki_page" - } - end - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) } - let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } - - it_behaves_like "#{service_name} service" - end - - context "with note events" do - let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) } - - context "with commit comment" do - let(:note) do - create(:note_on_commit, - author: user, - project: project, - commit_id: project.repository.commit.id, - note: "a comment on a commit") - end - - it_behaves_like "#{service_name} service" - end - - context "with merge request comment" do - let(:note) do - create(:note_on_merge_request, project: project, note: "merge request note") - end - - it_behaves_like "#{service_name} service" - end - - context "with issue comment" do - let(:note) do - create(:note_on_issue, project: project, note: "issue note") - end - - it_behaves_like "#{service_name} service" - end - - context "with snippet comment" do - let(:note) do - create(:note_on_project_snippet, project: project, note: "snippet note") - end - - it_behaves_like "#{service_name} service" - end - end - - context "with pipeline events" do - let(:pipeline) do - create(:ci_pipeline, - project: project, status: status, - sha: project.commit.sha, ref: project.default_branch) - end - let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } - - context "with failed pipeline" do - let(:status) { "failed" } - - it_behaves_like "#{service_name} service" - end - - context "with succeeded pipeline" do - let(:status) { "success" } - - context "with default notify_only_broken_pipelines" do - it "does not call Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_broken_pipelines is false" do - before do - subject.notify_only_broken_pipelines = false - end - - it_behaves_like "#{service_name} service" - end - end - - context "with not default branch" do - let(:pipeline) do - create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") - end - - context "when notify_only_default_branch enabled" do - before do - subject.notify_only_default_branch = true - end - - it "does not call the Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_default_branch disabled" do - before do - subject.notify_only_default_branch = false - end - - it_behaves_like "#{service_name} service" - end - end - end - end -end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb new file mode 100644 index 00000000000..7a04e940ee5 --- /dev/null +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +shared_examples_for 'UpdateProjectStatistics' do + let(:project) { subject.project } + let(:stat) { described_class.statistic_name } + let(:attribute) { described_class.statistic_attribute } + + def reload_stat + project.statistics.reload.send(stat).to_i + end + + def read_attribute + subject.read_attribute(attribute).to_i + end + + it { is_expected.to be_new_record } + + context 'when creating' do + it 'updates the project statistics' do + delta = read_attribute + + expect { subject.save! } + .to change { reload_stat } + .by(delta) + end + end + + context 'when updating' do + before do + subject.save! + end + + it 'updates project statistics' do + delta = 42 + + expect(ProjectStatistics) + .to receive(:increment_statistic) + .and_call_original + + subject.write_attribute(attribute, read_attribute + delta) + expect { subject.save! } + .to change { reload_stat } + .by(delta) + end + end + + context 'when destroying' do + before do + subject.save! + end + + it 'updates the project statistics' do + delta = -read_attribute + + expect(ProjectStatistics) + .to receive(:increment_statistic) + .and_call_original + + expect { subject.destroy } + .to change { reload_stat } + .by(delta) + end + + context 'when it is destroyed from the project level' do + it 'does not update the project statistics' do + expect(ProjectStatistics) + .not_to receive(:increment_statistic) + + project.update(pending_delete: true) + project.destroy! + end + end + end +end diff --git a/spec/support/shared_examples/models/update_project_statistics_spec.rb b/spec/support/shared_examples/models/update_project_statistics_spec.rb deleted file mode 100644 index 7a04e940ee5..00000000000 --- a/spec/support/shared_examples/models/update_project_statistics_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -shared_examples_for 'UpdateProjectStatistics' do - let(:project) { subject.project } - let(:stat) { described_class.statistic_name } - let(:attribute) { described_class.statistic_attribute } - - def reload_stat - project.statistics.reload.send(stat).to_i - end - - def read_attribute - subject.read_attribute(attribute).to_i - end - - it { is_expected.to be_new_record } - - context 'when creating' do - it 'updates the project statistics' do - delta = read_attribute - - expect { subject.save! } - .to change { reload_stat } - .by(delta) - end - end - - context 'when updating' do - before do - subject.save! - end - - it 'updates project statistics' do - delta = 42 - - expect(ProjectStatistics) - .to receive(:increment_statistic) - .and_call_original - - subject.write_attribute(attribute, read_attribute + delta) - expect { subject.save! } - .to change { reload_stat } - .by(delta) - end - end - - context 'when destroying' do - before do - subject.save! - end - - it 'updates the project statistics' do - delta = -read_attribute - - expect(ProjectStatistics) - .to receive(:increment_statistic) - .and_call_original - - expect { subject.destroy } - .to change { reload_stat } - .by(delta) - end - - context 'when it is destroyed from the project level' do - it 'does not update the project statistics' do - expect(ProjectStatistics) - .not_to receive(:increment_statistic) - - project.update(pending_delete: true) - project.destroy! - end - end - end -end diff --git a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb deleted file mode 100644 index 1133e95e44e..00000000000 --- a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'labeled issues with labels and label_name params' do - shared_examples 'returns label names' do - it 'returns label names' do - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) - end - end - - shared_examples 'returns basic label entity' do - it 'returns basic label entity' do - expect_paginated_array_response(issue.id) - expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title]) - expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic') - end - end - - context 'array of labeled issues when all labels match' do - let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } } - - it_behaves_like 'returns label names' - end - - context 'array of labeled issues when all labels match with labels param as array' do - let(:params) { { labels: [label.title, label_b.title, label_c.title] } } - - it_behaves_like 'returns label names' - end - - context 'when with_labels_details provided' do - context 'array of labeled issues when all labels match' do - let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } } - - it_behaves_like 'returns basic label entity' - end - - context 'array of labeled issues when all labels match with labels param as array' do - let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } } - - it_behaves_like 'returns basic label entity' - end - end -end diff --git a/spec/support/shared_examples/requests/api/issues_shared_examples.rb b/spec/support/shared_examples/requests/api/issues_shared_examples.rb new file mode 100644 index 00000000000..1133e95e44e --- /dev/null +++ b/spec/support/shared_examples/requests/api/issues_shared_examples.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +shared_examples 'labeled issues with labels and label_name params' do + shared_examples 'returns label names' do + it 'returns label names' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) + end + end + + shared_examples 'returns basic label entity' do + it 'returns basic label entity' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title]) + expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic') + end + end + + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } } + + it_behaves_like 'returns label names' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title] } } + + it_behaves_like 'returns label names' + end + + context 'when with_labels_details provided' do + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } } + + it_behaves_like 'returns basic label entity' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } } + + it_behaves_like 'returns basic label entity' + end + end +end -- cgit v1.2.1 From 24a81b3dccef87cf4c6837a26fadd702b0418d72 Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Mon, 20 May 2019 13:27:08 +0000 Subject: Display classname JUnit attribute in report modal --- spec/fixtures/api/schemas/entities/test_case.json | 1 + spec/serializers/test_case_entity_spec.rb | 2 ++ 2 files changed, 3 insertions(+) (limited to 'spec') diff --git a/spec/fixtures/api/schemas/entities/test_case.json b/spec/fixtures/api/schemas/entities/test_case.json index c9ba1f3ad18..70f6edeeeb7 100644 --- a/spec/fixtures/api/schemas/entities/test_case.json +++ b/spec/fixtures/api/schemas/entities/test_case.json @@ -7,6 +7,7 @@ "properties": { "status": { "type": "string" }, "name": { "type": "string" }, + "classname": { "type": "string" }, "execution_time": { "type": "float" }, "system_output": { "type": ["string", "null"] }, "stack_trace": { "type": ["string", "null"] } diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb index a55910f98bb..986c9feb07b 100644 --- a/spec/serializers/test_case_entity_spec.rb +++ b/spec/serializers/test_case_entity_spec.rb @@ -14,6 +14,7 @@ describe TestCaseEntity do it 'contains correct test case details' do expect(subject[:status]).to eq('success') expect(subject[:name]).to eq('Test#sum when a is 1 and b is 3 returns summary') + expect(subject[:classname]).to eq('spec.test_spec') expect(subject[:execution_time]).to eq(1.11) end end @@ -24,6 +25,7 @@ describe TestCaseEntity do it 'contains correct test case details' do expect(subject[:status]).to eq('failed') expect(subject[:name]).to eq('Test#sum when a is 2 and b is 2 returns summary') + expect(subject[:classname]).to eq('spec.test_spec') expect(subject[:execution_time]).to eq(2.22) end end -- cgit v1.2.1 From b70b43d07ec27c6410e4a8d7ad417662a8823f8f Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 20 May 2019 11:08:31 -0300 Subject: Resolve: Milestones leaked via search API Fix milestone titles being leaked using search API when users cannot read milestones --- spec/lib/gitlab/search_results_spec.rb | 24 ++++++++++++++++++ spec/models/project_spec.rb | 17 +++++++++++++ spec/requests/api/search_spec.rb | 46 +++++++++++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 312aa3be490..3d27156b356 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -256,4 +256,28 @@ describe Gitlab::SearchResults do expect(results.objects('merge_requests')).not_to include merge_request end + + context 'milestones' do + it 'returns correct set of milestones' do + private_project_1 = create(:project, :private) + private_project_2 = create(:project, :private) + internal_project = create(:project, :internal) + public_project_1 = create(:project, :public) + public_project_2 = create(:project, :public, :issues_disabled, :merge_requests_disabled) + private_project_1.add_developer(user) + # milestones that should not be visible + create(:milestone, project: private_project_2, title: 'Private project without access milestone') + create(:milestone, project: public_project_2, title: 'Public project with milestones disabled milestone') + # milestones that should be visible + milestone_1 = create(:milestone, project: private_project_1, title: 'Private project with access milestone', state: 'closed') + milestone_2 = create(:milestone, project: internal_project, title: 'Internal project milestone') + milestone_3 = create(:milestone, project: public_project_1, title: 'Public project with milestones enabled milestone') + # Global search scope takes user authorized projects, internal projects and public projects. + limit_projects = ProjectsFinder.new(current_user: user).execute + + milestones = described_class.new(user, limit_projects, 'milestone').objects('milestones') + + expect(milestones).to match_array([milestone_1, milestone_2, milestone_3]) + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 425096d7e80..ba2411d0b95 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3170,6 +3170,23 @@ describe Project do end end + describe '.ids_with_milestone_available_for' do + let!(:user) { create(:user) } + + it 'returns project ids with milestones available for user' do + project_1 = create(:project, :public, :merge_requests_disabled, :issues_disabled) + project_2 = create(:project, :public, :merge_requests_disabled) + project_3 = create(:project, :public, :issues_disabled) + project_4 = create(:project, :public) + project_4.project_feature.update(issues_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE ) + + project_ids = described_class.ids_with_milestone_available_for(user).pluck(:id) + + expect(project_ids).to include(project_2.id, project_3.id) + expect(project_ids).not_to include(project_1.id, project_4.id) + end + end + describe '.with_feature_available_for_user' do let(:user) { create(:user) } let(:feature) { MergeRequest } diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 49672591b3b..42e0efa10b7 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -70,11 +70,30 @@ describe API::Search do context 'for milestones scope' do before do create(:milestone, project: project, title: 'awesome milestone') + end + + context 'when user can read project milestones' do + before do + get api('/search', user), params: { scope: 'milestones', search: 'awesome' } + end - get api('/search', user), params: { scope: 'milestones', search: 'awesome' } + it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' end - it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' + context 'when user cannot read project milestones' do + before do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE) + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + end + + it 'returns empty array' do + get api('/search', user), params: { scope: 'milestones', search: 'awesome' } + + milestones = JSON.parse(response.body) + + expect(milestones).to be_empty + end + end end context 'for users scope' do @@ -318,11 +337,30 @@ describe API::Search do context 'for milestones scope' do before do create(:milestone, project: project, title: 'awesome milestone') + end + + context 'when user can read milestones' do + before do + get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' } + end - get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' } + it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' end - it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' + context 'when user cannot read project milestones' do + before do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE) + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + end + + it 'returns empty array' do + get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' } + + milestones = JSON.parse(response.body) + + expect(milestones).to be_empty + end + end end context 'for users scope' do -- cgit v1.2.1 From 2f6a20ce665de6a23fe2c1cc28cc6398afcb1b71 Mon Sep 17 00:00:00 2001 From: Yoginth Date: Mon, 20 May 2019 14:11:44 +0000 Subject: Fix typos in the whole gitlab-ce project --- spec/frontend/lib/utils/text_utility_spec.js | 4 ++-- .../vue_shared/components/project_selector/project_selector_spec.js | 2 +- spec/lib/banzai/filter/syntax_highlight_filter_spec.rb | 2 +- spec/requests/api/discussions_spec.rb | 4 ++-- spec/services/auth/container_registry_authentication_service_spec.rb | 2 +- spec/support/shared_examples/requests/api/discussions.rb | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) (limited to 'spec') diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 0878c1de095..9e920d59093 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -155,11 +155,11 @@ describe('text_utility', () => { expect(textUtils.truncateNamespace('a / b')).toBe('a'); }); - it(`should return the first 2 namespaces if the namespace inlcudes exactly 2 levels`, () => { + it(`should return the first 2 namespaces if the namespace includes exactly 2 levels`, () => { expect(textUtils.truncateNamespace('a / b / c')).toBe('a / b'); }); - it(`should return the first and last namespaces, separated by "...", if the namespace inlcudes more than 2 levels`, () => { + it(`should return the first and last namespaces, separated by "...", if the namespace includes more than 2 levels`, () => { expect(textUtils.truncateNamespace('a / b / c / d')).toBe('a / ... / c'); expect(textUtils.truncateNamespace('a / b / c / d / e / f / g / h / i')).toBe('a / ... / h'); }); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js index 34c0cd435cd..3ba0033171e 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -99,7 +99,7 @@ describe('ProjectSelector component', () => { expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search'); }); - it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, () => { + it(`shows a "minimum search query" message if showMinimumSearchQueryMessage === true`, () => { wrapper.setProps({ showMinimumSearchQueryMessage: true }); expect(wrapper.contains('.js-minimum-search-query-message')).toBe(true); diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 05057789cc1..80ca7a63435 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -80,7 +80,7 @@ describe Banzai::Filter::SyntaxHighlightFilter do let(:lang) { 'suggestion' } let(:lang_params) { '-1+10' } - it "delimits on the first appearence" do + it "delimits on the first appearance" do result = filter(%{
      This is a test
      }) expect(result.to_html).to eq(%{
      This is a test
      }) diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb index 16036297ec7..ca1ffe3c524 100644 --- a/spec/requests/api/discussions_spec.rb +++ b/spec/requests/api/discussions_spec.rb @@ -13,7 +13,7 @@ describe API::Discussions do let!(:issue) { create(:issue, project: project, author: user) } let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) } - it_behaves_like 'discussions API', 'projects', 'issues', 'iid', can_reply_to_invididual_notes: true do + it_behaves_like 'discussions API', 'projects', 'issues', 'iid', can_reply_to_individual_notes: true do let(:parent) { project } let(:noteable) { issue } let(:note) { issue_note } @@ -37,7 +37,7 @@ describe API::Discussions do let!(:diff_note) { create(:diff_note_on_merge_request, noteable: noteable, project: project, author: user) } let(:parent) { project } - it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid', can_reply_to_invididual_notes: true + it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid', can_reply_to_individual_notes: true it_behaves_like 'diff discussions API', 'projects', 'merge_requests', 'iid' it_behaves_like 'resolvable discussions API', 'projects', 'merge_requests', 'iid' end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index e24fe60f059..4f4776bbb27 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -285,7 +285,7 @@ describe Auth::ContainerRegistryAuthenticationService do it_behaves_like 'not a container repository factory' end - context 'disallow guest to delete images since regsitry 2.7' do + context 'disallow guest to delete images since registry 2.7' do before do project.add_guest(current_user) end diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb index 96f79081d26..c3132c41f5b 100644 --- a/spec/support/shared_examples/requests/api/discussions.rb +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -1,4 +1,4 @@ -shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_invididual_notes: false| +shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do it "returns an array of discussions" do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) @@ -144,7 +144,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_r "discussions/#{note.discussion_id}/notes", user), params: { body: 'hi!' } end - if can_reply_to_invididual_notes + if can_reply_to_individual_notes it 'creates a new discussion' do expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') -- cgit v1.2.1 From 2334b07726f819c6a9fe94d1380969d0b9eaef85 Mon Sep 17 00:00:00 2001 From: Ryan Cobb Date: Mon, 20 May 2019 14:34:00 +0000 Subject: Revert "Merge branch '56850-add-new-unicorn-metrics' into 'master'" This reverts merge request !27474 --- .../gitlab/metrics/samplers/ruby_sampler_spec.rb | 30 +++------------------- .../metrics/samplers/unicorn_sampler_spec.rb | 25 ++++-------------- spec/lib/gitlab/metrics/system_spec.rb | 24 ----------------- 3 files changed, 8 insertions(+), 71 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb index aaf8c9fa2a0..7972ff253fe 100644 --- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb @@ -10,20 +10,17 @@ describe Gitlab::Metrics::Samplers::RubySampler do describe '#sample' do it 'samples various statistics' do - expect(Gitlab::Metrics::System).to receive(:cpu_time) - expect(Gitlab::Metrics::System).to receive(:file_descriptor_count) expect(Gitlab::Metrics::System).to receive(:memory_usage) - expect(Gitlab::Metrics::System).to receive(:process_start_time) - expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors) + expect(Gitlab::Metrics::System).to receive(:file_descriptor_count) expect(sampler).to receive(:sample_gc) sampler.sample end - it 'adds a metric containing the process resident memory bytes' do + it 'adds a metric containing the memory usage' do expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000) - expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000) + expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000) sampler.sample end @@ -37,27 +34,6 @@ describe Gitlab::Metrics::Samplers::RubySampler do sampler.sample end - it 'adds a metric containing the process total cpu time' do - expect(Gitlab::Metrics::System).to receive(:cpu_time).and_return(0.51) - expect(sampler.metrics[:process_cpu_seconds_total]).to receive(:set).with({}, 0.51) - - sampler.sample - end - - it 'adds a metric containing the process start time' do - expect(Gitlab::Metrics::System).to receive(:process_start_time).and_return(12345) - expect(sampler.metrics[:process_start_time_seconds]).to receive(:set).with({}, 12345) - - sampler.sample - end - - it 'adds a metric containing the process max file descriptors' do - expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors).and_return(1024) - expect(sampler.metrics[:process_max_fds]).to receive(:set).with({}, 1024) - - sampler.sample - end - it 'clears any GC profiles' do expect(GC::Profiler).to receive(:clear) diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb index 090e456644f..4b03f3c2532 100644 --- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb @@ -39,8 +39,8 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do it 'updates metrics type unix and with addr' do labels = { socket_type: 'unix', socket_address: socket_address } - expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active') - expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued') + expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') + expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') subject.sample end @@ -50,6 +50,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do context 'unicorn listens on tcp sockets' do let(:tcp_socket_address) { '0.0.0.0:8080' } let(:tcp_sockets) { [tcp_socket_address] } + before do allow(unicorn).to receive(:listener_names).and_return(tcp_sockets) end @@ -70,29 +71,13 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do it 'updates metrics type unix and with addr' do labels = { socket_type: 'tcp', socket_address: tcp_socket_address } - expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active') - expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued') + expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') + expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') subject.sample end end end - - context 'additional metrics' do - let(:unicorn_workers) { 2 } - - before do - allow(unicorn).to receive(:listener_names).and_return([""]) - allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14) - allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers) - end - - it "sets additional metrics" do - expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers) - - subject.sample - end - end end describe '#start' do diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index b0603d96eb2..14afcdf5daa 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -13,18 +13,6 @@ describe Gitlab::Metrics::System do expect(described_class.file_descriptor_count).to be > 0 end end - - describe '.max_open_file_descriptors' do - it 'returns the max allowed open file descriptors' do - expect(described_class.max_open_file_descriptors).to be > 0 - end - end - - describe '.process_start_time' do - it 'returns the process start time' do - expect(described_class.process_start_time).to be > 0 - end - end else describe '.memory_usage' do it 'returns 0.0' do @@ -37,18 +25,6 @@ describe Gitlab::Metrics::System do expect(described_class.file_descriptor_count).to eq(0) end end - - describe '.max_open_file_descriptors' do - it 'returns 0' do - expect(described_class.max_open_file_descriptors).to eq(0) - end - end - - describe 'process_start_time' do - it 'returns 0' do - expect(described_class.process_start_time).to eq(0) - end - end end describe '.cpu_time' do -- cgit v1.2.1 From 7017889d0b92dd8bd215770a9d4f823b5dc1f2eb Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Mon, 20 May 2019 15:03:34 +0000 Subject: Added filtering jobs by age to jobs/request API endpoint --- spec/requests/api/runner_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index b331da1acba..4006e697a41 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -542,6 +542,30 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end end + context 'when job filtered by job_age' do + let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, queued_at: 60.seconds.ago) } + + context 'job is queued less than job_age parameter' do + let(:job_age) { 120 } + + it 'gives 204' do + request_job(job_age: job_age) + + expect(response).to have_gitlab_http_status(204) + end + end + + context 'job is queued more than job_age parameter' do + let(:job_age) { 30 } + + it 'picks a job' do + request_job(job_age: job_age) + + expect(response).to have_gitlab_http_status(201) + end + end + end + context 'when job is made for branch' do it 'sets tag as ref_type' do request_job -- cgit v1.2.1 From ec399e6565370a7a7fcabac2d851489fc83d8e72 Mon Sep 17 00:00:00 2001 From: Sarah Yasonik Date: Mon, 20 May 2019 15:04:04 +0000 Subject: Require time window params in metrics dashboard --- .../projects/environments_controller_spec.rb | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index cf23d937037..d5eea5b0439 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -383,6 +383,8 @@ describe Projects::EnvironmentsController do end describe 'GET #additional_metrics' do + let(:window_params) { { start: '1554702993.5398998', end: '1554717396.996232' } } + before do allow(controller).to receive(:environment).and_return(environment) end @@ -394,7 +396,7 @@ describe Projects::EnvironmentsController do context 'when requesting metrics as JSON' do it 'returns a metrics JSON document' do - additional_metrics + additional_metrics(window_params) expect(response).to have_gitlab_http_status(204) expect(json_response).to eq({}) @@ -414,23 +416,19 @@ describe Projects::EnvironmentsController do end it 'returns a metrics JSON document' do - additional_metrics + additional_metrics(window_params) expect(response).to be_ok expect(json_response['success']).to be(true) expect(json_response['data']).to eq({}) expect(json_response['last_update']).to eq(42) end + end - context 'when time params are provided' do - it 'returns a metrics JSON document' do - additional_metrics(start: '1554702993.5398998', end: '1554717396.996232') - - expect(response).to be_ok - expect(json_response['success']).to be(true) - expect(json_response['data']).to eq({}) - expect(json_response['last_update']).to eq(42) - end + context 'when time params are missing' do + it 'raises an error when window params are missing' do + expect { additional_metrics } + .to raise_error(ActionController::ParameterMissing) end end @@ -450,7 +448,7 @@ describe Projects::EnvironmentsController do end it 'raises an error when start is missing' do - expect { additional_metrics(start: '1552647300.651094') } + expect { additional_metrics(end: '1552647300.651094') } .to raise_error(ActionController::ParameterMissing) end -- cgit v1.2.1 From 68fd2bdd74f675f66e39d8785541e85b56ca54ce Mon Sep 17 00:00:00 2001 From: Sarah Groff Hennigh-Palermo Date: Mon, 20 May 2019 17:08:37 +0000 Subject: Add backport changes Adds backport changes for ee --- .../mr_widget_pipeline_container_spec.js | 4 +-- spec/javascripts/vue_mr_widget/mock_data.js | 37 ++++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js index e5155573f6f..dfbc68c48b9 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js @@ -1,4 +1,4 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue'; import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import { mockStore } from '../mock_data'; @@ -9,7 +9,7 @@ describe('MrWidgetPipelineContainer', () => { const factory = (props = {}) => { const localVue = createLocalVue(); - wrapper = shallowMount(localVue.extend(MrWidgetPipelineContainer), { + wrapper = mount(localVue.extend(MrWidgetPipelineContainer), { propsData: { mr: Object.assign({}, mockStore), ...props, diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index dda16375103..bec16b0aab0 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -235,11 +235,44 @@ export default { troubleshooting_docs_path: 'help', merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md', squash: true, + visual_review_app_available: true, }; export const mockStore = { - pipeline: { id: 0 }, - mergePipeline: { id: 1 }, + pipeline: { + id: 0, + details: { + status: { + details_path: '/root/review-app-tester/pipelines/66', + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2. png', + group: 'success-with-warnings', + has_details: true, + icon: 'status_warning', + illustration: null, + label: 'passed with warnings', + text: 'passed', + tooltip: 'passed', + }, + }, + }, + mergePipeline: { + id: 1, + details: { + status: { + details_path: '/root/review-app-tester/pipelines/66', + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2. png', + group: 'success-with-warnings', + has_details: true, + icon: 'status_warning', + illustration: null, + label: 'passed with warnings', + text: 'passed', + tooltip: 'passed', + }, + }, + }, targetBranch: 'target-branch', sourceBranch: 'source-branch', sourceBranchLink: 'source-branch-link', -- cgit v1.2.1 From c04ea58350083c3230c3031eefb94875cde2cb6c Mon Sep 17 00:00:00 2001 From: Kerri Miller Date: Mon, 20 May 2019 17:37:43 +0000 Subject: Revert "Merge branch '55127-add-delay-after-mr-creation-for-async-tasks-to-complete' into 'master'" This reverts merge request !27978 --- spec/requests/api/merge_requests_spec.rb | 27 --------------------------- 1 file changed, 27 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 007f3517e64..5c94a87529b 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1495,33 +1495,6 @@ describe API::MergeRequests do expect(json_response['merge_when_pipeline_succeeds']).to eq(true) end - context 'when the MR requires pipeline success' do - it 'returns 405 if the pipeline is missing' do - allow_any_instance_of(MergeRequest) - .to receive(:merge_when_pipeline_succeeds).and_return(true) - allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(nil) - - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user) - - expect(response).to have_gitlab_http_status(405) - expect(json_response['message']).to eq('Not allowed: pipeline does not exist') - end - end - - context 'when the request requires pipeline success' do - it 'returns 405 if the pipeline is missing' do - allow_any_instance_of(MergeRequest) - .to receive(:merge_when_pipeline_succeeds).and_return(true) - allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(nil) - - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), - params: { merge_when_pipeline_succeeds: true } - - expect(response).to have_gitlab_http_status(405) - expect(json_response['message']).to eq('Not allowed: pipeline does not exist') - end - end - it "returns 404 for an invalid merge request IID" do put api("/projects/#{project.id}/merge_requests/12345/merge", user) -- cgit v1.2.1 From c1827f1c502d6235790c7a3841587842c8ad8bc7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 20 May 2019 11:41:50 -0700 Subject: API: Fix recursive flag not working with Rugged get_tree_entries flag Attempting to use the API endpoint /projects/:id/repository/tree?recursive=true would only return a subset of the results since the full recursive list wasn't actually being returned. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/61979 --- spec/lib/gitlab/git/tree_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 7ad3cde97f8..7e169cfe270 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -19,7 +19,9 @@ describe Gitlab::Git::Tree, :seed_helper do it 'returns a list of tree objects' do entries = described_class.where(repository, SeedRepo::Commit::ID, 'files', true) - expect(entries.count).to be >= 5 + expect(entries.map(&:path)).to include('files/html', + 'files/markdown/ruby-style-guide.md') + expect(entries.count).to be >= 10 expect(entries).to all(be_a(Gitlab::Git::Tree)) end -- cgit v1.2.1 From 95afdfaeeb301699df3bbac0fde9cad0dda4d5f1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 23 Apr 2019 20:04:11 +0100 Subject: Add HamlLint::Linter::NoPlainNodes linter Add a simple haml_lint linter to report all plain nodes. "Plain nodes" in HAML are scritpless plaintext leaf nodes. Add haml-lint_todo to onboard new haml linters. Excludes existing NoPlainNodes lints. --- spec/lib/haml_lint/linter/no_plain_nodes_spec.rb | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 spec/lib/haml_lint/linter/no_plain_nodes_spec.rb (limited to 'spec') diff --git a/spec/lib/haml_lint/linter/no_plain_nodes_spec.rb b/spec/lib/haml_lint/linter/no_plain_nodes_spec.rb new file mode 100644 index 00000000000..99cc9b9bc8d --- /dev/null +++ b/spec/lib/haml_lint/linter/no_plain_nodes_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'haml_lint' +require 'haml_lint/spec' + +describe HamlLint::Linter::NoPlainNodes do + include_context 'linter' + + context 'reports when a tag has an inline plain node' do + let(:haml) { '%tag Hello Tanuki' } + let(:message) { "`Hello Tanuki` is a plain node. Please use an i18n method like `= _('Hello Tanuki')`" } + + it { is_expected.to report_lint message: message } + end + + context 'reports when a tag has multiline plain nodes' do + let(:haml) { <<-HAML } + %tag + Hello + Tanuki + HAML + + it { is_expected.to report_lint count: 1 } + end + + context 'reports when a tag has an inline plain node with interpolation' do + let(:haml) { '%tag Hello #{"Tanuki"}!' } # rubocop:disable Lint/InterpolationCheck + + it { is_expected.to report_lint } + end + + context 'does not report when a tag has an inline script' do + let(:haml) { '%tag= "Hello Tanuki"' } + + it { is_expected.not_to report_lint } + end + + context 'does not report when a tag is empty' do + let(:haml) { '%tag' } + + it { is_expected.not_to report_lint } + end + + context 'reports multiple when a tag has multiline plain nodes split by non-text nodes' do + let(:haml) { <<-HAML } + %tag + Hello + .split-node There + Tanuki + HAML + + it { is_expected.to report_lint count: 3 } + end +end -- cgit v1.2.1 From 7d5b68d837545909204e6caf2352f34ac2f1ba7a Mon Sep 17 00:00:00 2001 From: ddavison Date: Fri, 1 Mar 2019 18:03:43 -0800 Subject: Implement dynamic validation on QA Pages Elements now have the ability to be required on pages or not Currently using the default wait mechanism Altered the ElementWithPattern Cop to fit new splat for init --- spec/rubocop/cop/qa/element_with_pattern_spec.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb index c5beb40f9fd..ef20d9a1f26 100644 --- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb +++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'rubocop' @@ -23,7 +25,7 @@ describe RuboCop::Cop::QA::ElementWithPattern do element :groups_filter, 'search_field_tag :filter' ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead. element :groups_filter_placeholder, /Search by name/ - ^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead. + ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead. end RUBY end @@ -35,6 +37,13 @@ describe RuboCop::Cop::QA::ElementWithPattern do element :groups_filter_placeholder end RUBY + + expect_no_offenses(<<-RUBY) + view 'app/views/shared/groups/_search_form.html.haml' do + element :groups_filter, required: true + element :groups_filter_placeholder, required: false + end + RUBY end end -- cgit v1.2.1 From 9eeedfccbcbaa98265597a964cdf895f27fcf68b Mon Sep 17 00:00:00 2001 From: Ryan Cobb Date: Mon, 20 May 2019 13:36:59 -0600 Subject: Adds ruby and unicorn instrumentation This adds ruby and unicorn instrumentation. This was originally intended in 11.11 but due to performance concerns it was reverted. This new commit foregoes the sys-proctable gem was causing performance issues previously. --- .../gitlab/metrics/samplers/ruby_sampler_spec.rb | 30 +++++++++++++++++++--- .../metrics/samplers/unicorn_sampler_spec.rb | 25 ++++++++++++++---- spec/lib/gitlab/metrics/system_spec.rb | 24 +++++++++++++++++ 3 files changed, 71 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb index 7972ff253fe..aaf8c9fa2a0 100644 --- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb @@ -10,17 +10,20 @@ describe Gitlab::Metrics::Samplers::RubySampler do describe '#sample' do it 'samples various statistics' do - expect(Gitlab::Metrics::System).to receive(:memory_usage) + expect(Gitlab::Metrics::System).to receive(:cpu_time) expect(Gitlab::Metrics::System).to receive(:file_descriptor_count) + expect(Gitlab::Metrics::System).to receive(:memory_usage) + expect(Gitlab::Metrics::System).to receive(:process_start_time) + expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors) expect(sampler).to receive(:sample_gc) sampler.sample end - it 'adds a metric containing the memory usage' do + it 'adds a metric containing the process resident memory bytes' do expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000) - expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000) + expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000) sampler.sample end @@ -34,6 +37,27 @@ describe Gitlab::Metrics::Samplers::RubySampler do sampler.sample end + it 'adds a metric containing the process total cpu time' do + expect(Gitlab::Metrics::System).to receive(:cpu_time).and_return(0.51) + expect(sampler.metrics[:process_cpu_seconds_total]).to receive(:set).with({}, 0.51) + + sampler.sample + end + + it 'adds a metric containing the process start time' do + expect(Gitlab::Metrics::System).to receive(:process_start_time).and_return(12345) + expect(sampler.metrics[:process_start_time_seconds]).to receive(:set).with({}, 12345) + + sampler.sample + end + + it 'adds a metric containing the process max file descriptors' do + expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors).and_return(1024) + expect(sampler.metrics[:process_max_fds]).to receive(:set).with({}, 1024) + + sampler.sample + end + it 'clears any GC profiles' do expect(GC::Profiler).to receive(:clear) diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb index 4b03f3c2532..090e456644f 100644 --- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb @@ -39,8 +39,8 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do it 'updates metrics type unix and with addr' do labels = { socket_type: 'unix', socket_address: socket_address } - expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') - expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') + expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active') + expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued') subject.sample end @@ -50,7 +50,6 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do context 'unicorn listens on tcp sockets' do let(:tcp_socket_address) { '0.0.0.0:8080' } let(:tcp_sockets) { [tcp_socket_address] } - before do allow(unicorn).to receive(:listener_names).and_return(tcp_sockets) end @@ -71,13 +70,29 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do it 'updates metrics type unix and with addr' do labels = { socket_type: 'tcp', socket_address: tcp_socket_address } - expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') - expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') + expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active') + expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued') subject.sample end end end + + context 'additional metrics' do + let(:unicorn_workers) { 2 } + + before do + allow(unicorn).to receive(:listener_names).and_return([""]) + allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14) + allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers) + end + + it "sets additional metrics" do + expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers) + + subject.sample + end + end end describe '#start' do diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index 14afcdf5daa..b0603d96eb2 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -13,6 +13,18 @@ describe Gitlab::Metrics::System do expect(described_class.file_descriptor_count).to be > 0 end end + + describe '.max_open_file_descriptors' do + it 'returns the max allowed open file descriptors' do + expect(described_class.max_open_file_descriptors).to be > 0 + end + end + + describe '.process_start_time' do + it 'returns the process start time' do + expect(described_class.process_start_time).to be > 0 + end + end else describe '.memory_usage' do it 'returns 0.0' do @@ -25,6 +37,18 @@ describe Gitlab::Metrics::System do expect(described_class.file_descriptor_count).to eq(0) end end + + describe '.max_open_file_descriptors' do + it 'returns 0' do + expect(described_class.max_open_file_descriptors).to eq(0) + end + end + + describe 'process_start_time' do + it 'returns 0' do + expect(described_class.process_start_time).to eq(0) + end + end end describe '.cpu_time' do -- cgit v1.2.1 From d95043b63b5266e3e075b9869e01a5d47819c208 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 17 May 2019 13:43:03 +0100 Subject: Resolve CE/EE diffs in new group member view Part of single codebase changes. --- spec/helpers/groups/group_members_helper_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 spec/helpers/groups/group_members_helper_spec.rb (limited to 'spec') diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb new file mode 100644 index 00000000000..898c330c498 --- /dev/null +++ b/spec/helpers/groups/group_members_helper_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Groups::GroupMembersHelper do + describe '.group_member_select_options' do + let(:group) { create(:group) } + + before do + helper.instance_variable_set(:@group, group) + end + + it 'returns an options hash' do + expect(helper.group_member_select_options).to include(multiple: true, scope: :all, email_user: true) + end + end +end -- cgit v1.2.1 From 0f14b628c4e9624c42e783c0e6620e8b00073ab6 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Mon, 20 May 2019 16:19:00 -0500 Subject: Use BatchModelLoader for parent in GroupType --- spec/graphql/resolvers/group_resolver_spec.rb | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 spec/graphql/resolvers/group_resolver_spec.rb (limited to 'spec') diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb new file mode 100644 index 00000000000..5eb9cd06d15 --- /dev/null +++ b/spec/graphql/resolvers/group_resolver_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::GroupResolver do + include GraphqlHelpers + + set(:group1) { create(:group) } + set(:group2) { create(:group) } + + describe '#resolve' do + it 'batch-resolves groups by full path' do + paths = [group1.full_path, group2.full_path] + + result = batch(max_queries: 1) do + paths.map { |path| resolve_group(path) } + end + + expect(result).to contain_exactly(group1, group2) + end + + it 'resolves an unknown full_path to nil' do + result = batch { resolve_group('unknown/project') } + + expect(result).to be_nil + end + end + + def resolve_group(full_path) + resolve(described_class, args: { full_path: full_path }) + end +end -- cgit v1.2.1 From e082090e1f86366c5a18553a36238b1c73dd2e6b Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Mon, 20 May 2019 19:44:49 -0500 Subject: Remove 'already initialized constant' warnings --- spec/support/helpers/stub_configuration.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index 3e507fb133e..f6c613ad5aa 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -2,7 +2,8 @@ require 'active_support/core_ext/hash/transform_values' require 'active_support/hash_with_indifferent_access' require 'active_support/dependencies' -require_dependency 'gitlab' +# check gets rid of already initialized constant warnings when using spring +require_dependency 'gitlab' unless defined?(Gitlab) module StubConfiguration def stub_application_setting(messages) -- cgit v1.2.1 From fab6a50f17d15d21a157d4d561f41527fa943f27 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 12 Dec 2018 14:45:55 +0000 Subject: Prevent password sign in restriction bypass --- spec/controllers/sessions_controller_spec.rb | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 6bcff7f975c..9c4ddce5409 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -58,7 +58,26 @@ describe SessionsController do it 'authenticates user correctly' do post(:create, params: { user: user_params }) - expect(subject.current_user). to eq user + expect(subject.current_user).to eq user + end + + context 'with password authentication disabled' do + before do + stub_application_setting(password_authentication_enabled_for_web: false) + end + + it 'does not sign in the user' do + post(:create, params: { user: user_params }) + + expect(@request.env['warden']).not_to be_authenticated + expect(subject.current_user).to be_nil + end + + it 'returns status 403' do + post(:create, params: { user: user_params }) + + expect(response.status).to eq 403 + end end it 'creates an audit log record' do @@ -153,6 +172,19 @@ describe SessionsController do end end + context 'with password authentication disabled' do + before do + stub_application_setting(password_authentication_enabled_for_web: false) + end + + it 'allows 2FA stage of non-password login' do + authenticate_2fa(otp_attempt: user.current_otp) + + expect(@request.env['warden']).to be_authenticated + expect(subject.current_user).to eq user + end + end + ## # See #14900 issue # -- cgit v1.2.1 From 6d3f5a33419a6dd52d4f6378f674e28eb22ad323 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 21 May 2019 11:35:29 +0300 Subject: Add legacy routes for modified project path For settings, branches and tags Signed-off-by: Dmitriy Zaporozhets --- spec/routing/project_routing_spec.rb | 12 ++++++++++++ .../shared_examples/legacy_path_redirect_shared_examples.rb | 8 ++++++++ 2 files changed, 20 insertions(+) (limited to 'spec') diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 174293fe70c..9c2a824bab2 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -212,6 +212,8 @@ describe 'project routing' do expect(delete('/gitlab/gitlabhq/-/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') expect(delete('/gitlab/gitlabhq/-/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/branches", "/gitlab/gitlabhq/-/branches" end describe Projects::TagsController, 'routing' do @@ -597,6 +599,8 @@ describe 'project routing' do expect(get('/gitlab/gitlabhq/-/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') expect(get('/gitlab/gitlabhq/-/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/network/master", "/gitlab/gitlabhq/-/network/master" end describe Projects::GraphsController, 'routing' do @@ -662,4 +666,12 @@ describe 'project routing' do end end end + + describe Projects::Settings::RepositoryController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/-/settings/repository')).to route_to('projects/settings/repository#show', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/settings/repository", "/gitlab/gitlabhq/-/settings/repository" + end end diff --git a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb index f300bdd48b1..f326e502092 100644 --- a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb +++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb @@ -11,3 +11,11 @@ shared_examples 'redirecting a legacy path' do |source, target| expect(get(source)).not_to redirect_to(target) end end + +shared_examples 'redirecting a legacy project path' do |source, target| + include RSpec::Rails::RequestExampleGroup + + it "redirects #{source} to #{target}" do + expect(get(source)).to redirect_to(target) + end +end -- cgit v1.2.1 From eef6f768219e994fd3847694310e78040eb89815 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 21 May 2019 12:15:37 +0300 Subject: Add network and graphs to legacy routes too Signed-off-by: Dmitriy Zaporozhets --- spec/routing/project_routing_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 9c2a824bab2..dd9540c11d1 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -609,6 +609,8 @@ describe 'project routing' do expect(get('/gitlab/gitlabhq/-/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') expect(get('/gitlab/gitlabhq/-/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/graphs/master", "/gitlab/gitlabhq/-/graphs/master" end describe Projects::ForksController, 'routing' do -- cgit v1.2.1 From cef8d1d40847322a8cce3fdeb6f353ebfd298193 Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Tue, 21 May 2019 09:21:53 +0000 Subject: Add allow_failure attribute to Job API Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/50813 --- spec/fixtures/api/schemas/public_api/v4/job.json | 1 + spec/requests/api/jobs_spec.rb | 1 + 2 files changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/fixtures/api/schemas/public_api/v4/job.json b/spec/fixtures/api/schemas/public_api/v4/job.json index 454935422a0..c038ae0a664 100644 --- a/spec/fixtures/api/schemas/public_api/v4/job.json +++ b/spec/fixtures/api/schemas/public_api/v4/job.json @@ -28,6 +28,7 @@ "ref": { "type": "string" }, "tag": { "type": "boolean" }, "coverage": { "type": ["number", "null"] }, + "allow_failure": { "type": "boolean" }, "created_at": { "type": "string" }, "started_at": { "type": ["null", "string"] }, "finished_at": { "type": ["null", "string"] }, diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index c14507de186..43462913497 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -286,6 +286,7 @@ describe API::Jobs do expect(json_response['ref']).to eq(job.ref) expect(json_response['tag']).to eq(job.tag) expect(json_response['coverage']).to eq(job.coverage) + expect(json_response['allow_failure']).to eq(job.allow_failure) expect(Time.parse(json_response['created_at'])).to be_like_time(job.created_at) expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at) expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at) -- cgit v1.2.1 From 5ddedb6b8bf3f47c400e3d38e415462abefa3b50 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 21 May 2019 14:26:18 +0100 Subject: Added tree list row component --- .../table/__snapshots__/row_spec.js.snap | 33 +++++++++ .../repository/components/table/row_spec.js | 85 ++++++++++++++++++++++ spec/frontend/repository/utils/icon_spec.js | 23 ++++++ 3 files changed, 141 insertions(+) create mode 100644 spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap create mode 100644 spec/frontend/repository/components/table/row_spec.js create mode 100644 spec/frontend/repository/utils/icon_spec.js (limited to 'spec') diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap new file mode 100644 index 00000000000..f0b72343b6e --- /dev/null +++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Repository table row component renders table row 1`] = ` + + + + + + test + + + + + + + + + +`; diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js new file mode 100644 index 00000000000..216128dce25 --- /dev/null +++ b/spec/frontend/repository/components/table/row_spec.js @@ -0,0 +1,85 @@ +import { shallowMount, RouterLinkStub } from '@vue/test-utils'; +import TableRow from '~/repository/components/table/row.vue'; + +let vm; +let $router; + +function factory(propsData = {}) { + $router = { + push: jest.fn(), + }; + + vm = shallowMount(TableRow, { + propsData, + mocks: { + $router, + }, + stubs: { + RouterLink: RouterLinkStub, + }, + }); + + vm.setData({ ref: 'master' }); +} + +describe('Repository table row component', () => { + afterEach(() => { + vm.destroy(); + }); + + it('renders table row', () => { + factory({ + id: 1, + path: 'test', + type: 'file', + }); + + expect(vm.element).toMatchSnapshot(); + }); + + it.each` + type | component | componentName + ${'folder'} | ${RouterLinkStub} | ${'RouterLink'} + ${'file'} | ${'a'} | ${'hyperlink'} + ${'commit'} | ${'a'} | ${'hyperlink'} + `('renders a $componentName for type $type', ({ type, component }) => { + factory({ + id: 1, + path: 'test', + type, + }); + + expect(vm.find(component).exists()).toBe(true); + }); + + it.each` + type | pushes + ${'folder'} | ${true} + ${'file'} | ${false} + ${'commit'} | ${false} + `('pushes new router if type $type is folder', ({ type, pushes }) => { + factory({ + id: 1, + path: 'test', + type, + }); + + vm.trigger('click'); + + if (pushes) { + expect($router.push).toHaveBeenCalledWith({ path: '/tree/master/test' }); + } else { + expect($router.push).not.toHaveBeenCalled(); + } + }); + + it('renders commit ID for submodule', () => { + factory({ + id: 1, + path: 'test', + type: 'commit', + }); + + expect(vm.find('.commit-sha').text()).toContain('1'); + }); +}); diff --git a/spec/frontend/repository/utils/icon_spec.js b/spec/frontend/repository/utils/icon_spec.js new file mode 100644 index 00000000000..52787327bef --- /dev/null +++ b/spec/frontend/repository/utils/icon_spec.js @@ -0,0 +1,23 @@ +import { getIconName } from '~/repository/utils/icon'; + +describe('getIconName', () => { + // Tests the returning font awesome icon name + // We only test one for each file type to save testing a lot of different + // file types + it.each` + type | path | icon + ${'folder'} | ${''} | ${'folder'} + ${'commit'} | ${''} | ${'archive'} + ${'file'} | ${'test.pdf'} | ${'file-pdf-o'} + ${'file'} | ${'test.jpg'} | ${'file-image-o'} + ${'file'} | ${'test.zip'} | ${'file-archive-o'} + ${'file'} | ${'test.mp3'} | ${'file-audio-o'} + ${'file'} | ${'test.flv'} | ${'file-video-o'} + ${'file'} | ${'test.dotx'} | ${'file-word-o'} + ${'file'} | ${'test.xlsb'} | ${'file-excel-o'} + ${'file'} | ${'test.ppam'} | ${'file-powerpoint-o'} + ${'file'} | ${'test.js'} | ${'file-text-o'} + `('returns $icon for $type with path $path', ({ type, path, icon }) => { + expect(getIconName(type, path)).toEqual(icon); + }); +}); -- cgit v1.2.1 From 404a541dbc21b500cbf09c479ee3d2f341106863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 21 May 2019 02:31:03 +0200 Subject: Expire pipeline cache on finish --- spec/workers/build_finished_worker_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index 33f327d4a0c..4adb795b1d6 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -17,6 +17,7 @@ describe BuildFinishedWorker do expect_any_instance_of(BuildCoverageWorker).to receive(:perform) expect(BuildHooksWorker).to receive(:perform_async) expect(ArchiveTraceWorker).to receive(:perform_async) + expect(ExpirePipelineCacheWorker).to receive(:perform_async) described_class.new.perform(build.id) end -- cgit v1.2.1 From 101c4480b32044682e453753c6bb18c2a296b044 Mon Sep 17 00:00:00 2001 From: Tiger Date: Thu, 16 May 2019 17:49:12 -0500 Subject: Remove legacy Kubernetes #actual_namespace When Kubernetes clusters were originally built they could only exist at the project level, and so there was logic included that assumed there would only ever be a single Kubernetes namespace per cluster. We now support clusters at the group and instance level, which allows multiple namespaces. This change consolidates various project-specific fallbacks to generate namespaces, and hands all responsibility to the Clusters::KubernetesNamespace model. There is now no concept of a single namespace for a Clusters::Platforms::Kubernetes; to retrieve a namespace a project must now be supplied in all cases. This simplifies upcoming work to use a separate Kubernetes namespace per project environment (instead of a namespace per project). --- spec/lib/gitlab/prometheus/query_variables_spec.rb | 2 +- spec/models/clusters/cluster_spec.rb | 1 - spec/models/clusters/platforms/kubernetes_spec.rb | 29 ++++++++++++---------- spec/models/clusters/project_spec.rb | 1 - .../project_services/kubernetes_service_spec.rb | 22 ++++++++-------- spec/requests/api/project_clusters_spec.rb | 2 +- .../create_or_update_namespace_service_spec.rb | 2 +- spec/support/helpers/kubernetes_helpers.rb | 24 +++++++++++------- .../additional_metrics_shared_examples.rb | 2 +- 9 files changed, 47 insertions(+), 38 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb index 048f4af6020..6dc99ef26ec 100644 --- a/spec/lib/gitlab/prometheus/query_variables_spec.rb +++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Prometheus::QueryVariables do context 'with deployment platform' do context 'with project cluster' do - let(:kube_namespace) { environment.deployment_platform.actual_namespace } + let(:kube_namespace) { environment.deployment_platform.cluster.kubernetes_namespace_for(project) } before do create(:cluster, :project, :provided_by_user, projects: [project]) diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 58203da5b22..60065ff22af 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -17,7 +17,6 @@ describe Clusters::Cluster do it { is_expected.to have_one(:application_prometheus) } 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) } diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index e35d14f2282..c485850c16e 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -15,10 +15,8 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to validate_presence_of(:api_url) } it { is_expected.to validate_presence_of(:token) } - it { is_expected.to delegate_method(:project).to(:cluster) } it { is_expected.to delegate_method(:enabled?).to(:cluster) } it { is_expected.to delegate_method(:provided_by_user?).to(:cluster) } - it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) } it_behaves_like 'having unique enum values' @@ -209,7 +207,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to be_truthy } end - describe '#actual_namespace' do + describe '#kubernetes_namespace_for' do let(:cluster) { create(:cluster, :project) } let(:project) { cluster.project } @@ -219,7 +217,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching namespace: namespace) end - subject { platform.actual_namespace } + subject { platform.kubernetes_namespace_for(project) } context 'with a namespace assigned' do let(:namespace) { 'namespace-123' } @@ -305,8 +303,6 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end context 'no namespace provided' do - let(:namespace) { kubernetes.actual_namespace } - it_behaves_like 'setting variables' it 'sets KUBE_TOKEN' do @@ -389,7 +385,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end context 'with valid pods' do - let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(project), project_slug: project.full_path_slug) } let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } @@ -419,6 +415,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching let!(:cluster) { create(:cluster, :project, enabled: enabled, platform_kubernetes: service) } let(:service) { create(:cluster_platform_kubernetes, :configured) } let(:enabled) { true } + let(:namespace) { cluster.kubernetes_namespace_for(cluster.project) } context 'when cluster is disabled' do let(:enabled) { false } @@ -428,8 +425,8 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching context 'when kubernetes responds with valid pods and deployments' do before do - stub_kubeclient_pods - stub_kubeclient_deployments + stub_kubeclient_pods(namespace) + stub_kubeclient_deployments(namespace) end it { is_expected.to include(pods: [kube_pod]) } @@ -437,8 +434,8 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching context 'when kubernetes responds with 500s' do before do - stub_kubeclient_pods(status: 500) - stub_kubeclient_deployments(status: 500) + stub_kubeclient_pods(namespace, status: 500) + stub_kubeclient_deployments(namespace, status: 500) end it { expect { subject }.to raise_error(Kubeclient::HttpError) } @@ -446,12 +443,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching context 'when kubernetes responds with 404s' do before do - stub_kubeclient_pods(status: 404) - stub_kubeclient_deployments(status: 404) + stub_kubeclient_pods(namespace, status: 404) + stub_kubeclient_deployments(namespace, status: 404) end it { is_expected.to include(pods: []) } end + + context 'when the cluster is not project level' do + let(:cluster) { create(:cluster, :group, platform_kubernetes: service) } + + it { is_expected.to include(pods: []) } + end end describe '#update_kubernetes_namespace' do diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb index 2f017e69251..671af085d10 100644 --- a/spec/models/clusters/project_spec.rb +++ b/spec/models/clusters/project_spec.rb @@ -6,5 +6,4 @@ describe Clusters::Project do it { is_expected.to belong_to(:cluster) } it { is_expected.to belong_to(:project) } it { is_expected.to have_many(:kubernetes_namespaces) } - it { is_expected.to have_one(:kubernetes_namespace) } end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 3a381cb405d..2fce120381b 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -161,8 +161,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end end - describe '#actual_namespace' do - subject { service.actual_namespace } + describe '#kubernetes_namespace_for' do + subject { service.kubernetes_namespace_for(project) } shared_examples 'a correctly formatted namespace' do it 'returns a valid Kubernetes namespace name' do @@ -298,7 +298,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end context 'no namespace provided' do - let(:namespace) { subject.actual_namespace } + let(:namespace) { subject.kubernetes_namespace_for(project) } it_behaves_like 'setting variables' @@ -325,7 +325,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end context 'with valid pods' do - let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:pod) { kube_pod(environment_slug: environment.slug, namespace: service.kubernetes_namespace_for(project), project_slug: project.full_path_slug) } let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } @@ -352,6 +352,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do describe '#calculate_reactive_cache' do subject { service.calculate_reactive_cache } + let(:namespace) { service.kubernetes_namespace_for(project) } + context 'when service is inactive' do before do service.active = false @@ -362,8 +364,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'when kubernetes responds with valid pods' do before do - stub_kubeclient_pods - stub_kubeclient_deployments # Used by EE + stub_kubeclient_pods(namespace) + stub_kubeclient_deployments(namespace) # Used by EE end it { is_expected.to include(pods: [kube_pod]) } @@ -371,8 +373,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'when kubernetes responds with 500s' do before do - stub_kubeclient_pods(status: 500) - stub_kubeclient_deployments(status: 500) # Used by EE + stub_kubeclient_pods(namespace, status: 500) + stub_kubeclient_deployments(namespace, status: 500) # Used by EE end it { expect { subject }.to raise_error(Kubeclient::HttpError) } @@ -380,8 +382,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'when kubernetes responds with 404s' do before do - stub_kubeclient_pods(status: 404) - stub_kubeclient_deployments(status: 404) # Used by EE + stub_kubeclient_pods(namespace, status: 404) + stub_kubeclient_deployments(namespace, status: 404) # Used by EE end it { is_expected.to include(pods: []) } diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 5357be3cdee..fc0381159dd 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -351,7 +351,7 @@ describe API::ProjectClusters do it 'does not update cluster attributes' do expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') - expect(cluster.kubernetes_namespace.namespace).not_to eq('invalid_namespace') + expect(cluster.kubernetes_namespace_for(project)).not_to eq('invalid_namespace') end it 'returns validation errors' do diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb index 18f218fc236..be052a07da7 100644 --- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb @@ -113,7 +113,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d it 'does not create any Clusters::KubernetesNamespace' do subject - expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace) + expect(cluster.kubernetes_namespaces).to eq([kubernetes_namespace]) end it 'creates project service account' do diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index ac52acb6570..78b7ae9c00c 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -24,30 +24,34 @@ module KubernetesHelpers WebMock.stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1').to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body)) end - def stub_kubeclient_service_pods(response = nil) + def stub_kubeclient_service_pods(status: nil) stub_kubeclient_discover(service.api_url) pods_url = service.api_url + "/api/v1/pods" + response = { status: status } if status WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) end - def stub_kubeclient_pods(response = nil) + def stub_kubeclient_pods(namespace, status: nil) stub_kubeclient_discover(service.api_url) - pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" + pods_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods" + response = { status: status } if status WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) end - def stub_kubeclient_logs(pod_name, response = nil) + def stub_kubeclient_logs(pod_name, namespace, status: nil) stub_kubeclient_discover(service.api_url) - logs_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods/#{pod_name}/log?tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}" + logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}/log?tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}" + response = { status: status } if status WebMock.stub_request(:get, logs_url).to_return(response || kube_logs_response) end - def stub_kubeclient_deployments(response = nil) + def stub_kubeclient_deployments(namespace, status: nil) stub_kubeclient_discover(service.api_url) - deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{service.actual_namespace}/deployments" + deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/deployments" + response = { status: status } if status WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response) end @@ -250,10 +254,11 @@ module KubernetesHelpers # This is a partial response, it will have many more elements in reality but # these are the ones we care about at the moment - def kube_pod(name: "kube-pod", environment_slug: "production", project_slug: "project-path-slug", status: "Running", track: nil) + def kube_pod(name: "kube-pod", environment_slug: "production", namespace: "project-namespace", project_slug: "project-path-slug", status: "Running", track: nil) { "metadata" => { "name" => name, + "namespace" => namespace, "generate_name" => "generated-name-with-suffix", "creationTimestamp" => "2016-11-25T19:55:19Z", "annotations" => { @@ -369,12 +374,13 @@ module KubernetesHelpers def kube_terminals(service, pod) pod_name = pod['metadata']['name'] + pod_namespace = pod['metadata']['namespace'] containers = pod['spec']['containers'] containers.map do |container| terminal = { selectors: { pod: pod_name, container: container['name'] }, - url: container_exec_url(service.api_url, service.actual_namespace, pod_name, container['name']), + url: container_exec_url(service.api_url, pod_namespace, pod_name, container['name']), subprotocols: ['channel.k8s.io'], headers: { 'Authorization' => ["Bearer #{service.token}"] }, created_at: DateTime.parse(pod['metadata']['creationTimestamp']), diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb index 0fd67531c3b..8044b061ca5 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/prometheus/additional_metrics_shared_examples.rb @@ -46,7 +46,7 @@ RSpec.shared_examples 'additional metrics query' do describe 'project has Kubernetes service' do shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do let(:environment) { create(:environment, slug: 'environment-slug', project: project) } - let(:kube_namespace) { project.deployment_platform.actual_namespace } + let(:kube_namespace) { project.deployment_platform.kubernetes_namespace_for(project) } it_behaves_like 'query context containing environment slug and filter' -- cgit v1.2.1 From 6c35fb59b79e7abc321cee65fc1730ce67908b6b Mon Sep 17 00:00:00 2001 From: John Cai Date: Tue, 23 Apr 2019 10:32:06 -0700 Subject: Add GitDeduplicationService for deduplication housekeeping GitDeduplicationService performs idempotent operations on deduplicated projects. --- .../projects/git_deduplication_service_spec.rb | 90 ++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 spec/services/projects/git_deduplication_service_spec.rb (limited to 'spec') diff --git a/spec/services/projects/git_deduplication_service_spec.rb b/spec/services/projects/git_deduplication_service_spec.rb new file mode 100644 index 00000000000..3acbc46b473 --- /dev/null +++ b/spec/services/projects/git_deduplication_service_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::GitDeduplicationService do + include ExclusiveLeaseHelpers + + let(:pool) { create(:pool_repository, :ready) } + let(:project) { create(:project, :repository) } + let(:lease_key) { "git_deduplication:#{project.id}" } + let(:lease_timeout) { Projects::GitDeduplicationService::LEASE_TIMEOUT } + + subject(:service) { described_class.new(project) } + + describe '#execute' do + context 'when there is not already a lease' do + context 'when the project does not have a pool repository' do + it 'calls disconnect_git_alternates' do + stub_exclusive_lease(lease_key, timeout: lease_timeout) + + expect(project.repository).to receive(:disconnect_alternates) + + service.execute + end + end + + context 'when the project has a pool repository' do + let(:project) { create(:project, :repository, pool_repository: pool) } + + context 'when the project is a source project' do + let(:lease_key) { "git_deduplication:#{pool.source_project.id}" } + + subject(:service) { described_class.new(pool.source_project) } + + it 'calls fetch' do + stub_exclusive_lease(lease_key, timeout: lease_timeout) + allow(pool.source_project).to receive(:git_objects_poolable?).and_return(true) + + expect(pool.object_pool).to receive(:fetch) + + service.execute + end + + it 'does not call fetch if git objects are not poolable' do + stub_exclusive_lease(lease_key, timeout: lease_timeout) + allow(pool.source_project).to receive(:git_objects_poolable?).and_return(false) + + expect(pool.object_pool).not_to receive(:fetch) + + service.execute + end + + it 'does not call fetch if pool and project are not on the same storage' do + stub_exclusive_lease(lease_key, timeout: lease_timeout) + allow(pool.source_project.repository).to receive(:storage).and_return('special_storage_001') + + expect(pool.object_pool).not_to receive(:fetch) + + service.execute + end + end + + it 'links the repository to the object pool' do + expect(project).to receive(:link_pool_repository) + + service.execute + end + + it 'does not link the repository to the object pool if they are not on the same storage' do + allow(project.repository).to receive(:storage).and_return('special_storage_001') + expect(project).not_to receive(:link_pool_repository) + + service.execute + end + end + + context 'when a lease is already out' do + before do + stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) + end + + it 'fails when a lease is already out' do + expect(service).to receive(:log_error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.') + + service.execute + end + end + end + end +end -- cgit v1.2.1 From 3783c23457adaa4a1f97ffd0891a1a5a8d02f188 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Mon, 20 May 2019 17:13:29 +1200 Subject: Link to issue in CodeReuse/ActiveRecord message This provides the user with some context as to why certain ActiveRecord methods are blacklisted. --- spec/rubocop/cop/code_reuse/active_record_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/rubocop/cop/code_reuse/active_record_spec.rb b/spec/rubocop/cop/code_reuse/active_record_spec.rb index a30fc52d26f..8f3a3690d88 100644 --- a/spec/rubocop/cop/code_reuse/active_record_spec.rb +++ b/spec/rubocop/cop/code_reuse/active_record_spec.rb @@ -14,7 +14,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do expect_offense(<<~SOURCE) def foo User.where - ^^^^^ This method can only be used inside an ActiveRecord model + ^^^^^ This method can only be used inside an ActiveRecord model: https://gitlab.com/gitlab-org/gitlab-ce/issues/49653 end SOURCE end @@ -23,7 +23,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do expect_offense(<<~SOURCE) def foo User.where(id: 10) - ^^^^^ This method can only be used inside an ActiveRecord model + ^^^^^ This method can only be used inside an ActiveRecord model: https://gitlab.com/gitlab-org/gitlab-ce/issues/49653 end SOURCE end @@ -40,7 +40,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do expect_offense(<<~SOURCE) def foo project.group(:name) - ^^^^^ This method can only be used inside an ActiveRecord model + ^^^^^ This method can only be used inside an ActiveRecord model: https://gitlab.com/gitlab-org/gitlab-ce/issues/49653 end SOURCE end -- cgit v1.2.1 From 27381e22a92453b23f1ed75406970b37d926f1ec Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 21 May 2019 19:49:14 -0700 Subject: Move files from lib/haml_lint to haml_lint Files in lib will be eager loaded and hence will require haml_lint to be loaded. Since this is only a development dependency, we can't assume this gem will be available in production, so it should never be loaded in production. --- spec/haml_lint/linter/no_plain_nodes_spec.rb | 56 ++++++++++++++++++++++++ spec/lib/haml_lint/linter/no_plain_nodes_spec.rb | 55 ----------------------- 2 files changed, 56 insertions(+), 55 deletions(-) create mode 100644 spec/haml_lint/linter/no_plain_nodes_spec.rb delete mode 100644 spec/lib/haml_lint/linter/no_plain_nodes_spec.rb (limited to 'spec') diff --git a/spec/haml_lint/linter/no_plain_nodes_spec.rb b/spec/haml_lint/linter/no_plain_nodes_spec.rb new file mode 100644 index 00000000000..08deb5a4e9e --- /dev/null +++ b/spec/haml_lint/linter/no_plain_nodes_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'haml_lint' +require 'haml_lint/spec' +require Rails.root.join('haml_lint/linter/no_plain_nodes') + +describe HamlLint::Linter::NoPlainNodes do + include_context 'linter' + + context 'reports when a tag has an inline plain node' do + let(:haml) { '%tag Hello Tanuki' } + let(:message) { "`Hello Tanuki` is a plain node. Please use an i18n method like `= _('Hello Tanuki')`" } + + it { is_expected.to report_lint message: message } + end + + context 'reports when a tag has multiline plain nodes' do + let(:haml) { <<-HAML } + %tag + Hello + Tanuki + HAML + + it { is_expected.to report_lint count: 1 } + end + + context 'reports when a tag has an inline plain node with interpolation' do + let(:haml) { '%tag Hello #{"Tanuki"}!' } # rubocop:disable Lint/InterpolationCheck + + it { is_expected.to report_lint } + end + + context 'does not report when a tag has an inline script' do + let(:haml) { '%tag= "Hello Tanuki"' } + + it { is_expected.not_to report_lint } + end + + context 'does not report when a tag is empty' do + let(:haml) { '%tag' } + + it { is_expected.not_to report_lint } + end + + context 'reports multiple when a tag has multiline plain nodes split by non-text nodes' do + let(:haml) { <<-HAML } + %tag + Hello + .split-node There + Tanuki + HAML + + it { is_expected.to report_lint count: 3 } + end +end diff --git a/spec/lib/haml_lint/linter/no_plain_nodes_spec.rb b/spec/lib/haml_lint/linter/no_plain_nodes_spec.rb deleted file mode 100644 index 99cc9b9bc8d..00000000000 --- a/spec/lib/haml_lint/linter/no_plain_nodes_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'haml_lint' -require 'haml_lint/spec' - -describe HamlLint::Linter::NoPlainNodes do - include_context 'linter' - - context 'reports when a tag has an inline plain node' do - let(:haml) { '%tag Hello Tanuki' } - let(:message) { "`Hello Tanuki` is a plain node. Please use an i18n method like `= _('Hello Tanuki')`" } - - it { is_expected.to report_lint message: message } - end - - context 'reports when a tag has multiline plain nodes' do - let(:haml) { <<-HAML } - %tag - Hello - Tanuki - HAML - - it { is_expected.to report_lint count: 1 } - end - - context 'reports when a tag has an inline plain node with interpolation' do - let(:haml) { '%tag Hello #{"Tanuki"}!' } # rubocop:disable Lint/InterpolationCheck - - it { is_expected.to report_lint } - end - - context 'does not report when a tag has an inline script' do - let(:haml) { '%tag= "Hello Tanuki"' } - - it { is_expected.not_to report_lint } - end - - context 'does not report when a tag is empty' do - let(:haml) { '%tag' } - - it { is_expected.not_to report_lint } - end - - context 'reports multiple when a tag has multiline plain nodes split by non-text nodes' do - let(:haml) { <<-HAML } - %tag - Hello - .split-node There - Tanuki - HAML - - it { is_expected.to report_lint count: 3 } - end -end -- cgit v1.2.1 From 19b6c8100b20271a24ec4d63cbc1b8da42300e43 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 22 May 2019 04:03:11 +0100 Subject: Resolve CE/EE diffs in api_spec Part of single codebase changes. --- spec/javascripts/api_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 494b3b934a8..805bb10bda6 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -288,7 +288,7 @@ describe('Api', () => { it('creates a group label', done => { const namespace = 'group/subgroup'; const labelData = { some: 'data' }; - const expectedUrl = `${dummyUrlRoot}/groups/${namespace}/-/labels`; + const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace); const expectedData = { label: labelData, }; -- cgit v1.2.1 From 00c851384e8bc957f601d27c25c36c2ca80b60c1 Mon Sep 17 00:00:00 2001 From: Mathieu Parent Date: Thu, 16 May 2019 22:07:42 +0200 Subject: API: Allow to get and set "masked" attribute for variables --- spec/requests/api/variables_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index cc07869a744..55b1419a004 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -43,6 +43,7 @@ describe API::Variables do expect(response).to have_gitlab_http_status(200) expect(json_response['value']).to eq(variable.value) expect(json_response['protected']).to eq(variable.protected?) + expect(json_response['masked']).to eq(variable.masked?) expect(json_response['variable_type']).to eq('env_var') end @@ -74,13 +75,14 @@ describe API::Variables do context 'authorized user with proper permissions' do it 'creates variable' do expect do - post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true } + post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true, masked: true } end.to change {project.variables.count}.by(1) expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy + expect(json_response['masked']).to be_truthy expect(json_response['variable_type']).to eq('env_var') end @@ -93,6 +95,7 @@ describe API::Variables do expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_falsey + expect(json_response['masked']).to be_falsey expect(json_response['variable_type']).to eq('file') end -- cgit v1.2.1 From 07a353259424cd225368ca4af59883eebf10066d Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 21 May 2019 18:04:09 +0300 Subject: Geo: Remove Gitlab::LfsToken::LegacyRedisDeviseToken implementation We kept it for smooth update only --- spec/lib/gitlab/lfs_token_spec.rb | 98 +++++++++------------------------------ 1 file changed, 22 insertions(+), 76 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index 8961ecc4be0..701ed1f3a1b 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -77,96 +77,42 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do let(:actor) { create(:user, username: 'test_user_lfs_1') } let(:lfs_token) { described_class.new(actor) } - context 'for an HMAC token' do - before do - # We're not interested in testing LegacyRedisDeviseToken here - allow(Gitlab::LfsToken::LegacyRedisDeviseToken).to receive_message_chain(:new, :token_valid?).and_return(false) - end - - context 'where the token is invalid' do - context "because it's junk" do - it 'returns false' do - expect(lfs_token.token_valid?('junk')).to be_falsey - end - end - - context "because it's been fiddled with" do - it 'returns false' do - fiddled_token = lfs_token.token.tap { |token| token[0] = 'E' } - expect(lfs_token.token_valid?(fiddled_token)).to be_falsey - end - end - - context "because it was generated with a different secret" do - it 'returns false' do - different_actor = create(:user, username: 'test_user_lfs_2') - different_secret_token = described_class.new(different_actor).token - expect(lfs_token.token_valid?(different_secret_token)).to be_falsey - end - end - - context "because it's expired" do - it 'returns false' do - expired_token = lfs_token.token - # Needs to be at least 1860 seconds, because the default expiry is - # 1800 seconds with an additional 60 second leeway. - Timecop.freeze(Time.now + 1865) do - expect(lfs_token.token_valid?(expired_token)).to be_falsey - end - end + context 'where the token is invalid' do + context "because it's junk" do + it 'returns false' do + expect(lfs_token.token_valid?('junk')).to be_falsey end end - context 'where the token is valid' do - it 'returns true' do - expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy + context "because it's been fiddled with" do + it 'returns false' do + fiddled_token = lfs_token.token.tap { |token| token[0] = 'E' } + expect(lfs_token.token_valid?(fiddled_token)).to be_falsey end end - end - - context 'for a LegacyRedisDevise token' do - before do - # We're not interested in testing HMACToken here - allow_any_instance_of(Gitlab::LfsToken::HMACToken).to receive(:token_valid?).and_return(false) - end - - context 'where the token is invalid' do - context "because it's junk" do - it 'returns false' do - expect(lfs_token.token_valid?('junk')).to be_falsey - end - end - context "because it's been fiddled with" do - it 'returns false' do - generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token - fiddled_token = generated_token.tap { |token| token[0] = 'E' } - expect(lfs_token.token_valid?(fiddled_token)).to be_falsey - end - end - - context "because it was generated with a different secret" do - it 'returns false' do - different_actor = create(:user, username: 'test_user_lfs_2') - different_secret_token = described_class.new(different_actor).token - expect(lfs_token.token_valid?(different_secret_token)).to be_falsey - end + context "because it was generated with a different secret" do + it 'returns false' do + different_actor = create(:user, username: 'test_user_lfs_2') + different_secret_token = described_class.new(different_actor).token + expect(lfs_token.token_valid?(different_secret_token)).to be_falsey end + end - context "because it's expired" do - it 'returns false' do - generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token(1) - # We need a real sleep here because we need to wait for redis to expire the key. - sleep(0.01) - expect(lfs_token.token_valid?(generated_token)).to be_falsey + context "because it's expired" do + it 'returns false' do + expired_token = lfs_token.token + # Needs to be at least 1860 seconds, because the default expiry is + # 1800 seconds with an additional 60 second leeway. + Timecop.freeze(Time.now + 1865) do + expect(lfs_token.token_valid?(expired_token)).to be_falsey end end end context 'where the token is valid' do it 'returns true' do - generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token - expect(lfs_token.token_valid?(generated_token)).to be_truthy + expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy end end end -- cgit v1.2.1 From 52d24e66cec1fa6c0100adb64effd2073869cd8b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 18 May 2019 21:58:21 -0700 Subject: Bump shoulda-matchers to 4.0.1 This removes the warning when using Ruby 2.6: BigDecimal.new is deprecated; use BigDecimal() method instead. This also adds Rails 5 support, which eliminates the need for the monkey patch to handle https://github.com/thoughtbot/shoulda-matchers/issues/913. --- spec/support/shoulda/matchers/rails_shim.rb | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 spec/support/shoulda/matchers/rails_shim.rb (limited to 'spec') diff --git a/spec/support/shoulda/matchers/rails_shim.rb b/spec/support/shoulda/matchers/rails_shim.rb deleted file mode 100644 index 8d70598beb5..00000000000 --- a/spec/support/shoulda/matchers/rails_shim.rb +++ /dev/null @@ -1,27 +0,0 @@ -# monkey patch which fixes serialization matcher in Rails 5 -# https://github.com/thoughtbot/shoulda-matchers/issues/913 -# This can be removed when a new version of shoulda-matchers -# is released -module Shoulda - module Matchers - class RailsShim - def self.serialized_attributes_for(model) - if defined?(::ActiveRecord::Type::Serialized) - # Rails 5+ - serialized_columns = model.columns.select do |column| - model.type_for_attribute(column.name).is_a?( - ::ActiveRecord::Type::Serialized - ) - end - - serialized_columns.inject({}) do |hash, column| # rubocop:disable Style/EachWithObject - hash[column.name.to_s] = model.type_for_attribute(column.name).coder - hash - end - else - model.serialized_attributes - end - end - end - end -end -- cgit v1.2.1 From d707e2a49f2efe4670b5ebe62fb61554640ca7e9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 19 May 2019 04:01:16 -0700 Subject: Ensure subject passes validations Rails 5 requires that belongs_to associations have the associated record present. These tests were failing because they had nil values. --- spec/models/ci/pipeline_schedule_spec.rb | 2 ++ spec/models/clusters/cluster_spec.rb | 2 ++ 2 files changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 1bfc14d2839..42d4769a921 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Ci::PipelineSchedule do + subject { build(:ci_pipeline_schedule) } + it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:owner) } diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 58203da5b22..f9d8ffd06e0 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -5,6 +5,8 @@ require 'spec_helper' describe Clusters::Cluster do it_behaves_like 'having unique enum values' + subject { build(:cluster) } + it { is_expected.to belong_to(:user) } it { is_expected.to have_many(:cluster_projects) } it { is_expected.to have_many(:projects) } -- cgit v1.2.1 From 37a335e60edd4f4c8b61fd2f9ffe0f1d27e3bfb8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 21 May 2019 14:07:37 -0700 Subject: Fix remaining failures in shoulda-matcher Starting with Rails 5, belongs_to now adds a presence validation to the association, and so as of shoulda-matchers 4.0.0 the belong_to matcher follows suit and tests that this validation is there by setting the association to nil and asserting that there are validation errors. This exposed an error with the `validate_branches` method: we need to check the source and target project exist. --- spec/models/deployment_spec.rb | 4 ++-- spec/models/environment_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index f51322e1404..1dceef3fc00 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -5,8 +5,8 @@ require 'spec_helper' describe Deployment do subject { build(:deployment) } - it { is_expected.to belong_to(:project) } - it { is_expected.to belong_to(:environment) } + it { is_expected.to belong_to(:project).required } + it { is_expected.to belong_to(:environment).required } it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:deployable) } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 17246f238e0..7233d2454c6 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -6,7 +6,7 @@ describe Environment do let(:project) { create(:project, :stubbed_repository) } subject(:environment) { create(:environment, project: project) } - it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:project).required } it { is_expected.to have_many(:deployments) } it { is_expected.to delegate_method(:stop_action).to(:last_deployment) } -- cgit v1.2.1 From fa3aa0c5a7e8910a658572dce3ad5291e1112db1 Mon Sep 17 00:00:00 2001 From: Kartikey Tanna Date: Wed, 22 May 2019 07:59:15 +0000 Subject: #61441 Allow user to set email ID before setting up 2FA --- spec/controllers/application_controller_spec.rb | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 7296a4b4526..5ecd1b6b7c8 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -206,8 +206,19 @@ describe ApplicationController do describe '#check_two_factor_requirement' do subject { controller.send :check_two_factor_requirement } + it 'does not redirect if user has temporary oauth email' do + oauth_user = create(:user, email: 'temp-email-for-oauth@email.com') + allow(controller).to receive(:two_factor_authentication_required?).and_return(true) + allow(controller).to receive(:current_user).and_return(oauth_user) + + expect(controller).not_to receive(:redirect_to) + + subject + end + it 'does not redirect if 2FA is not required' do allow(controller).to receive(:two_factor_authentication_required?).and_return(false) + expect(controller).not_to receive(:redirect_to) subject @@ -216,6 +227,7 @@ describe ApplicationController do it 'does not redirect if user is not logged in' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) allow(controller).to receive(:current_user).and_return(nil) + expect(controller).not_to receive(:redirect_to) subject @@ -223,8 +235,9 @@ describe ApplicationController do it 'does not redirect if user has 2FA enabled' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) - allow(controller).to receive(:current_user).twice.and_return(user) + allow(controller).to receive(:current_user).thrice.and_return(user) allow(user).to receive(:two_factor_enabled?).and_return(true) + expect(controller).not_to receive(:redirect_to) subject @@ -232,9 +245,10 @@ describe ApplicationController do it 'does not redirect if 2FA setup can be skipped' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) - allow(controller).to receive(:current_user).twice.and_return(user) + allow(controller).to receive(:current_user).thrice.and_return(user) allow(user).to receive(:two_factor_enabled?).and_return(false) allow(controller).to receive(:skip_two_factor?).and_return(true) + expect(controller).not_to receive(:redirect_to) subject @@ -242,10 +256,11 @@ describe ApplicationController do it 'redirects to 2FA setup otherwise' do allow(controller).to receive(:two_factor_authentication_required?).and_return(true) - allow(controller).to receive(:current_user).twice.and_return(user) + allow(controller).to receive(:current_user).thrice.and_return(user) allow(user).to receive(:two_factor_enabled?).and_return(false) allow(controller).to receive(:skip_two_factor?).and_return(false) allow(controller).to receive(:profile_two_factor_auth_path) + expect(controller).to receive(:redirect_to) subject -- cgit v1.2.1 From 67a50861953be9347e267795a9ce736dfea5316b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 22 May 2019 12:43:35 +0100 Subject: Added repository files to GraphQL API --- spec/graphql/resolvers/tree_resolver_spec.rb | 35 +++++++++++ spec/graphql/types/project_type_spec.rb | 2 + spec/graphql/types/repository_type_spec.rb | 11 ++++ spec/graphql/types/tree/blob_type_spec.rb | 9 +++ spec/graphql/types/tree/submodule_type_spec.rb | 9 +++ spec/graphql/types/tree/tree_entry_type_spec.rb | 9 +++ spec/graphql/types/tree/tree_type_spec.rb | 9 +++ spec/graphql/types/tree/type_enum_spec.rb | 11 ++++ .../api/graphql/project/repository_spec.rb | 37 +++++++++++ .../requests/api/graphql/project/tree/tree_spec.rb | 73 ++++++++++++++++++++++ 10 files changed, 205 insertions(+) create mode 100644 spec/graphql/resolvers/tree_resolver_spec.rb create mode 100644 spec/graphql/types/repository_type_spec.rb create mode 100644 spec/graphql/types/tree/blob_type_spec.rb create mode 100644 spec/graphql/types/tree/submodule_type_spec.rb create mode 100644 spec/graphql/types/tree/tree_entry_type_spec.rb create mode 100644 spec/graphql/types/tree/tree_type_spec.rb create mode 100644 spec/graphql/types/tree/type_enum_spec.rb create mode 100644 spec/requests/api/graphql/project/repository_spec.rb create mode 100644 spec/requests/api/graphql/project/tree/tree_spec.rb (limited to 'spec') diff --git a/spec/graphql/resolvers/tree_resolver_spec.rb b/spec/graphql/resolvers/tree_resolver_spec.rb new file mode 100644 index 00000000000..9f95b740ab1 --- /dev/null +++ b/spec/graphql/resolvers/tree_resolver_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Resolvers::TreeResolver do + include GraphqlHelpers + + let(:repository) { create(:project, :repository).repository } + + describe '#resolve' do + it 'resolves to a tree' do + result = resolve_repository({ ref: "master" }) + + expect(result).to be_an_instance_of(Tree) + end + + it 'resolve to a recursive tree' do + result = resolve_repository({ ref: "master", recursive: true }) + + expect(result.trees[4].path).to eq('files/html') + end + + context 'when repository does not exist' do + it 'returns nil' do + allow(repository).to receive(:exists?).and_return(false) + + result = resolve_repository({ ref: "master" }) + + expect(result).to be(nil) + end + end + end + + def resolve_repository(args) + resolve(described_class, obj: repository, args: args) + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index e0ad09bdf22..075fa7c7e43 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -17,4 +17,6 @@ describe GitlabSchema.types['Project'] do end it { is_expected.to have_graphql_field(:pipelines) } + + it { is_expected.to have_graphql_field(:repository) } end diff --git a/spec/graphql/types/repository_type_spec.rb b/spec/graphql/types/repository_type_spec.rb new file mode 100644 index 00000000000..8a8238f2a2a --- /dev/null +++ b/spec/graphql/types/repository_type_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe GitlabSchema.types['Repository'] do + it { expect(described_class.graphql_name).to eq('Repository') } + + it { expect(described_class).to require_graphql_authorizations(:download_code) } + + it { is_expected.to have_graphql_field(:root_ref) } + + it { is_expected.to have_graphql_field(:tree) } +end diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb new file mode 100644 index 00000000000..fa29bb5fff7 --- /dev/null +++ b/spec/graphql/types/tree/blob_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::Tree::BlobType do + it { expect(described_class.graphql_name).to eq('Blob') } + + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) } +end diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb new file mode 100644 index 00000000000..bdb3149b41c --- /dev/null +++ b/spec/graphql/types/tree/submodule_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::Tree::SubmoduleType do + it { expect(described_class.graphql_name).to eq('Submodule') } + + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) } +end diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb new file mode 100644 index 00000000000..397cabde8e5 --- /dev/null +++ b/spec/graphql/types/tree/tree_entry_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::Tree::TreeEntryType do + it { expect(described_class.graphql_name).to eq('TreeEntry') } + + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) } +end diff --git a/spec/graphql/types/tree/tree_type_spec.rb b/spec/graphql/types/tree/tree_type_spec.rb new file mode 100644 index 00000000000..b9c5570115e --- /dev/null +++ b/spec/graphql/types/tree/tree_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::Tree::TreeType do + it { expect(described_class.graphql_name).to eq('Tree') } + + it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs) } +end diff --git a/spec/graphql/types/tree/type_enum_spec.rb b/spec/graphql/types/tree/type_enum_spec.rb new file mode 100644 index 00000000000..4caf9e1c457 --- /dev/null +++ b/spec/graphql/types/tree/type_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::Tree::TypeEnum do + it { expect(described_class.graphql_name).to eq('EntryType') } + + it 'exposes all tree entry types' do + expect(described_class.values.keys).to include(*%w[tree blob commit]) + end +end diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb new file mode 100644 index 00000000000..67af612a4a0 --- /dev/null +++ b/spec/requests/api/graphql/project/repository_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'getting a repository in a project' do + include GraphqlHelpers + + let(:project) { create(:project, :repository) } + let(:current_user) { project.owner } + let(:fields) do + <<~QUERY + #{all_graphql_fields_for('repository'.classify)} + QUERY + end + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('repository', {}, fields) + ) + end + + it 'returns repository' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']).to be_present + end + + context 'as a non-authorized user' do + let(:current_user) { create(:user) } + + it 'returns nil' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']).to be(nil) + end + end +end diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb new file mode 100644 index 00000000000..b07aa1e12d3 --- /dev/null +++ b/spec/requests/api/graphql/project/tree/tree_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'getting a tree in a project' do + include GraphqlHelpers + + let(:project) { create(:project, :repository) } + let(:current_user) { project.owner } + let(:path) { "" } + let(:ref) { "master" } + let(:fields) do + <<~QUERY + tree(path:"#{path}", ref:"#{ref}") { + #{all_graphql_fields_for('tree'.classify)} + } + QUERY + end + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('repository', {}, fields) + ) + end + + context 'when path does not exist' do + let(:path) { "testing123" } + + it 'returns empty tree' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['tree']['trees']['edges']).to eq([]) + expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([]) + expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([]) + end + end + + context 'when ref does not exist' do + let(:ref) { "testing123" } + + it 'returns empty tree' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['tree']['trees']['edges']).to eq([]) + expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([]) + expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([]) + end + end + + context 'when ref and path exist' do + it 'returns tree' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['tree']).to be_present + end + + it 'returns blobs, subtrees and submodules inside tree' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['tree']['trees']['edges'].size).to be > 0 + expect(graphql_data['project']['repository']['tree']['blobs']['edges'].size).to be > 0 + expect(graphql_data['project']['repository']['tree']['submodules']['edges'].size).to be > 0 + end + end + + context 'when current user is nil' do + it 'returns empty project' do + post_graphql(query, current_user: nil) + + expect(graphql_data['project']).to be(nil) + end + end +end -- cgit v1.2.1 From 4f20c9de6f8e09131d751174243214d9bd3b6419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 22 May 2019 11:45:46 +0000 Subject: Revert "Merge branch 'reorganize-tests-jobs-by-level' into 'master'" This reverts merge request !27514 --- spec/lib/quality/test_level_spec.rb | 105 --------- spec/spec_helper.rb | 5 +- .../finders/assignees_filter_shared_examples.rb | 49 ---- .../finders/assignees_filter_spec.rb | 49 ++++ .../models/atomic_internal_id_shared_examples.rb | 113 ---------- .../models/atomic_internal_id_spec.rb | 113 ++++++++++ .../models/chat_service_shared_examples.rb | 248 --------------------- .../shared_examples/models/chat_service_spec.rb | 248 +++++++++++++++++++++ .../update_project_statistics_shared_examples.rb | 76 ------- .../models/update_project_statistics_spec.rb | 76 +++++++ .../requests/api/issues_shared_example_spec.rb | 44 ++++ .../requests/api/issues_shared_examples.rb | 44 ---- 12 files changed, 531 insertions(+), 639 deletions(-) delete mode 100644 spec/lib/quality/test_level_spec.rb delete mode 100644 spec/support/shared_examples/finders/assignees_filter_shared_examples.rb create mode 100644 spec/support/shared_examples/finders/assignees_filter_spec.rb delete mode 100644 spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb create mode 100644 spec/support/shared_examples/models/atomic_internal_id_spec.rb delete mode 100644 spec/support/shared_examples/models/chat_service_shared_examples.rb create mode 100644 spec/support/shared_examples/models/chat_service_spec.rb delete mode 100644 spec/support/shared_examples/models/update_project_statistics_shared_examples.rb create mode 100644 spec/support/shared_examples/models/update_project_statistics_spec.rb create mode 100644 spec/support/shared_examples/requests/api/issues_shared_example_spec.rb delete mode 100644 spec/support/shared_examples/requests/api/issues_shared_examples.rb (limited to 'spec') diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb deleted file mode 100644 index 3465c3a050b..00000000000 --- a/spec/lib/quality/test_level_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -require 'fast_spec_helper' - -RSpec.describe Quality::TestLevel do - describe '#pattern' do - context 'when level is unit' do - it 'returns a pattern' do - expect(subject.pattern(:unit)) - .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") - end - end - - context 'when level is integration' do - it 'returns a pattern' do - expect(subject.pattern(:integration)) - .to eq("spec/{controllers,mailers,requests}{,/**/}*_spec.rb") - end - end - - context 'when level is system' do - it 'returns a pattern' do - expect(subject.pattern(:system)) - .to eq("spec/{features}{,/**/}*_spec.rb") - end - end - - context 'with a prefix' do - it 'returns a pattern' do - expect(described_class.new('ee/').pattern(:system)) - .to eq("ee/spec/{features}{,/**/}*_spec.rb") - end - end - - describe 'performance' do - it 'memoizes the pattern for a given level' do - expect(subject.pattern(:system).object_id).to eq(subject.pattern(:system).object_id) - end - - it 'freezes the pattern for a given level' do - expect(subject.pattern(:system)).to be_frozen - end - end - end - - describe '#regexp' do - context 'when level is unit' do - it 'returns a regexp' do - expect(subject.regexp(:unit)) - .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) - end - end - - context 'when level is integration' do - it 'returns a regexp' do - expect(subject.regexp(:integration)) - .to eq(%r{spec/(controllers|mailers|requests)}) - end - end - - context 'when level is system' do - it 'returns a regexp' do - expect(subject.regexp(:system)) - .to eq(%r{spec/(features)}) - end - end - - context 'with a prefix' do - it 'returns a regexp' do - expect(described_class.new('ee/').regexp(:system)) - .to eq(%r{ee/spec/(features)}) - end - end - - describe 'performance' do - it 'memoizes the regexp for a given level' do - expect(subject.regexp(:system).object_id).to eq(subject.regexp(:system).object_id) - end - - it 'freezes the regexp for a given level' do - expect(subject.regexp(:system)).to be_frozen - end - end - end - - describe '#level_for' do - it 'returns the correct level for a unit test' do - expect(subject.level_for('spec/models/abuse_report_spec.rb')).to eq(:unit) - end - - it 'returns the correct level for an integration test' do - expect(subject.level_for('spec/mailers/abuse_report_mailer_spec.rb')).to eq(:integration) - end - - it 'returns the correct level for a system test' do - expect(subject.level_for('spec/features/abuse_report_spec.rb')).to eq(:system) - end - - it 'raises an error for an unknown level' do - expect { subject.level_for('spec/unknown/foo_spec.rb') } - .to raise_error(described_class::UnknownTestLevelError, - %r{Test level for spec/unknown/foo_spec.rb couldn't be set. Please rename the file properly or change the test level detection regexes in .+/lib/quality/test_level.rb.}) - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 390a869d93f..69589c9aa33 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,8 +44,6 @@ Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } -quality_level = Quality::TestLevel.new - RSpec.configure do |config| config.use_transactional_fixtures = false config.use_instantiated_fixtures = false @@ -57,10 +55,9 @@ RSpec.configure do |config| config.infer_spec_type_from_file_location! config.full_backtrace = !!ENV['CI'] - config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata| + config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| location = metadata[:location] - metadata[:level] = quality_level.level_for(location) metadata[:api] = true if location =~ %r{/spec/requests/api/} # do not overwrite type if it's already set diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb deleted file mode 100644 index 782a2d97746..00000000000 --- a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'assignee ID filter' do - it 'returns issuables assigned to that user' do - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'assignee username filter' do - it 'returns issuables assigned to those users' do - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'no assignee filter' do - let(:params) { { assignee_id: 'None' } } - - it 'returns issuables not assigned to any assignee' do - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables not assigned to any assignee' do - params[:assignee_id] = 0 - - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables not assigned to any assignee' do - params[:assignee_id] = 'none' - - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'any assignee filter' do - context '' do - let(:params) { { assignee_id: 'Any' } } - - it 'returns issuables assigned to any assignee' do - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables assigned to any assignee' do - params[:assignee_id] = 'any' - - expect(issuables).to contain_exactly(*expected_issuables) - end - end -end diff --git a/spec/support/shared_examples/finders/assignees_filter_spec.rb b/spec/support/shared_examples/finders/assignees_filter_spec.rb new file mode 100644 index 00000000000..782a2d97746 --- /dev/null +++ b/spec/support/shared_examples/finders/assignees_filter_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +shared_examples 'assignee ID filter' do + it 'returns issuables assigned to that user' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'assignee username filter' do + it 'returns issuables assigned to those users' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'no assignee filter' do + let(:params) { { assignee_id: 'None' } } + + it 'returns issuables not assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 0 + + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 'none' + + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'any assignee filter' do + context '' do + let(:params) { { assignee_id: 'Any' } } + + it 'returns issuables assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables assigned to any assignee' do + params[:assignee_id] = 'any' + + expect(issuables).to contain_exactly(*expected_issuables) + end + end +end diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb deleted file mode 100644 index a248f60d23e..00000000000 --- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'spec_helper' - -shared_examples_for 'AtomicInternalId' do |validate_presence: true| - describe '.has_internal_id' do - describe 'Module inclusion' do - subject { described_class } - - it { is_expected.to include_module(AtomicInternalId) } - end - - describe 'Validation' do - before do - allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") - - instance.valid? - end - - context 'when presence validation is required' do - before do - skip unless validate_presence - end - - it 'validates presence' do - expect(instance.errors[internal_id_attribute]).to include("can't be blank") - end - end - - context 'when presence validation is not required' do - before do - skip if validate_presence - end - - it 'does not validate presence' do - expect(instance.errors[internal_id_attribute]).to be_empty - end - end - end - - describe 'Creating an instance' do - subject { instance.save! } - - it 'saves a new instance properly' do - expect { subject }.not_to raise_error - end - end - - describe 'internal id generation' do - subject { instance.save! } - - it 'calls InternalId.generate_next and sets internal id attribute' do - iid = rand(1..1000) - - expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid) - subject - expect(read_internal_id).to eq(iid) - end - - it 'does not overwrite an existing internal id' do - write_internal_id(4711) - - expect { subject }.not_to change { read_internal_id } - end - - context 'when the instance has an internal ID set' do - let(:internal_id) { 9001 } - - it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do - write_internal_id(internal_id) - - expect(InternalId) - .to receive(:track_greatest) - .with(instance, scope_attrs, usage, internal_id, any_args) - .and_return(internal_id) - subject - end - end - end - - describe "#reset_scope_internal_id_attribute" do - it 'rewinds the allocated IID' do - expect { ensure_scope_attribute! }.not_to raise_error - expect(read_internal_id).not_to be_nil - - expect(reset_scope_attribute).to be_nil - expect(read_internal_id).to be_nil - end - - it 'allocates the same IID' do - internal_id = ensure_scope_attribute! - reset_scope_attribute - expect(read_internal_id).to be_nil - - expect(ensure_scope_attribute!).to eq(internal_id) - end - end - - def ensure_scope_attribute! - instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!") - end - - def reset_scope_attribute - instance.public_send(:"reset_#{scope}_#{internal_id_attribute}") - end - - def read_internal_id - instance.public_send(internal_id_attribute) - end - - def write_internal_id(value) - instance.public_send(:"#{internal_id_attribute}=", value) - end - end -end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb new file mode 100644 index 00000000000..a248f60d23e --- /dev/null +++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb @@ -0,0 +1,113 @@ +require 'spec_helper' + +shared_examples_for 'AtomicInternalId' do |validate_presence: true| + describe '.has_internal_id' do + describe 'Module inclusion' do + subject { described_class } + + it { is_expected.to include_module(AtomicInternalId) } + end + + describe 'Validation' do + before do + allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") + + instance.valid? + end + + context 'when presence validation is required' do + before do + skip unless validate_presence + end + + it 'validates presence' do + expect(instance.errors[internal_id_attribute]).to include("can't be blank") + end + end + + context 'when presence validation is not required' do + before do + skip if validate_presence + end + + it 'does not validate presence' do + expect(instance.errors[internal_id_attribute]).to be_empty + end + end + end + + describe 'Creating an instance' do + subject { instance.save! } + + it 'saves a new instance properly' do + expect { subject }.not_to raise_error + end + end + + describe 'internal id generation' do + subject { instance.save! } + + it 'calls InternalId.generate_next and sets internal id attribute' do + iid = rand(1..1000) + + expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid) + subject + expect(read_internal_id).to eq(iid) + end + + it 'does not overwrite an existing internal id' do + write_internal_id(4711) + + expect { subject }.not_to change { read_internal_id } + end + + context 'when the instance has an internal ID set' do + let(:internal_id) { 9001 } + + it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do + write_internal_id(internal_id) + + expect(InternalId) + .to receive(:track_greatest) + .with(instance, scope_attrs, usage, internal_id, any_args) + .and_return(internal_id) + subject + end + end + end + + describe "#reset_scope_internal_id_attribute" do + it 'rewinds the allocated IID' do + expect { ensure_scope_attribute! }.not_to raise_error + expect(read_internal_id).not_to be_nil + + expect(reset_scope_attribute).to be_nil + expect(read_internal_id).to be_nil + end + + it 'allocates the same IID' do + internal_id = ensure_scope_attribute! + reset_scope_attribute + expect(read_internal_id).to be_nil + + expect(ensure_scope_attribute!).to eq(internal_id) + end + end + + def ensure_scope_attribute! + instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!") + end + + def reset_scope_attribute + instance.public_send(:"reset_#{scope}_#{internal_id_attribute}") + end + + def read_internal_id + instance.public_send(internal_id_attribute) + end + + def write_internal_id(value) + instance.public_send(:"#{internal_id_attribute}=", value) + end + end +end diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb deleted file mode 100644 index 0a302e7d030..00000000000 --- a/spec/support/shared_examples/models/chat_service_shared_examples.rb +++ /dev/null @@ -1,248 +0,0 @@ -require "spec_helper" - -shared_examples_for "chat service" do |service_name| - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - - describe "Validations" do - context "when service is active" do - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:webhook) } - it_behaves_like "issue tracker service URL attribute", :webhook - end - - context "when service is inactive" do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:webhook) } - end - end - - describe '.supported_events' do - it 'does not support deployment_events' do - expect(described_class.supported_events).not_to include('deployment') - end - end - - describe "#execute" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:webhook_url) { "https://example.gitlab.com/" } - - before do - allow(subject).to receive_messages( - project: project, - project_id: project.id, - service_hook: true, - webhook: webhook_url - ) - - WebMock.stub_request(:post, webhook_url) - end - - shared_examples "#{service_name} service" do - it "calls #{service_name} API" do - subject.execute(sample_data) - - expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once - end - end - - context "with push events" do - let(:sample_data) do - Gitlab::DataBuilder::Push.build_sample(project, user) - end - - it_behaves_like "#{service_name} service" - - it "specifies the webhook when it is configured" do - expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) - - subject.execute(sample_data) - end - - context "with not default branch" do - let(:sample_data) do - Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch") - end - - context "when notify_only_default_branch enabled" do - before do - subject.notify_only_default_branch = true - end - - it "does not call the Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_default_branch disabled" do - before do - subject.notify_only_default_branch = false - end - - it_behaves_like "#{service_name} service" - end - end - end - - context "with issue events" do - let(:opts) { { title: "Awesome issue", description: "please fix" } } - let(:sample_data) do - service = Issues::CreateService.new(project, user, opts) - issue = service.execute - service.hook_data(issue, "open") - end - - it_behaves_like "#{service_name} service" - end - - context "with merge events" do - let(:opts) do - { - title: "Awesome merge_request", - description: "please fix", - source_branch: "feature", - target_branch: "master" - } - end - - let(:sample_data) do - service = MergeRequests::CreateService.new(project, user, opts) - merge_request = service.execute - service.hook_data(merge_request, "open") - end - - before do - project.add_developer(user) - end - - it_behaves_like "#{service_name} service" - end - - context "with wiki page events" do - let(:opts) do - { - title: "Awesome wiki_page", - content: "Some text describing some thing or another", - format: "md", - message: "user created page: Awesome wiki_page" - } - end - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) } - let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } - - it_behaves_like "#{service_name} service" - end - - context "with note events" do - let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) } - - context "with commit comment" do - let(:note) do - create(:note_on_commit, - author: user, - project: project, - commit_id: project.repository.commit.id, - note: "a comment on a commit") - end - - it_behaves_like "#{service_name} service" - end - - context "with merge request comment" do - let(:note) do - create(:note_on_merge_request, project: project, note: "merge request note") - end - - it_behaves_like "#{service_name} service" - end - - context "with issue comment" do - let(:note) do - create(:note_on_issue, project: project, note: "issue note") - end - - it_behaves_like "#{service_name} service" - end - - context "with snippet comment" do - let(:note) do - create(:note_on_project_snippet, project: project, note: "snippet note") - end - - it_behaves_like "#{service_name} service" - end - end - - context "with pipeline events" do - let(:pipeline) do - create(:ci_pipeline, - project: project, status: status, - sha: project.commit.sha, ref: project.default_branch) - end - let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } - - context "with failed pipeline" do - let(:status) { "failed" } - - it_behaves_like "#{service_name} service" - end - - context "with succeeded pipeline" do - let(:status) { "success" } - - context "with default notify_only_broken_pipelines" do - it "does not call Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_broken_pipelines is false" do - before do - subject.notify_only_broken_pipelines = false - end - - it_behaves_like "#{service_name} service" - end - end - - context "with not default branch" do - let(:pipeline) do - create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") - end - - context "when notify_only_default_branch enabled" do - before do - subject.notify_only_default_branch = true - end - - it "does not call the Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_default_branch disabled" do - before do - subject.notify_only_default_branch = false - end - - it_behaves_like "#{service_name} service" - end - end - end - end -end diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_spec.rb new file mode 100644 index 00000000000..0a302e7d030 --- /dev/null +++ b/spec/support/shared_examples/models/chat_service_spec.rb @@ -0,0 +1,248 @@ +require "spec_helper" + +shared_examples_for "chat service" do |service_name| + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Validations" do + context "when service is active" do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:webhook) } + it_behaves_like "issue tracker service URL attribute", :webhook + end + + context "when service is inactive" do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:webhook) } + end + end + + describe '.supported_events' do + it 'does not support deployment_events' do + expect(described_class.supported_events).not_to include('deployment') + end + end + + describe "#execute" do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:webhook_url) { "https://example.gitlab.com/" } + + before do + allow(subject).to receive_messages( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + end + + shared_examples "#{service_name} service" do + it "calls #{service_name} API" do + subject.execute(sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once + end + end + + context "with push events" do + let(:sample_data) do + Gitlab::DataBuilder::Push.build_sample(project, user) + end + + it_behaves_like "#{service_name} service" + + it "specifies the webhook when it is configured" do + expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) + + subject.execute(sample_data) + end + + context "with not default branch" do + let(:sample_data) do + Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch") + end + + context "when notify_only_default_branch enabled" do + before do + subject.notify_only_default_branch = true + end + + it "does not call the Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_default_branch disabled" do + before do + subject.notify_only_default_branch = false + end + + it_behaves_like "#{service_name} service" + end + end + end + + context "with issue events" do + let(:opts) { { title: "Awesome issue", description: "please fix" } } + let(:sample_data) do + service = Issues::CreateService.new(project, user, opts) + issue = service.execute + service.hook_data(issue, "open") + end + + it_behaves_like "#{service_name} service" + end + + context "with merge events" do + let(:opts) do + { + title: "Awesome merge_request", + description: "please fix", + source_branch: "feature", + target_branch: "master" + } + end + + let(:sample_data) do + service = MergeRequests::CreateService.new(project, user, opts) + merge_request = service.execute + service.hook_data(merge_request, "open") + end + + before do + project.add_developer(user) + end + + it_behaves_like "#{service_name} service" + end + + context "with wiki page events" do + let(:opts) do + { + title: "Awesome wiki_page", + content: "Some text describing some thing or another", + format: "md", + message: "user created page: Awesome wiki_page" + } + end + let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) } + let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } + + it_behaves_like "#{service_name} service" + end + + context "with note events" do + let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) } + + context "with commit comment" do + let(:note) do + create(:note_on_commit, + author: user, + project: project, + commit_id: project.repository.commit.id, + note: "a comment on a commit") + end + + it_behaves_like "#{service_name} service" + end + + context "with merge request comment" do + let(:note) do + create(:note_on_merge_request, project: project, note: "merge request note") + end + + it_behaves_like "#{service_name} service" + end + + context "with issue comment" do + let(:note) do + create(:note_on_issue, project: project, note: "issue note") + end + + it_behaves_like "#{service_name} service" + end + + context "with snippet comment" do + let(:note) do + create(:note_on_project_snippet, project: project, note: "snippet note") + end + + it_behaves_like "#{service_name} service" + end + end + + context "with pipeline events" do + let(:pipeline) do + create(:ci_pipeline, + project: project, status: status, + sha: project.commit.sha, ref: project.default_branch) + end + let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } + + context "with failed pipeline" do + let(:status) { "failed" } + + it_behaves_like "#{service_name} service" + end + + context "with succeeded pipeline" do + let(:status) { "success" } + + context "with default notify_only_broken_pipelines" do + it "does not call Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_broken_pipelines is false" do + before do + subject.notify_only_broken_pipelines = false + end + + it_behaves_like "#{service_name} service" + end + end + + context "with not default branch" do + let(:pipeline) do + create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") + end + + context "when notify_only_default_branch enabled" do + before do + subject.notify_only_default_branch = true + end + + it "does not call the Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_default_branch disabled" do + before do + subject.notify_only_default_branch = false + end + + it_behaves_like "#{service_name} service" + end + end + end + end +end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb deleted file mode 100644 index 7a04e940ee5..00000000000 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -shared_examples_for 'UpdateProjectStatistics' do - let(:project) { subject.project } - let(:stat) { described_class.statistic_name } - let(:attribute) { described_class.statistic_attribute } - - def reload_stat - project.statistics.reload.send(stat).to_i - end - - def read_attribute - subject.read_attribute(attribute).to_i - end - - it { is_expected.to be_new_record } - - context 'when creating' do - it 'updates the project statistics' do - delta = read_attribute - - expect { subject.save! } - .to change { reload_stat } - .by(delta) - end - end - - context 'when updating' do - before do - subject.save! - end - - it 'updates project statistics' do - delta = 42 - - expect(ProjectStatistics) - .to receive(:increment_statistic) - .and_call_original - - subject.write_attribute(attribute, read_attribute + delta) - expect { subject.save! } - .to change { reload_stat } - .by(delta) - end - end - - context 'when destroying' do - before do - subject.save! - end - - it 'updates the project statistics' do - delta = -read_attribute - - expect(ProjectStatistics) - .to receive(:increment_statistic) - .and_call_original - - expect { subject.destroy } - .to change { reload_stat } - .by(delta) - end - - context 'when it is destroyed from the project level' do - it 'does not update the project statistics' do - expect(ProjectStatistics) - .not_to receive(:increment_statistic) - - project.update(pending_delete: true) - project.destroy! - end - end - end -end diff --git a/spec/support/shared_examples/models/update_project_statistics_spec.rb b/spec/support/shared_examples/models/update_project_statistics_spec.rb new file mode 100644 index 00000000000..7a04e940ee5 --- /dev/null +++ b/spec/support/shared_examples/models/update_project_statistics_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +shared_examples_for 'UpdateProjectStatistics' do + let(:project) { subject.project } + let(:stat) { described_class.statistic_name } + let(:attribute) { described_class.statistic_attribute } + + def reload_stat + project.statistics.reload.send(stat).to_i + end + + def read_attribute + subject.read_attribute(attribute).to_i + end + + it { is_expected.to be_new_record } + + context 'when creating' do + it 'updates the project statistics' do + delta = read_attribute + + expect { subject.save! } + .to change { reload_stat } + .by(delta) + end + end + + context 'when updating' do + before do + subject.save! + end + + it 'updates project statistics' do + delta = 42 + + expect(ProjectStatistics) + .to receive(:increment_statistic) + .and_call_original + + subject.write_attribute(attribute, read_attribute + delta) + expect { subject.save! } + .to change { reload_stat } + .by(delta) + end + end + + context 'when destroying' do + before do + subject.save! + end + + it 'updates the project statistics' do + delta = -read_attribute + + expect(ProjectStatistics) + .to receive(:increment_statistic) + .and_call_original + + expect { subject.destroy } + .to change { reload_stat } + .by(delta) + end + + context 'when it is destroyed from the project level' do + it 'does not update the project statistics' do + expect(ProjectStatistics) + .not_to receive(:increment_statistic) + + project.update(pending_delete: true) + project.destroy! + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb new file mode 100644 index 00000000000..1133e95e44e --- /dev/null +++ b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +shared_examples 'labeled issues with labels and label_name params' do + shared_examples 'returns label names' do + it 'returns label names' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) + end + end + + shared_examples 'returns basic label entity' do + it 'returns basic label entity' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title]) + expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic') + end + end + + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } } + + it_behaves_like 'returns label names' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title] } } + + it_behaves_like 'returns label names' + end + + context 'when with_labels_details provided' do + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } } + + it_behaves_like 'returns basic label entity' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } } + + it_behaves_like 'returns basic label entity' + end + end +end diff --git a/spec/support/shared_examples/requests/api/issues_shared_examples.rb b/spec/support/shared_examples/requests/api/issues_shared_examples.rb deleted file mode 100644 index 1133e95e44e..00000000000 --- a/spec/support/shared_examples/requests/api/issues_shared_examples.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'labeled issues with labels and label_name params' do - shared_examples 'returns label names' do - it 'returns label names' do - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) - end - end - - shared_examples 'returns basic label entity' do - it 'returns basic label entity' do - expect_paginated_array_response(issue.id) - expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title]) - expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic') - end - end - - context 'array of labeled issues when all labels match' do - let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } } - - it_behaves_like 'returns label names' - end - - context 'array of labeled issues when all labels match with labels param as array' do - let(:params) { { labels: [label.title, label_b.title, label_c.title] } } - - it_behaves_like 'returns label names' - end - - context 'when with_labels_details provided' do - context 'array of labeled issues when all labels match' do - let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } } - - it_behaves_like 'returns basic label entity' - end - - context 'array of labeled issues when all labels match with labels param as array' do - let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } } - - it_behaves_like 'returns basic label entity' - end - end -end -- cgit v1.2.1 From f9f9147290b5bc315e595e059c851593c1fc466f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 22 May 2019 15:30:10 +0100 Subject: Fix page_description helper performance This helper is used for extracting part of the issue / MR / whatever description for use in the description meta tag: 1. To do that, we look at the source of the Markdown description. 2. We then strip out all HTML tags. 3. And then take the first 30 words. Doing that can be really slow - especially as Markdown is supposed to be treated as plain text. There are many better ways to do this, but the immediate performance fix is to swap steps 2 and 3. This does mean that the description may be less than 30 words (or even empty), but it is much faster when the description is very long. --- spec/helpers/page_layout_helper_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index cf98eed27f1..bf50763d06f 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -38,6 +38,14 @@ describe PageLayoutHelper do expect(helper.page_description).to eq 'Bold Header' end + + it 'truncates before sanitizing' do + helper.page_description('Bold

      Header

      ' * 10) + + # 12 words because was counted as a word + expect(helper.page_description) + .to eq('Bold Header Bold Header Bold Header Bold Header Bold Header Bold Header...') + end end describe 'page_image' do -- cgit v1.2.1 From 5c8cd42bbd6a387f5e73c688615c54606810983e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 22 May 2019 11:29:10 -0700 Subject: Fix invalid visibility string comparison in project import This resolves an "ArgumentError: comparison of String with 0 failed" issue where the visibility_level is stored as a string in the project import data because the value comes directly from the Web form. This problem happened upon creating a project from a template or restoring a project. We now cast the value to an integer to guard against these kinds of failures. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/61692 --- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 6084dc96410..651aa600fb2 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -328,6 +328,19 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'when the project has overridden params in import data' do + it 'handles string versions of visibility_level' do + # Project needs to be in a group for visibility level comparison + # to happen + group = create(:group) + project.group = group + + project.create_import_data(data: { override_params: { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } }) + + restored_project_json + + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + it 'overwrites the params stored in the JSON' do project.create_import_data(data: { override_params: { description: "Overridden" } }) -- cgit v1.2.1 From 6145ddf515f93fcc7ed873b911b6369cc2bc0462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 22 May 2019 14:16:49 +0200 Subject: Revert "Merge branch 'revert-04c3c6dd' into 'master'" This reverts commit 744f1f2e7037f5c70c3168d9e2e89b1c327465d2, reversing changes made to c4d930e5f54e7da07c80cc028dfc0f5c08719146. --- spec/lib/quality/test_level_spec.rb | 105 +++++++++ spec/spec_helper.rb | 5 +- .../finders/assignees_filter_shared_examples.rb | 49 ++++ .../finders/assignees_filter_spec.rb | 49 ---- .../models/atomic_internal_id_shared_examples.rb | 113 ++++++++++ .../models/atomic_internal_id_spec.rb | 113 ---------- .../models/chat_service_shared_examples.rb | 248 +++++++++++++++++++++ .../shared_examples/models/chat_service_spec.rb | 248 --------------------- .../update_project_statistics_shared_examples.rb | 76 +++++++ .../models/update_project_statistics_spec.rb | 76 ------- .../requests/api/issues_shared_example_spec.rb | 44 ---- .../requests/api/issues_shared_examples.rb | 44 ++++ 12 files changed, 639 insertions(+), 531 deletions(-) create mode 100644 spec/lib/quality/test_level_spec.rb create mode 100644 spec/support/shared_examples/finders/assignees_filter_shared_examples.rb delete mode 100644 spec/support/shared_examples/finders/assignees_filter_spec.rb create mode 100644 spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb delete mode 100644 spec/support/shared_examples/models/atomic_internal_id_spec.rb create mode 100644 spec/support/shared_examples/models/chat_service_shared_examples.rb delete mode 100644 spec/support/shared_examples/models/chat_service_spec.rb create mode 100644 spec/support/shared_examples/models/update_project_statistics_shared_examples.rb delete mode 100644 spec/support/shared_examples/models/update_project_statistics_spec.rb delete mode 100644 spec/support/shared_examples/requests/api/issues_shared_example_spec.rb create mode 100644 spec/support/shared_examples/requests/api/issues_shared_examples.rb (limited to 'spec') diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb new file mode 100644 index 00000000000..3465c3a050b --- /dev/null +++ b/spec/lib/quality/test_level_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Quality::TestLevel do + describe '#pattern' do + context 'when level is unit' do + it 'returns a pattern' do + expect(subject.pattern(:unit)) + .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") + end + end + + context 'when level is integration' do + it 'returns a pattern' do + expect(subject.pattern(:integration)) + .to eq("spec/{controllers,mailers,requests}{,/**/}*_spec.rb") + end + end + + context 'when level is system' do + it 'returns a pattern' do + expect(subject.pattern(:system)) + .to eq("spec/{features}{,/**/}*_spec.rb") + end + end + + context 'with a prefix' do + it 'returns a pattern' do + expect(described_class.new('ee/').pattern(:system)) + .to eq("ee/spec/{features}{,/**/}*_spec.rb") + end + end + + describe 'performance' do + it 'memoizes the pattern for a given level' do + expect(subject.pattern(:system).object_id).to eq(subject.pattern(:system).object_id) + end + + it 'freezes the pattern for a given level' do + expect(subject.pattern(:system)).to be_frozen + end + end + end + + describe '#regexp' do + context 'when level is unit' do + it 'returns a regexp' do + expect(subject.regexp(:unit)) + .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) + end + end + + context 'when level is integration' do + it 'returns a regexp' do + expect(subject.regexp(:integration)) + .to eq(%r{spec/(controllers|mailers|requests)}) + end + end + + context 'when level is system' do + it 'returns a regexp' do + expect(subject.regexp(:system)) + .to eq(%r{spec/(features)}) + end + end + + context 'with a prefix' do + it 'returns a regexp' do + expect(described_class.new('ee/').regexp(:system)) + .to eq(%r{ee/spec/(features)}) + end + end + + describe 'performance' do + it 'memoizes the regexp for a given level' do + expect(subject.regexp(:system).object_id).to eq(subject.regexp(:system).object_id) + end + + it 'freezes the regexp for a given level' do + expect(subject.regexp(:system)).to be_frozen + end + end + end + + describe '#level_for' do + it 'returns the correct level for a unit test' do + expect(subject.level_for('spec/models/abuse_report_spec.rb')).to eq(:unit) + end + + it 'returns the correct level for an integration test' do + expect(subject.level_for('spec/mailers/abuse_report_mailer_spec.rb')).to eq(:integration) + end + + it 'returns the correct level for a system test' do + expect(subject.level_for('spec/features/abuse_report_spec.rb')).to eq(:system) + end + + it 'raises an error for an unknown level' do + expect { subject.level_for('spec/unknown/foo_spec.rb') } + .to raise_error(described_class::UnknownTestLevelError, + %r{Test level for spec/unknown/foo_spec.rb couldn't be set. Please rename the file properly or change the test level detection regexes in .+/lib/quality/test_level.rb.}) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 69589c9aa33..390a869d93f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,6 +44,8 @@ Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } +quality_level = Quality::TestLevel.new + RSpec.configure do |config| config.use_transactional_fixtures = false config.use_instantiated_fixtures = false @@ -55,9 +57,10 @@ RSpec.configure do |config| config.infer_spec_type_from_file_location! config.full_backtrace = !!ENV['CI'] - config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| + config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata| location = metadata[:location] + metadata[:level] = quality_level.level_for(location) metadata[:api] = true if location =~ %r{/spec/requests/api/} # do not overwrite type if it's already set diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb new file mode 100644 index 00000000000..782a2d97746 --- /dev/null +++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +shared_examples 'assignee ID filter' do + it 'returns issuables assigned to that user' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'assignee username filter' do + it 'returns issuables assigned to those users' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'no assignee filter' do + let(:params) { { assignee_id: 'None' } } + + it 'returns issuables not assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 0 + + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables not assigned to any assignee' do + params[:assignee_id] = 'none' + + expect(issuables).to contain_exactly(*expected_issuables) + end +end + +shared_examples 'any assignee filter' do + context '' do + let(:params) { { assignee_id: 'Any' } } + + it 'returns issuables assigned to any assignee' do + expect(issuables).to contain_exactly(*expected_issuables) + end + + it 'returns issuables assigned to any assignee' do + params[:assignee_id] = 'any' + + expect(issuables).to contain_exactly(*expected_issuables) + end + end +end diff --git a/spec/support/shared_examples/finders/assignees_filter_spec.rb b/spec/support/shared_examples/finders/assignees_filter_spec.rb deleted file mode 100644 index 782a2d97746..00000000000 --- a/spec/support/shared_examples/finders/assignees_filter_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'assignee ID filter' do - it 'returns issuables assigned to that user' do - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'assignee username filter' do - it 'returns issuables assigned to those users' do - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'no assignee filter' do - let(:params) { { assignee_id: 'None' } } - - it 'returns issuables not assigned to any assignee' do - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables not assigned to any assignee' do - params[:assignee_id] = 0 - - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables not assigned to any assignee' do - params[:assignee_id] = 'none' - - expect(issuables).to contain_exactly(*expected_issuables) - end -end - -shared_examples 'any assignee filter' do - context '' do - let(:params) { { assignee_id: 'Any' } } - - it 'returns issuables assigned to any assignee' do - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables assigned to any assignee' do - params[:assignee_id] = 'any' - - expect(issuables).to contain_exactly(*expected_issuables) - end - end -end diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb new file mode 100644 index 00000000000..a248f60d23e --- /dev/null +++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb @@ -0,0 +1,113 @@ +require 'spec_helper' + +shared_examples_for 'AtomicInternalId' do |validate_presence: true| + describe '.has_internal_id' do + describe 'Module inclusion' do + subject { described_class } + + it { is_expected.to include_module(AtomicInternalId) } + end + + describe 'Validation' do + before do + allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") + + instance.valid? + end + + context 'when presence validation is required' do + before do + skip unless validate_presence + end + + it 'validates presence' do + expect(instance.errors[internal_id_attribute]).to include("can't be blank") + end + end + + context 'when presence validation is not required' do + before do + skip if validate_presence + end + + it 'does not validate presence' do + expect(instance.errors[internal_id_attribute]).to be_empty + end + end + end + + describe 'Creating an instance' do + subject { instance.save! } + + it 'saves a new instance properly' do + expect { subject }.not_to raise_error + end + end + + describe 'internal id generation' do + subject { instance.save! } + + it 'calls InternalId.generate_next and sets internal id attribute' do + iid = rand(1..1000) + + expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid) + subject + expect(read_internal_id).to eq(iid) + end + + it 'does not overwrite an existing internal id' do + write_internal_id(4711) + + expect { subject }.not_to change { read_internal_id } + end + + context 'when the instance has an internal ID set' do + let(:internal_id) { 9001 } + + it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do + write_internal_id(internal_id) + + expect(InternalId) + .to receive(:track_greatest) + .with(instance, scope_attrs, usage, internal_id, any_args) + .and_return(internal_id) + subject + end + end + end + + describe "#reset_scope_internal_id_attribute" do + it 'rewinds the allocated IID' do + expect { ensure_scope_attribute! }.not_to raise_error + expect(read_internal_id).not_to be_nil + + expect(reset_scope_attribute).to be_nil + expect(read_internal_id).to be_nil + end + + it 'allocates the same IID' do + internal_id = ensure_scope_attribute! + reset_scope_attribute + expect(read_internal_id).to be_nil + + expect(ensure_scope_attribute!).to eq(internal_id) + end + end + + def ensure_scope_attribute! + instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!") + end + + def reset_scope_attribute + instance.public_send(:"reset_#{scope}_#{internal_id_attribute}") + end + + def read_internal_id + instance.public_send(internal_id_attribute) + end + + def write_internal_id(value) + instance.public_send(:"#{internal_id_attribute}=", value) + end + end +end diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb deleted file mode 100644 index a248f60d23e..00000000000 --- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'spec_helper' - -shared_examples_for 'AtomicInternalId' do |validate_presence: true| - describe '.has_internal_id' do - describe 'Module inclusion' do - subject { described_class } - - it { is_expected.to include_module(AtomicInternalId) } - end - - describe 'Validation' do - before do - allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!") - - instance.valid? - end - - context 'when presence validation is required' do - before do - skip unless validate_presence - end - - it 'validates presence' do - expect(instance.errors[internal_id_attribute]).to include("can't be blank") - end - end - - context 'when presence validation is not required' do - before do - skip if validate_presence - end - - it 'does not validate presence' do - expect(instance.errors[internal_id_attribute]).to be_empty - end - end - end - - describe 'Creating an instance' do - subject { instance.save! } - - it 'saves a new instance properly' do - expect { subject }.not_to raise_error - end - end - - describe 'internal id generation' do - subject { instance.save! } - - it 'calls InternalId.generate_next and sets internal id attribute' do - iid = rand(1..1000) - - expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid) - subject - expect(read_internal_id).to eq(iid) - end - - it 'does not overwrite an existing internal id' do - write_internal_id(4711) - - expect { subject }.not_to change { read_internal_id } - end - - context 'when the instance has an internal ID set' do - let(:internal_id) { 9001 } - - it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do - write_internal_id(internal_id) - - expect(InternalId) - .to receive(:track_greatest) - .with(instance, scope_attrs, usage, internal_id, any_args) - .and_return(internal_id) - subject - end - end - end - - describe "#reset_scope_internal_id_attribute" do - it 'rewinds the allocated IID' do - expect { ensure_scope_attribute! }.not_to raise_error - expect(read_internal_id).not_to be_nil - - expect(reset_scope_attribute).to be_nil - expect(read_internal_id).to be_nil - end - - it 'allocates the same IID' do - internal_id = ensure_scope_attribute! - reset_scope_attribute - expect(read_internal_id).to be_nil - - expect(ensure_scope_attribute!).to eq(internal_id) - end - end - - def ensure_scope_attribute! - instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!") - end - - def reset_scope_attribute - instance.public_send(:"reset_#{scope}_#{internal_id_attribute}") - end - - def read_internal_id - instance.public_send(internal_id_attribute) - end - - def write_internal_id(value) - instance.public_send(:"#{internal_id_attribute}=", value) - end - end -end diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb new file mode 100644 index 00000000000..0a302e7d030 --- /dev/null +++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb @@ -0,0 +1,248 @@ +require "spec_helper" + +shared_examples_for "chat service" do |service_name| + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Validations" do + context "when service is active" do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:webhook) } + it_behaves_like "issue tracker service URL attribute", :webhook + end + + context "when service is inactive" do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:webhook) } + end + end + + describe '.supported_events' do + it 'does not support deployment_events' do + expect(described_class.supported_events).not_to include('deployment') + end + end + + describe "#execute" do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:webhook_url) { "https://example.gitlab.com/" } + + before do + allow(subject).to receive_messages( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + end + + shared_examples "#{service_name} service" do + it "calls #{service_name} API" do + subject.execute(sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once + end + end + + context "with push events" do + let(:sample_data) do + Gitlab::DataBuilder::Push.build_sample(project, user) + end + + it_behaves_like "#{service_name} service" + + it "specifies the webhook when it is configured" do + expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) + + subject.execute(sample_data) + end + + context "with not default branch" do + let(:sample_data) do + Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch") + end + + context "when notify_only_default_branch enabled" do + before do + subject.notify_only_default_branch = true + end + + it "does not call the Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_default_branch disabled" do + before do + subject.notify_only_default_branch = false + end + + it_behaves_like "#{service_name} service" + end + end + end + + context "with issue events" do + let(:opts) { { title: "Awesome issue", description: "please fix" } } + let(:sample_data) do + service = Issues::CreateService.new(project, user, opts) + issue = service.execute + service.hook_data(issue, "open") + end + + it_behaves_like "#{service_name} service" + end + + context "with merge events" do + let(:opts) do + { + title: "Awesome merge_request", + description: "please fix", + source_branch: "feature", + target_branch: "master" + } + end + + let(:sample_data) do + service = MergeRequests::CreateService.new(project, user, opts) + merge_request = service.execute + service.hook_data(merge_request, "open") + end + + before do + project.add_developer(user) + end + + it_behaves_like "#{service_name} service" + end + + context "with wiki page events" do + let(:opts) do + { + title: "Awesome wiki_page", + content: "Some text describing some thing or another", + format: "md", + message: "user created page: Awesome wiki_page" + } + end + let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) } + let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } + + it_behaves_like "#{service_name} service" + end + + context "with note events" do + let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) } + + context "with commit comment" do + let(:note) do + create(:note_on_commit, + author: user, + project: project, + commit_id: project.repository.commit.id, + note: "a comment on a commit") + end + + it_behaves_like "#{service_name} service" + end + + context "with merge request comment" do + let(:note) do + create(:note_on_merge_request, project: project, note: "merge request note") + end + + it_behaves_like "#{service_name} service" + end + + context "with issue comment" do + let(:note) do + create(:note_on_issue, project: project, note: "issue note") + end + + it_behaves_like "#{service_name} service" + end + + context "with snippet comment" do + let(:note) do + create(:note_on_project_snippet, project: project, note: "snippet note") + end + + it_behaves_like "#{service_name} service" + end + end + + context "with pipeline events" do + let(:pipeline) do + create(:ci_pipeline, + project: project, status: status, + sha: project.commit.sha, ref: project.default_branch) + end + let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } + + context "with failed pipeline" do + let(:status) { "failed" } + + it_behaves_like "#{service_name} service" + end + + context "with succeeded pipeline" do + let(:status) { "success" } + + context "with default notify_only_broken_pipelines" do + it "does not call Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_broken_pipelines is false" do + before do + subject.notify_only_broken_pipelines = false + end + + it_behaves_like "#{service_name} service" + end + end + + context "with not default branch" do + let(:pipeline) do + create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") + end + + context "when notify_only_default_branch enabled" do + before do + subject.notify_only_default_branch = true + end + + it "does not call the Discord Webhooks API" do + result = subject.execute(sample_data) + + expect(result).to be_falsy + end + end + + context "when notify_only_default_branch disabled" do + before do + subject.notify_only_default_branch = false + end + + it_behaves_like "#{service_name} service" + end + end + end + end +end diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_spec.rb deleted file mode 100644 index 0a302e7d030..00000000000 --- a/spec/support/shared_examples/models/chat_service_spec.rb +++ /dev/null @@ -1,248 +0,0 @@ -require "spec_helper" - -shared_examples_for "chat service" do |service_name| - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - - describe "Validations" do - context "when service is active" do - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:webhook) } - it_behaves_like "issue tracker service URL attribute", :webhook - end - - context "when service is inactive" do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:webhook) } - end - end - - describe '.supported_events' do - it 'does not support deployment_events' do - expect(described_class.supported_events).not_to include('deployment') - end - end - - describe "#execute" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:webhook_url) { "https://example.gitlab.com/" } - - before do - allow(subject).to receive_messages( - project: project, - project_id: project.id, - service_hook: true, - webhook: webhook_url - ) - - WebMock.stub_request(:post, webhook_url) - end - - shared_examples "#{service_name} service" do - it "calls #{service_name} API" do - subject.execute(sample_data) - - expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once - end - end - - context "with push events" do - let(:sample_data) do - Gitlab::DataBuilder::Push.build_sample(project, user) - end - - it_behaves_like "#{service_name} service" - - it "specifies the webhook when it is configured" do - expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) - - subject.execute(sample_data) - end - - context "with not default branch" do - let(:sample_data) do - Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch") - end - - context "when notify_only_default_branch enabled" do - before do - subject.notify_only_default_branch = true - end - - it "does not call the Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_default_branch disabled" do - before do - subject.notify_only_default_branch = false - end - - it_behaves_like "#{service_name} service" - end - end - end - - context "with issue events" do - let(:opts) { { title: "Awesome issue", description: "please fix" } } - let(:sample_data) do - service = Issues::CreateService.new(project, user, opts) - issue = service.execute - service.hook_data(issue, "open") - end - - it_behaves_like "#{service_name} service" - end - - context "with merge events" do - let(:opts) do - { - title: "Awesome merge_request", - description: "please fix", - source_branch: "feature", - target_branch: "master" - } - end - - let(:sample_data) do - service = MergeRequests::CreateService.new(project, user, opts) - merge_request = service.execute - service.hook_data(merge_request, "open") - end - - before do - project.add_developer(user) - end - - it_behaves_like "#{service_name} service" - end - - context "with wiki page events" do - let(:opts) do - { - title: "Awesome wiki_page", - content: "Some text describing some thing or another", - format: "md", - message: "user created page: Awesome wiki_page" - } - end - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) } - let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } - - it_behaves_like "#{service_name} service" - end - - context "with note events" do - let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) } - - context "with commit comment" do - let(:note) do - create(:note_on_commit, - author: user, - project: project, - commit_id: project.repository.commit.id, - note: "a comment on a commit") - end - - it_behaves_like "#{service_name} service" - end - - context "with merge request comment" do - let(:note) do - create(:note_on_merge_request, project: project, note: "merge request note") - end - - it_behaves_like "#{service_name} service" - end - - context "with issue comment" do - let(:note) do - create(:note_on_issue, project: project, note: "issue note") - end - - it_behaves_like "#{service_name} service" - end - - context "with snippet comment" do - let(:note) do - create(:note_on_project_snippet, project: project, note: "snippet note") - end - - it_behaves_like "#{service_name} service" - end - end - - context "with pipeline events" do - let(:pipeline) do - create(:ci_pipeline, - project: project, status: status, - sha: project.commit.sha, ref: project.default_branch) - end - let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } - - context "with failed pipeline" do - let(:status) { "failed" } - - it_behaves_like "#{service_name} service" - end - - context "with succeeded pipeline" do - let(:status) { "success" } - - context "with default notify_only_broken_pipelines" do - it "does not call Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_broken_pipelines is false" do - before do - subject.notify_only_broken_pipelines = false - end - - it_behaves_like "#{service_name} service" - end - end - - context "with not default branch" do - let(:pipeline) do - create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") - end - - context "when notify_only_default_branch enabled" do - before do - subject.notify_only_default_branch = true - end - - it "does not call the Discord Webhooks API" do - result = subject.execute(sample_data) - - expect(result).to be_falsy - end - end - - context "when notify_only_default_branch disabled" do - before do - subject.notify_only_default_branch = false - end - - it_behaves_like "#{service_name} service" - end - end - end - end -end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb new file mode 100644 index 00000000000..7a04e940ee5 --- /dev/null +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +shared_examples_for 'UpdateProjectStatistics' do + let(:project) { subject.project } + let(:stat) { described_class.statistic_name } + let(:attribute) { described_class.statistic_attribute } + + def reload_stat + project.statistics.reload.send(stat).to_i + end + + def read_attribute + subject.read_attribute(attribute).to_i + end + + it { is_expected.to be_new_record } + + context 'when creating' do + it 'updates the project statistics' do + delta = read_attribute + + expect { subject.save! } + .to change { reload_stat } + .by(delta) + end + end + + context 'when updating' do + before do + subject.save! + end + + it 'updates project statistics' do + delta = 42 + + expect(ProjectStatistics) + .to receive(:increment_statistic) + .and_call_original + + subject.write_attribute(attribute, read_attribute + delta) + expect { subject.save! } + .to change { reload_stat } + .by(delta) + end + end + + context 'when destroying' do + before do + subject.save! + end + + it 'updates the project statistics' do + delta = -read_attribute + + expect(ProjectStatistics) + .to receive(:increment_statistic) + .and_call_original + + expect { subject.destroy } + .to change { reload_stat } + .by(delta) + end + + context 'when it is destroyed from the project level' do + it 'does not update the project statistics' do + expect(ProjectStatistics) + .not_to receive(:increment_statistic) + + project.update(pending_delete: true) + project.destroy! + end + end + end +end diff --git a/spec/support/shared_examples/models/update_project_statistics_spec.rb b/spec/support/shared_examples/models/update_project_statistics_spec.rb deleted file mode 100644 index 7a04e940ee5..00000000000 --- a/spec/support/shared_examples/models/update_project_statistics_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -shared_examples_for 'UpdateProjectStatistics' do - let(:project) { subject.project } - let(:stat) { described_class.statistic_name } - let(:attribute) { described_class.statistic_attribute } - - def reload_stat - project.statistics.reload.send(stat).to_i - end - - def read_attribute - subject.read_attribute(attribute).to_i - end - - it { is_expected.to be_new_record } - - context 'when creating' do - it 'updates the project statistics' do - delta = read_attribute - - expect { subject.save! } - .to change { reload_stat } - .by(delta) - end - end - - context 'when updating' do - before do - subject.save! - end - - it 'updates project statistics' do - delta = 42 - - expect(ProjectStatistics) - .to receive(:increment_statistic) - .and_call_original - - subject.write_attribute(attribute, read_attribute + delta) - expect { subject.save! } - .to change { reload_stat } - .by(delta) - end - end - - context 'when destroying' do - before do - subject.save! - end - - it 'updates the project statistics' do - delta = -read_attribute - - expect(ProjectStatistics) - .to receive(:increment_statistic) - .and_call_original - - expect { subject.destroy } - .to change { reload_stat } - .by(delta) - end - - context 'when it is destroyed from the project level' do - it 'does not update the project statistics' do - expect(ProjectStatistics) - .not_to receive(:increment_statistic) - - project.update(pending_delete: true) - project.destroy! - end - end - end -end diff --git a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb b/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb deleted file mode 100644 index 1133e95e44e..00000000000 --- a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'labeled issues with labels and label_name params' do - shared_examples 'returns label names' do - it 'returns label names' do - expect_paginated_array_response(issue.id) - expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) - end - end - - shared_examples 'returns basic label entity' do - it 'returns basic label entity' do - expect_paginated_array_response(issue.id) - expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title]) - expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic') - end - end - - context 'array of labeled issues when all labels match' do - let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } } - - it_behaves_like 'returns label names' - end - - context 'array of labeled issues when all labels match with labels param as array' do - let(:params) { { labels: [label.title, label_b.title, label_c.title] } } - - it_behaves_like 'returns label names' - end - - context 'when with_labels_details provided' do - context 'array of labeled issues when all labels match' do - let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } } - - it_behaves_like 'returns basic label entity' - end - - context 'array of labeled issues when all labels match with labels param as array' do - let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } } - - it_behaves_like 'returns basic label entity' - end - end -end diff --git a/spec/support/shared_examples/requests/api/issues_shared_examples.rb b/spec/support/shared_examples/requests/api/issues_shared_examples.rb new file mode 100644 index 00000000000..1133e95e44e --- /dev/null +++ b/spec/support/shared_examples/requests/api/issues_shared_examples.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +shared_examples 'labeled issues with labels and label_name params' do + shared_examples 'returns label names' do + it 'returns label names' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) + end + end + + shared_examples 'returns basic label entity' do + it 'returns basic label entity' do + expect_paginated_array_response(issue.id) + expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title]) + expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic') + end + end + + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } } + + it_behaves_like 'returns label names' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title] } } + + it_behaves_like 'returns label names' + end + + context 'when with_labels_details provided' do + context 'array of labeled issues when all labels match' do + let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } } + + it_behaves_like 'returns basic label entity' + end + + context 'array of labeled issues when all labels match with labels param as array' do + let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } } + + it_behaves_like 'returns basic label entity' + end + end +end -- cgit v1.2.1 From ee92acccb47c073992f46f9b8f9132a8ef9a92a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Thu, 23 May 2019 13:23:38 +0200 Subject: Add specs for median serializing --- .../serializers/analytics_stage_serializer_spec.rb | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'spec') diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb index dbfb3eace83..907d1b35e74 100644 --- a/spec/serializers/analytics_stage_serializer_spec.rb +++ b/spec/serializers/analytics_stage_serializer_spec.rb @@ -21,4 +21,34 @@ describe AnalyticsStageSerializer do it 'contains important elements of AnalyticsStage' do expect(subject).to include(:title, :description, :value) end + + context 'when median is equal 0' do + before do + allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0) + end + + it 'value is nil' do + expect(subject.fetch(:value)).to be_nil + end + end + + context 'when median is below 1' do + before do + allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12) + end + + it 'value is equal to median' do + expect(subject.fetch(:value)).to eq('less than a minute') + end + end + + context 'when median is above 1' do + before do + allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12) + end + + it 'value is equal to median' do + expect(subject.fetch(:value)).to eq('less than a minute') + end + end end -- cgit v1.2.1 From c6707291e02b3b82579a036a27bf4ae5da7d799e Mon Sep 17 00:00:00 2001 From: Luke Picciau Date: Thu, 23 May 2019 12:04:09 +0000 Subject: Change system notes to use relative urls --- spec/services/system_note_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 4d33c6f4094..2420817e1f7 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -132,7 +132,7 @@ describe SystemNoteService do end it 'sets the note text' do - link = "http://localhost/#{project.full_path}/-/tags/#{tag_name}" + link = "/#{project.full_path}/-/tags/#{tag_name}" expect(subject.note).to eq "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})" end @@ -1139,7 +1139,7 @@ describe SystemNoteService do diff_id = merge_request.merge_request_diff.id line_code = change_position.line_code(project.repository) - expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code)) + expect(subject.note).to include(diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code)) end end -- cgit v1.2.1 From 30d915110f94c75b464e1cee5e51a16fbd72fabd Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 23 May 2019 13:10:38 +0000 Subject: Fix issue importing members with owner access --- spec/lib/gitlab/import_export/members_mapper_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index 67e4c289906..c663cf42a83 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -73,6 +73,13 @@ describe Gitlab::ImportExport::MembersMapper do expect(user2.authorized_project?(project)).to be true end + it 'maps an owner as a maintainer' do + exported_members.first['access_level'] = ProjectMember::OWNER + + expect(members_mapper.map[exported_user_id]).to eq(user2.id) + expect(ProjectMember.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER) + end + context 'user is not an admin' do let(:user) { create(:user) } -- cgit v1.2.1 From ec7821d8d130bbc9f06e9b3112b2dfc4fe980d37 Mon Sep 17 00:00:00 2001 From: egrieff Date: Thu, 23 May 2019 14:12:41 +0100 Subject: Test that upload links are rewritten for new notes --- spec/services/issuable/clone/content_rewriter_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'spec') diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb index 4d3cb0bd254..92292528cc3 100644 --- a/spec/services/issuable/clone/content_rewriter_spec.rb +++ b/spec/services/issuable/clone/content_rewriter_spec.rb @@ -149,5 +149,21 @@ describe Issuable::Clone::ContentRewriter do expect(new_note.author).to eq(note.author) end end + + context 'notes with uploads' do + let(:uploader) { build(:file_uploader, project: project1) } + let(:text) { "Simple text with image: #{uploader.markdown_link} "} + let!(:note) { create(:note, noteable: original_issue, note: text, project: project1) } + + it 'rewrites note content correclty' do + subject.execute + new_note = new_issue.notes.first + + expect(note.note).to match(/Simple text with image: #{FileUploader::MARKDOWN_PATTERN}/) + expect(new_note.note).to match(/Simple text with image: #{FileUploader::MARKDOWN_PATTERN}/) + expect(note.note).not_to eq(new_note.note) + expect(note.note_html).not_to eq(new_note.note_html) + end + end end end -- cgit v1.2.1 From bf8f4c135a0d893defb6dd97e07b84184f5270a7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 23 May 2019 11:50:06 -0700 Subject: Fix OmniAuth OAuth2Generic strategy not loading In https://github.com/rails/rails/commit/83b767ce, Rails 5.1 removed support for using a String to specify a middleware. When the strategy_class argument is passed from the GitLab YAML config to Devise, Devise passes the string value straight through to Rails, and GitLab would crash with a NoMethodError inside ActionDispatch::MiddlewareStack. To make this OmniAuth strategy work again, we normalize the arguments by converting the strategy_class value into an actual Class. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/62216 --- spec/lib/gitlab/omniauth_initializer_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb index d808b4d49e0..f9c0daf1ef1 100644 --- a/spec/lib/gitlab/omniauth_initializer_spec.rb +++ b/spec/lib/gitlab/omniauth_initializer_spec.rb @@ -38,6 +38,28 @@ describe Gitlab::OmniauthInitializer do subject.execute([hash_config]) end + it 'normalizes a String strategy_class' do + hash_config = { 'name' => 'hash', 'args' => { strategy_class: 'OmniAuth::Strategies::OAuth2Generic' } } + + expect(devise_config).to receive(:omniauth).with(:hash, strategy_class: OmniAuth::Strategies::OAuth2Generic) + + subject.execute([hash_config]) + end + + it 'allows a class to be specified in strategy_class' do + hash_config = { 'name' => 'hash', 'args' => { strategy_class: OmniAuth::Strategies::OAuth2Generic } } + + expect(devise_config).to receive(:omniauth).with(:hash, strategy_class: OmniAuth::Strategies::OAuth2Generic) + + subject.execute([hash_config]) + end + + it 'throws an error for an invalid strategy_class' do + hash_config = { 'name' => 'hash', 'args' => { strategy_class: 'OmniAuth::Strategies::Bogus' } } + + expect { subject.execute([hash_config]) }.to raise_error(NameError) + end + it 'configures fail_with_empty_uid for shibboleth' do shibboleth_config = { 'name' => 'shibboleth', 'args' => {} } -- cgit v1.2.1 From ed8ebc635a06380e04f2e82df6e65095314a54d2 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Fri, 24 May 2019 00:45:02 +0000 Subject: Changes RackAttack logger to use structured logs Creates a new filename to register auth logs. This change should allow SRE's queries to make better queries through logging infrastructure. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/54528 --- spec/requests/rack_attack_global_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'spec') diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index a12646ea222..89adbc77a7f 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -182,6 +182,17 @@ describe 'Rack Attack global throttles' do end end end + + it 'logs RackAttack info into structured logs' do + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_http_status 200 + end + + expect(Gitlab::AuthLogger).to receive(:error).once + + get url_that_does_not_require_authentication + end end context 'when the throttle is disabled' do @@ -327,6 +338,17 @@ describe 'Rack Attack global throttles' do expect_rejection { get url_that_requires_authentication } end + + it 'logs RackAttack info into structured logs' do + requests_per_period.times do + get url_that_requires_authentication + expect(response).to have_http_status 200 + end + + expect(Gitlab::AuthLogger).to receive(:error).once + + get url_that_requires_authentication + end end context 'when the throttle is disabled' do -- cgit v1.2.1 From f0efa2f91f8687a1cf0f39596790edaf338e8ec5 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 20 May 2019 15:55:11 +1200 Subject: Stop emitting AUTO_DEVOPS_DOMAIN Ci variable Update documentation to reflect removal --- spec/lib/gitlab/ci/config_spec.rb | 2 -- spec/models/ci/build_spec.rb | 24 -------------- spec/models/clusters/cluster_spec.rb | 59 ++++----------------------------- spec/models/project_auto_devops_spec.rb | 32 ------------------ spec/models/project_spec.rb | 58 -------------------------------- 5 files changed, 6 insertions(+), 169 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index fd2a29e4ddb..092e9f242b7 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -190,7 +190,6 @@ describe Gitlab::Ci::Config do let(:remote_file_content) do <<~HEREDOC variables: - AUTO_DEVOPS_DOMAIN: domain.example.com POSTGRES_USER: user POSTGRES_PASSWORD: testing-password POSTGRES_ENABLED: "true" @@ -232,7 +231,6 @@ describe Gitlab::Ci::Config do "bundle install --jobs $(nproc) \"${FLAGS[@]}\"" ] variables = { - AUTO_DEVOPS_DOMAIN: "domain.example.com", POSTGRES_USER: "user", POSTGRES_PASSWORD: "testing-password", POSTGRES_ENABLED: "true", diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 5f2e8aa0baa..bc81c34f7ab 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2604,30 +2604,6 @@ describe Ci::Build do it { is_expected.to include(ci_config_path) } end - context 'when using auto devops' do - context 'and is enabled' do - before do - project.create_auto_devops!(enabled: true, domain: 'example.com') - end - - it "includes AUTO_DEVOPS_DOMAIN" do - is_expected.to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) - end - end - - context 'and is disabled' do - before do - project.create_auto_devops!(enabled: false, domain: 'example.com') - end - - it "includes AUTO_DEVOPS_DOMAIN" do - is_expected.not_to include( - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false }) - end - end - end - context 'when pipeline variable overrides build variable' do before do build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }] diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 3ee8c340bfe..f066ed6b620 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -557,62 +557,15 @@ describe Clusters::Cluster do end context 'with no domain on cluster' do - context 'with a project cluster' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } - context 'with domain set at instance level' do - before do - stub_application_setting(auto_devops_domain: 'global_domain.com') - - it { is_expected.to eq('global_domain.com') } - end - end - - context 'with domain set on ProjectAutoDevops' do - before do - auto_devops = project.build_auto_devops(domain: 'legacy-ado-domain.com') - auto_devops.save - end - - it { is_expected.to eq('legacy-ado-domain.com') } - end - - context 'with domain set as environment variable on project' do - before do - variable = project.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'project-ado-domain.com') - variable.save - end - - it { is_expected.to eq('project-ado-domain.com') } + context 'with domain set at instance level' do + before do + stub_application_setting(auto_devops_domain: 'global_domain.com') end - context 'with domain set as environment variable on the group project' do - let(:group) { create(:group) } - - before do - project.update(parent_id: group.id) - variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com') - variable.save - end - - it { is_expected.to eq('group-ado-domain.com') } - end - end - - context 'with a group cluster' do - let(:cluster) { create(:cluster, :group, :provided_by_gcp) } - - context 'with domain set as environment variable for the group' do - let(:group) { cluster.group } - - before do - variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com') - variable.save - end - - it { is_expected.to eq('group-ado-domain.com') } - end + it { is_expected.to eq('global_domain.com') } end end end diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index b81e5610e2c..5148604677c 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -45,34 +45,6 @@ describe ProjectAutoDevops do describe '#predefined_variables' do let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) } - context 'when domain is defined' do - let(:domain) { 'example.com' } - - it 'returns AUTO_DEVOPS_DOMAIN' do - expect(auto_devops.predefined_variables).to include(domain_variable) - end - end - - context 'when domain is not defined' do - let(:domain) { nil } - - context 'when there is an instance domain specified' do - before do - allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com') - end - - it { expect(auto_devops.predefined_variables).to include(domain_variable) } - end - - context 'when there is no instance domain specified' do - before do - allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil) - end - - it { expect(auto_devops.predefined_variables).not_to include(domain_variable) } - end - end - context 'when deploy_strategy is manual' do let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) } let(:expected_variables) do @@ -105,10 +77,6 @@ describe ProjectAutoDevops do .not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED") end end - - def domain_variable - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true } - end end describe '#create_gitlab_deploy_token' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 425096d7e80..08662231fdf 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3975,64 +3975,6 @@ describe Project do end end - describe '#auto_devops_variables' do - set(:project) { create(:project) } - - subject { project.auto_devops_variables } - - context 'when enabled in instance settings' do - before do - stub_application_setting(auto_devops_enabled: true) - end - - context 'when domain is empty' do - before do - stub_application_setting(auto_devops_domain: nil) - end - - it 'variables does not include AUTO_DEVOPS_DOMAIN' do - is_expected.not_to include(domain_variable) - end - end - - context 'when domain is configured' do - before do - stub_application_setting(auto_devops_domain: 'example.com') - end - - it 'variables includes AUTO_DEVOPS_DOMAIN' do - is_expected.to include(domain_variable) - end - end - end - - context 'when explicitly enabled' do - context 'when domain is empty' do - before do - create(:project_auto_devops, project: project, domain: nil) - end - - it 'variables does not include AUTO_DEVOPS_DOMAIN' do - is_expected.not_to include(domain_variable) - end - end - - context 'when domain is configured' do - before do - create(:project_auto_devops, project: project, domain: 'example.com') - end - - it 'variables includes AUTO_DEVOPS_DOMAIN' do - is_expected.to include(domain_variable) - end - end - end - - def domain_variable - { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true } - end - end - describe '#latest_successful_builds_for' do let(:project) { build(:project) } -- cgit v1.2.1 From f2e6aec0b84357e4d755e71aae9ebafa85df3220 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 22 May 2019 14:14:17 +1200 Subject: Remove un-used methods These two methods were used in emitting AUTO_DEVOPS_DOMAIN which has now been dropped. --- spec/models/project_auto_devops_spec.rb | 28 ---------------------------- 1 file changed, 28 deletions(-) (limited to 'spec') diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 5148604677c..7bdd2367a68 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -14,34 +14,6 @@ describe ProjectAutoDevops do it { is_expected.to respond_to(:created_at) } it { is_expected.to respond_to(:updated_at) } - describe '#has_domain?' do - context 'when domain is defined' do - let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: 'domain.com') } - - it { expect(auto_devops).to have_domain } - end - - context 'when domain is empty' do - let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: '') } - - context 'when there is an instance domain specified' do - before do - allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com') - end - - it { expect(auto_devops).to have_domain } - end - - context 'when there is no instance domain specified' do - before do - allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil) - end - - it { expect(auto_devops).not_to have_domain } - end - end - end - describe '#predefined_variables' do let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) } -- cgit v1.2.1 From b9c19f6b0f5c85956bf85935452c34fc6a21bcb0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 10:08:42 +0200 Subject: Do not serialize a pipeline again when showing a build This change makes it possible to avoid additional serialization of entire pipeline when rendering a build details page. Instead we expose more information from the build entity itself what is much cheaper than serializing entire pipeline. Additionally we do not need to serialize `latest?` flag, which involves multiple Gitaly calls. --- spec/serializers/pipeline_entity_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 47f767ae4ab..93d18f70cdc 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -48,8 +48,8 @@ describe PipelineEntity do it 'contains flags' do expect(subject).to include :flags expect(subject[:flags]) - .to include :latest, :stuck, :auto_devops, - :yaml_errors, :retryable, :cancelable, :merge_request + .to include :stuck, :auto_devops, :yaml_errors, + :retryable, :cancelable, :merge_request end end -- cgit v1.2.1 From a12b61849cec04bb1d391f51bfe6fae87873e6ca Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 12:45:24 +0200 Subject: Add a controller test verifying exposure of stages --- spec/controllers/projects/jobs_controller_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index bd30d4ee88b..982e64c4887 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -148,6 +148,12 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do get_show(id: job.id, format: :json) end + it 'does not serialize builds in exposed stages' do + json_response.dig('pipeline', 'details', 'stages').tap do |stages| + expect(stages.map(&:keys).flatten).to eq %w[name title status path dropdown_path] + end + end + context 'when job failed' do it 'exposes needed information' do expect(response).to have_gitlab_http_status(:ok) -- cgit v1.2.1 From d457f3d3ef90b17da74d21cba736badec4d77414 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 13:04:43 +0200 Subject: Enable counting Gitaly requests in jobs show tests --- spec/controllers/projects/jobs_controller_spec.rb | 78 +++++++++++++++++------ 1 file changed, 59 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 982e64c4887..cf6bab46961 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -101,7 +101,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end end - describe 'GET show' do + describe 'GET show', :request_store do let!(:job) { create(:ci_build, :failed, pipeline: pipeline) } let!(:second_job) { create(:ci_build, :failed, pipeline: pipeline) } let!(:third_job) { create(:ci_build, :failed) } @@ -143,19 +143,24 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do project.add_developer(user) sign_in(user) - allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request) - - get_show(id: job.id, format: :json) + allow_any_instance_of(Ci::Build) + .to receive(:merge_request) + .and_return(merge_request) end it 'does not serialize builds in exposed stages' do + get_show_json + json_response.dig('pipeline', 'details', 'stages').tap do |stages| - expect(stages.map(&:keys).flatten).to eq %w[name title status path dropdown_path] + expect(stages.map(&:keys).flatten) + .to eq %w[name title status path dropdown_path] end end context 'when job failed' do it 'exposes needed information' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z}) @@ -165,6 +170,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end context 'when job is running' do + before do + get_show_json + end + context 'job is cancelable' do let(:job) { create(:ci_build, :running, pipeline: pipeline) } @@ -187,6 +196,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end context 'when job has artifacts' do + before do + get_show_json + end + context 'with not expiry date' do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } @@ -218,6 +231,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } it 'exposes empty state illustrations' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['status']['illustration']).to have_key('image') @@ -230,6 +245,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :success, pipeline: pipeline) } it 'does not exposes the deployment information' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(json_response['deployment_status']).to be_nil end @@ -241,6 +258,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) } it 'exposes the deployment information' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(json_response).to match_schema('job/job_details') expect(json_response['deployment_status']["status"]).to eq 'creating' @@ -256,11 +275,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do before do project.add_maintainer(user) sign_in(user) - - get_show(id: job.id, format: :json) end it 'user can edit runner' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['runner']).to have_key('edit_path') @@ -276,11 +295,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do before do project.add_maintainer(user) sign_in(user) - - get_show(id: job.id, format: :json) end it 'user can not edit runner' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['runner']).not_to have_key('edit_path') @@ -295,11 +314,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do before do project.add_maintainer(user) sign_in(user) - - get_show(id: job.id, format: :json) end it 'user can not edit runner' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['runner']).not_to have_key('edit_path') @@ -312,6 +331,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) } it 'exposes needed information' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['runners']['online']).to be false @@ -325,6 +346,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) } it 'exposes needed information' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['runners']['online']).to be false @@ -334,6 +357,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end context 'settings_path' do + before do + get_show_json + end + context 'when user is developer' do it 'settings_path is not available' do expect(response).to have_gitlab_http_status(:ok) @@ -360,6 +387,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do context 'when no trace is available' do it 'has_trace is false' do + get_show_json + expect(response).to match_response_schema('job/job_details') expect(json_response['has_trace']).to be false end @@ -369,17 +398,21 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :running, :trace_live, pipeline: pipeline) } it "has_trace is true" do + get_show_json + expect(response).to match_response_schema('job/job_details') expect(json_response['has_trace']).to be true end end it 'exposes the stage the job belongs to' do + get_show_json + expect(json_response['stage']).to eq('test') end end - context 'when requesting JSON job is triggered' do + context 'when requesting triggered job JSON' do let!(:merge_request) { create(:merge_request, source_project: project) } let(:trigger) { create(:ci_trigger, project: project) } let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) } @@ -389,15 +422,15 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do project.add_developer(user) sign_in(user) - allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request) + allow_any_instance_of(Ci::Build) + .to receive(:merge_request) + .and_return(merge_request) end context 'with no variables' do - before do - get_show(id: job.id, format: :json) - end - it 'exposes trigger information' do + get_show_json + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['trigger']['short_token']).to eq 'toke' @@ -414,7 +447,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do before do project.add_maintainer(user) - get_show(id: job.id, format: :json) + get_show_json end it 'returns a job_detail' do @@ -438,7 +471,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do context 'user is not a mantainer' do before do - get_show(id: job.id, format: :json) + get_show_json end it 'returns a job_detail' do @@ -462,6 +495,13 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end end + def get_show_json + get_show(id: job.id, format: :json) + + puts 'X'*50 + puts Gitlab::GitalyClient.get_request_count + end + def get_show(**extra_params) params = { namespace_id: project.namespace.to_param, -- cgit v1.2.1 From b0360335dcca34b2028b80febfb4a6221dc79967 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 13:10:14 +0200 Subject: Assert on no Gitaly requests in a job show tests --- spec/controllers/projects/jobs_controller_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index cf6bab46961..21cbd498744 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -496,10 +496,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end def get_show_json - get_show(id: job.id, format: :json) - - puts 'X'*50 - puts Gitlab::GitalyClient.get_request_count + expect { get_show(id: job.id, format: :json) } + .not_to change { Gitlab::GitalyClient.get_request_count } end def get_show(**extra_params) -- cgit v1.2.1 From 1052f64c3cf90694443487f3485d6f9ae26a5ab8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 13:41:52 +0200 Subject: Add specs for Gitaly calls in deployment serialization --- spec/controllers/projects/jobs_controller_spec.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 21cbd498744..f467d13cfd5 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -253,6 +253,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end context 'with deployment' do + before do + create(:deployment, :success, environment: environment, project: project) + end + let(:merge_request) { create(:merge_request, source_project: project) } let(:environment) { create(:environment, project: project, name: 'staging', state: :available) } let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) } @@ -262,8 +266,9 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to match_schema('job/job_details') - expect(json_response['deployment_status']["status"]).to eq 'creating' - expect(json_response['deployment_status']["environment"]).not_to be_nil + expect(json_response.dig('deployment_status', 'status')).to eq 'creating' + expect(json_response.dig('deployment_status', 'environment')).not_to be_nil + expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to be_nil end end -- cgit v1.2.1 From 412a38570245003b6509bf20cff2cce206ec8aa7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 15:20:20 +0200 Subject: Do not serialize a deployment commit showing a job --- spec/controllers/projects/jobs_controller_spec.rb | 2 ++ spec/serializers/build_details_entity_spec.rb | 24 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index f467d13cfd5..b5d85ad1bcf 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -269,6 +269,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(json_response.dig('deployment_status', 'status')).to eq 'creating' expect(json_response.dig('deployment_status', 'environment')).not_to be_nil expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to be_nil + expect(json_response.dig('deployment_status', 'environment', 'last_deployment')) + .not_to include('commit') end end diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 1edf69dc290..aa0db7bc326 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -122,5 +122,29 @@ describe BuildDetailsEntity do it { is_expected.to include(failure_reason: 'unmet_prerequisites') } end + + context 'when a build has environment with latest deployment' do + let(:build) do + create(:ci_build, :running, environment: environment.name, pipeline: pipeline) + end + + let(:environment) do + create(:environment, project: project, name: 'staging', state: :available) + end + + before do + create(:deployment, :success, environment: environment, project: project) + + allow(request).to receive(:project).and_return(project) + end + + it 'does not serialize latest deployment commit' do + response = subject.with_indifferent_access + + response.dig(:deployment_status, :environment, :last_deployment).tap do |deployment| + expect(deployment).not_to include(:commit) + end + end + end end end -- cgit v1.2.1 From de24df98a3a20f87f5fed513d27a20b6238d6959 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 17:30:57 +0200 Subject: Do not serialize deployment details for build details page --- spec/controllers/projects/jobs_controller_spec.rb | 8 +++--- spec/serializers/build_details_entity_spec.rb | 4 +-- spec/serializers/deployment_entity_spec.rb | 32 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index b5d85ad1bcf..9ef00fff3b2 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -253,14 +253,14 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end context 'with deployment' do - before do - create(:deployment, :success, environment: environment, project: project) - end - let(:merge_request) { create(:merge_request, source_project: project) } let(:environment) { create(:environment, project: project, name: 'staging', state: :available) } let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) } + before do + create(:deployment, :success, environment: environment, project: project) + end + it 'exposes the deployment information' do get_show_json diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index aa0db7bc326..68cf428ed38 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -138,11 +138,11 @@ describe BuildDetailsEntity do allow(request).to receive(:project).and_return(project) end - it 'does not serialize latest deployment commit' do + it 'does not serialize latest deployment commit and associated builds' do response = subject.with_indifferent_access response.dig(:deployment_status, :environment, :last_deployment).tap do |deployment| - expect(deployment).not_to include(:commit) + expect(deployment).not_to include(:commit, :deployable, :manual_actions, :scheduled_actions) end end end diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb index 894fd7a0a12..f0bbf90c6c6 100644 --- a/spec/serializers/deployment_entity_spec.rb +++ b/spec/serializers/deployment_entity_spec.rb @@ -10,6 +10,7 @@ describe DeploymentEntity do let(:build) { create(:ci_build, :manual, pipeline: pipeline) } let(:pipeline) { create(:ci_pipeline, project: project, user: user) } let(:entity) { described_class.new(deployment, request: request) } + subject { entity.as_json } before do @@ -47,6 +48,16 @@ describe DeploymentEntity do expect(subject[:manual_actions]).not_to be_present end end + + context 'when deployment details serialization was disabled' do + let(:entity) do + described_class.new(deployment, request: request, deployment_details: false) + end + + it 'does not serialize manual actions details' do + expect(subject.with_indifferent_access).not_to include(:manual_actions) + end + end end describe 'scheduled_actions' do @@ -69,5 +80,26 @@ describe DeploymentEntity do expect(subject[:scheduled_actions]).to be_empty end end + + context 'when deployment details serialization was disabled' do + let(:entity) do + described_class.new(deployment, request: request, deployment_details: false) + end + + it 'does not serialize scheduled actions details' do + expect(subject.with_indifferent_access).not_to include(:scheduled_actions) + end + end + end + + context 'when deployment details serialization was disabled' do + let(:entity) do + described_class.new(deployment, request: request, deployment_details: false) + end + + it 'does not serialize deployment details' do + expect(subject.with_indifferent_access) + .not_to include(:commit, :deployable, :manual_actions, :scheduled_actions) + end end end -- cgit v1.2.1 From cc4a0d9a470867ba5d0c383720b573dedcf2220e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 6 May 2019 17:36:02 +0200 Subject: Add specs checking serialization of a stage builds --- spec/serializers/pipeline_entity_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 93d18f70cdc..6be612ec226 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -64,6 +64,12 @@ describe PipelineEntity do create(:ci_build, :failed, pipeline: pipeline) end + it 'does not serialize stage builds' do + subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage| + expect(stage).not_to include(:groups, :latest_statuses, :retries) + end + end + context 'user has ability to retry pipeline' do before do project.add_developer(user) @@ -92,6 +98,12 @@ describe PipelineEntity do create(:ci_build, :pending, pipeline: pipeline) end + it 'does not serialize stage builds' do + subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage| + expect(stage).not_to include(:groups, :latest_statuses, :retries) + end + end + context 'user has ability to cancel pipeline' do before do project.add_developer(user) -- cgit v1.2.1 From a96b9ebfbeb7926fdee632f0eeef9e09a63bec21 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 6 May 2019 23:49:12 +0100 Subject: Removes fetchStage actions and mutations Updates tests --- spec/frontend/jobs/store/mutations_spec.js | 38 +--- spec/javascripts/jobs/components/sidebar_spec.js | 14 +- spec/javascripts/jobs/mock_data.js | 269 ++++++++++++----------- spec/javascripts/jobs/store/actions_spec.js | 105 --------- 4 files changed, 137 insertions(+), 289 deletions(-) (limited to 'spec') diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js index d7908efcf13..343301b8716 100644 --- a/spec/frontend/jobs/store/mutations_spec.js +++ b/spec/frontend/jobs/store/mutations_spec.js @@ -150,44 +150,8 @@ describe('Jobs Store Mutations', () => { }); }); - describe('REQUEST_STAGES', () => { - it('sets isLoadingStages to true', () => { - mutations[types.REQUEST_STAGES](stateCopy); - - expect(stateCopy.isLoadingStages).toEqual(true); - }); - }); - - describe('RECEIVE_STAGES_SUCCESS', () => { - beforeEach(() => { - mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]); - }); - - it('sets isLoadingStages to false', () => { - expect(stateCopy.isLoadingStages).toEqual(false); - }); - - it('sets stages', () => { - expect(stateCopy.stages).toEqual([{ name: 'build' }]); - }); - }); - - describe('RECEIVE_STAGES_ERROR', () => { - beforeEach(() => { - mutations[types.RECEIVE_STAGES_ERROR](stateCopy); - }); - - it('sets isLoadingStages to false', () => { - expect(stateCopy.isLoadingStages).toEqual(false); - }); - - it('resets stages', () => { - expect(stateCopy.stages).toEqual([]); - }); - }); - describe('REQUEST_JOBS_FOR_STAGE', () => { - it('sets isLoadingStages to true', () => { + it('sets isLoadingJobs to true', () => { mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); expect(stateCopy.isLoadingJobs).toEqual(true); diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js index 26d9effcac5..740bc3d0491 100644 --- a/spec/javascripts/jobs/components/sidebar_spec.js +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import sidebarDetailsBlock from '~/jobs/components/sidebar.vue'; import createStore from '~/jobs/store'; -import job, { stages, jobsInStage } from '../mock_data'; +import job, { jobsInStage } from '../mock_data'; import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { trimText } from '../../helpers/text_helper'; @@ -131,18 +131,8 @@ describe('Sidebar details block', () => { store.dispatch('receiveJobSuccess', job); }); - describe('while fetching stages', () => { - it('it does not render dropdown', () => { - store.dispatch('requestStages'); - vm = mountComponentWithStore(SidebarComponent, { store }); - - expect(vm.$el.querySelector('.js-selected-stage')).toBeNull(); - }); - }); - describe('with stages', () => { beforeEach(() => { - store.dispatch('receiveStagesSuccess', stages); vm = mountComponentWithStore(SidebarComponent, { store }); }); @@ -156,7 +146,6 @@ describe('Sidebar details block', () => { describe('without jobs for stages', () => { beforeEach(() => { store.dispatch('receiveJobSuccess', job); - store.dispatch('receiveStagesSuccess', stages); vm = mountComponentWithStore(SidebarComponent, { store }); }); @@ -168,7 +157,6 @@ describe('Sidebar details block', () => { describe('with jobs for stages', () => { beforeEach(() => { store.dispatch('receiveJobSuccess', job); - store.dispatch('receiveStagesSuccess', stages); store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses); vm = mountComponentWithStore(SidebarComponent, { store }); }); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 1a7f338c5fa..f913db41d31 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -3,140 +3,6 @@ import { TEST_HOST } from 'spec/test_constants'; const threeWeeksAgo = new Date(); threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); -export default { - id: 4757, - name: 'test', - build_path: '/root/ci-mock/-/jobs/4757', - retry_path: '/root/ci-mock/-/jobs/4757/retry', - cancel_path: '/root/ci-mock/-/jobs/4757/cancel', - new_issue_path: '/root/ci-mock/issues/new', - playable: false, - created_at: threeWeeksAgo.toISOString(), - updated_at: threeWeeksAgo.toISOString(), - finished_at: threeWeeksAgo.toISOString(), - queued: 9.54, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`, - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/-/jobs/4757/retry', - method: 'post', - }, - }, - coverage: 20, - erased_at: threeWeeksAgo.toISOString(), - erased: false, - duration: 6.785563, - tags: ['tag'], - user: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - erase_path: '/root/ci-mock/-/jobs/4757/erase', - artifacts: [null], - runner: { - id: 1, - description: 'local ci runner', - edit_path: '/root/ci-mock/runners/1/edit', - }, - pipeline: { - id: 140, - user: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - active: false, - coverage: null, - source: 'unknown', - created_at: '2017-05-24T09:59:58.634Z', - updated_at: '2017-06-01T17:32:00.062Z', - path: '/root/ci-mock/pipelines/140', - flags: { - latest: true, - stuck: false, - yaml_errors: false, - retryable: false, - cancelable: false, - }, - details: { - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/pipelines/140', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - }, - duration: 6, - finished_at: '2017-06-01T17:32:00.042Z', - }, - ref: { - name: 'abc', - path: '/root/ci-mock/commits/abc', - tag: false, - branch: true, - }, - commit: { - id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - short_id: 'c5864777', - title: 'Add new file', - created_at: '2017-05-24T10:59:52.000+01:00', - parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'], - message: 'Add new file', - author_name: 'Root', - author_email: 'admin@example.com', - authored_date: '2017-05-24T10:59:52.000+01:00', - committer_name: 'Root', - committer_email: 'admin@example.com', - committed_date: '2017-05-24T10:59:52.000+01:00', - author: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - author_gravatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - commit_url: - 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - }, - }, - metadata: { - timeout_human_readable: '1m 40s', - timeout_source: 'runner', - }, - merge_request: { - iid: 2, - path: '/root/ci-mock/merge_requests/2', - }, - raw_path: '/root/ci-mock/builds/4757/raw', - has_trace: true, -}; - export const stages = [ { name: 'build', @@ -1043,6 +909,141 @@ export const stages = [ }, ]; +export default { + id: 4757, + name: 'test', + build_path: '/root/ci-mock/-/jobs/4757', + retry_path: '/root/ci-mock/-/jobs/4757/retry', + cancel_path: '/root/ci-mock/-/jobs/4757/cancel', + new_issue_path: '/root/ci-mock/issues/new', + playable: false, + created_at: threeWeeksAgo.toISOString(), + updated_at: threeWeeksAgo.toISOString(), + finished_at: threeWeeksAgo.toISOString(), + queued: 9.54, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + has_details: true, + details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`, + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/root/ci-mock/-/jobs/4757/retry', + method: 'post', + }, + }, + coverage: 20, + erased_at: threeWeeksAgo.toISOString(), + erased: false, + duration: 6.785563, + tags: ['tag'], + user: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + erase_path: '/root/ci-mock/-/jobs/4757/erase', + artifacts: [null], + runner: { + id: 1, + description: 'local ci runner', + edit_path: '/root/ci-mock/runners/1/edit', + }, + pipeline: { + id: 140, + user: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + active: false, + coverage: null, + source: 'unknown', + created_at: '2017-05-24T09:59:58.634Z', + updated_at: '2017-06-01T17:32:00.062Z', + path: '/root/ci-mock/pipelines/140', + flags: { + latest: true, + stuck: false, + yaml_errors: false, + retryable: false, + cancelable: false, + }, + details: { + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + has_details: true, + details_path: '/root/ci-mock/pipelines/140', + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + }, + duration: 6, + finished_at: '2017-06-01T17:32:00.042Z', + stages: stages, + }, + ref: { + name: 'abc', + path: '/root/ci-mock/commits/abc', + tag: false, + branch: true, + }, + commit: { + id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + short_id: 'c5864777', + title: 'Add new file', + created_at: '2017-05-24T10:59:52.000+01:00', + parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'], + message: 'Add new file', + author_name: 'Root', + author_email: 'admin@example.com', + authored_date: '2017-05-24T10:59:52.000+01:00', + committer_name: 'Root', + committer_email: 'admin@example.com', + committed_date: '2017-05-24T10:59:52.000+01:00', + author: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + author_gravatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + commit_url: + 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + }, + }, + metadata: { + timeout_human_readable: '1m 40s', + timeout_source: 'runner', + }, + merge_request: { + iid: 2, + path: '/root/ci-mock/merge_requests/2', + }, + raw_path: '/root/ci-mock/builds/4757/raw', + has_trace: true, +}; + export const jobsInStage = { name: 'build', title: 'build: running', diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js index 77b44995b12..7b96df85b82 100644 --- a/spec/javascripts/jobs/store/actions_spec.js +++ b/spec/javascripts/jobs/store/actions_spec.js @@ -16,10 +16,6 @@ import { stopPollingTrace, receiveTraceSuccess, receiveTraceError, - requestStages, - fetchStages, - receiveStagesSuccess, - receiveStagesError, requestJobsForStage, fetchJobsForStage, receiveJobsForStageSuccess, @@ -307,107 +303,6 @@ describe('Job State actions', () => { }); }); - describe('requestStages', () => { - it('should commit REQUEST_STAGES mutation ', done => { - testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done); - }); - }); - - describe('fetchStages', () => { - let mock; - - beforeEach(() => { - mockedState.job.pipeline = { - path: `${TEST_HOST}/endpoint`, - }; - mockedState.selectedStage = 'deploy'; - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('success', () => { - it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => { - mock - .onGet(`${TEST_HOST}/endpoint.json`) - .replyOnce(200, { details: { stages: [{ name: 'build' }, { name: 'deploy' }] } }); - - testAction( - fetchStages, - null, - mockedState, - [], - [ - { - type: 'requestStages', - }, - { - payload: [{ name: 'build' }, { name: 'deploy' }], - type: 'receiveStagesSuccess', - }, - { - payload: { name: 'deploy' }, - type: 'fetchJobsForStage', - }, - ], - done, - ); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); - }); - - it('dispatches requestStages and receiveStagesError ', done => { - testAction( - fetchStages, - null, - mockedState, - [], - [ - { - type: 'requestStages', - }, - { - type: 'receiveStagesError', - }, - ], - done, - ); - }); - }); - }); - - describe('receiveStagesSuccess', () => { - it('should commit RECEIVE_STAGES_SUCCESS mutation ', done => { - testAction( - receiveStagesSuccess, - {}, - mockedState, - [{ type: types.RECEIVE_STAGES_SUCCESS, payload: {} }], - [], - done, - ); - }); - }); - - describe('receiveStagesError', () => { - it('should commit RECEIVE_STAGES_ERROR mutation ', done => { - testAction( - receiveStagesError, - null, - mockedState, - [{ type: types.RECEIVE_STAGES_ERROR }], - [], - done, - ); - }); - }); - describe('requestJobsForStage', () => { it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => { testAction( -- cgit v1.2.1 From 6ef27f77447ad389ca9b508909964c127fd38eeb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 10 May 2019 19:02:00 +0200 Subject: Expose build environment latest deployable name and path --- spec/fixtures/api/schemas/environment.json | 6 +++++- spec/serializers/build_details_entity_spec.rb | 2 +- spec/serializers/deployment_entity_spec.rb | 11 ++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json index 9a10ab18c30..5b1e3c049fa 100644 --- a/spec/fixtures/api/schemas/environment.json +++ b/spec/fixtures/api/schemas/environment.json @@ -31,7 +31,11 @@ "last_deployment": { "oneOf": [ { "type": "null" }, - { "$ref": "deployment.json" } + { "$ref": "deployment.json" }, + { + "name": { "type": "string" }, + "build_path": { "type": "string" } + } ] } }, diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 68cf428ed38..9c2e5c79a9d 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -142,7 +142,7 @@ describe BuildDetailsEntity do response = subject.with_indifferent_access response.dig(:deployment_status, :environment, :last_deployment).tap do |deployment| - expect(deployment).not_to include(:commit, :deployable, :manual_actions, :scheduled_actions) + expect(deployment).not_to include(:commit, :manual_actions, :scheduled_actions) end end end diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb index f0bbf90c6c6..37614cc0b4c 100644 --- a/spec/serializers/deployment_entity_spec.rb +++ b/spec/serializers/deployment_entity_spec.rb @@ -93,13 +93,22 @@ describe DeploymentEntity do end context 'when deployment details serialization was disabled' do + include Gitlab::Routing + let(:entity) do described_class.new(deployment, request: request, deployment_details: false) end it 'does not serialize deployment details' do expect(subject.with_indifferent_access) - .not_to include(:commit, :deployable, :manual_actions, :scheduled_actions) + .not_to include(:commit, :manual_actions, :scheduled_actions) + end + + it 'only exposes deployable name and path' do + project_job_path(project, deployment.deployable).tap do |path| + expect(subject.fetch(:deployable)) + .to eq('name' => 'test', 'build_path' => path) + end end end end -- cgit v1.2.1 From 087680d78034243ed1f223d8fdfb3a34c74e5eff Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 13 May 2019 16:30:40 +0200 Subject: Fix deployment entity tests Fixes style lint errors --- spec/javascripts/jobs/mock_data.js | 2 +- spec/serializers/deployment_entity_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index f913db41d31..e37520f1f1a 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -995,7 +995,7 @@ export default { }, duration: 6, finished_at: '2017-06-01T17:32:00.042Z', - stages: stages, + stages, }, ref: { name: 'abc', diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb index 37614cc0b4c..76ad2aee5c5 100644 --- a/spec/serializers/deployment_entity_spec.rb +++ b/spec/serializers/deployment_entity_spec.rb @@ -107,7 +107,7 @@ describe DeploymentEntity do it 'only exposes deployable name and path' do project_job_path(project, deployment.deployable).tap do |path| expect(subject.fetch(:deployable)) - .to eq('name' => 'test', 'build_path' => path) + .to eq(name: 'test', build_path: path) end end end -- cgit v1.2.1 From fe225fd9289acb8d50157703849363de39046bca Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 23 May 2019 14:27:54 +0100 Subject: Set up mock data for stages --- spec/javascripts/jobs/mock_data.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index e37520f1f1a..3d40e94d219 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -995,7 +995,33 @@ export default { }, duration: 6, finished_at: '2017-06-01T17:32:00.042Z', - stages, + stages: [ + { + dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=build', + name: 'build', + path: '/jashkenas/underscore/pipelines/16#build', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + }, + title: 'build: passed', + }, + { + dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=test', + name: 'test', + path: '/jashkenas/underscore/pipelines/16#test', + status: { + icon: 'status_warning', + text: 'passed', + label: 'passed with warnings', + group: 'success-with-warnings', + }, + title: 'test: passed with warnings', + }, + ], }, ref: { name: 'abc', -- cgit v1.2.1 From 17b97bf029a7085f6b726071a15f5d231510f1b6 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 23 May 2019 15:28:55 +0100 Subject: Fix milestone references with HTML entities in the name When a milestone name contained an HTML entity that would be escaped (&, <, >), then it wasn't possible to refer to this milestone by name, or use it in a quick action. This already worked for labels, but not for milestones. We take care to re-escape un-matched milestones, too. --- .../filter/milestone_reference_filter_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'spec') diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 4c94e4fdae0..f0a5dc8d0d7 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -295,6 +295,25 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end + shared_examples 'references with HTML entities' do + before do + milestone.update!(title: '<html>') + end + + it 'links to a valid reference' do + doc = reference_filter('See %"<html>"') + + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) + expect(doc.text).to eq 'See %' + end + + it 'ignores invalid milestone names and escapes entities' do + act = %(Milestone %"<non valid>") + + expect(reference_filter(act).to_html).to eq act + end + end + shared_context 'project milestones' do let(:reference) { milestone.to_reference(format: :iid) } @@ -307,6 +326,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do it_behaves_like 'cross-project / cross-namespace complete reference' it_behaves_like 'cross-project / same-namespace complete reference' it_behaves_like 'cross project shorthand reference' + it_behaves_like 'references with HTML entities' end shared_context 'group milestones' do @@ -317,6 +337,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do it_behaves_like 'String-based single-word references' it_behaves_like 'String-based multi-word references in quotes' it_behaves_like 'referencing a milestone in a link href' + it_behaves_like 'references with HTML entities' it 'does not support references by IID' do doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}") -- cgit v1.2.1 From 52b0d3a492013b6fddb9be6ea359800dd3025b81 Mon Sep 17 00:00:00 2001 From: egrieff Date: Fri, 24 May 2019 10:18:56 +0100 Subject: Fix typo in test --- spec/services/issuable/clone/content_rewriter_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb index 92292528cc3..230e1123280 100644 --- a/spec/services/issuable/clone/content_rewriter_spec.rb +++ b/spec/services/issuable/clone/content_rewriter_spec.rb @@ -150,12 +150,12 @@ describe Issuable::Clone::ContentRewriter do end end - context 'notes with uploads' do + context 'notes with upload' do let(:uploader) { build(:file_uploader, project: project1) } let(:text) { "Simple text with image: #{uploader.markdown_link} "} let!(:note) { create(:note, noteable: original_issue, note: text, project: project1) } - it 'rewrites note content correclty' do + it 'rewrites note content correctly' do subject.execute new_note = new_issue.notes.first -- cgit v1.2.1 From d8bb8d458c09146d70261026a5c90e97903b99c4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 24 May 2019 10:39:18 +0100 Subject: Pull files for repository tree from GraphQL API --- .../table/__snapshots__/row_spec.js.snap | 2 + .../repository/components/table/index_spec.js | 51 ++++++++++++++++++---- .../repository/components/table/row_spec.js | 18 +++++--- spec/frontend/repository/utils/icon_spec.js | 2 +- 4 files changed, 56 insertions(+), 17 deletions(-) (limited to 'spec') diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap index f0b72343b6e..1b4564303e4 100644 --- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap +++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap @@ -16,7 +16,9 @@ exports[`Repository table row component renders table row 1`] = ` + test + diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js index 6f52cffe077..827927e6d9a 100644 --- a/spec/frontend/repository/components/table/index_spec.js +++ b/spec/frontend/repository/components/table/index_spec.js @@ -3,18 +3,19 @@ import { GlLoadingIcon } from '@gitlab/ui'; import Table from '~/repository/components/table/index.vue'; let vm; +let $apollo; + +function factory(path, data = () => ({})) { + $apollo = { + query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })), + }; -function factory(path, loading = false) { vm = shallowMount(Table, { propsData: { path, }, mocks: { - $apollo: { - queries: { - files: { loading }, - }, - }, + $apollo, }, }); } @@ -39,9 +40,41 @@ describe('Repository table component', () => { ); }); - it('renders loading icon', () => { - factory('/', true); + it('shows loading icon', () => { + factory('/'); + + vm.setData({ isLoadingFiles: true }); + + expect(vm.find(GlLoadingIcon).isVisible()).toBe(true); + }); + + describe('normalizeData', () => { + it('normalizes edge nodes', () => { + const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]); + + expect(output).toEqual(['1', '2']); + }); + }); + + describe('hasNextPage', () => { + it('returns undefined when hasNextPage is false', () => { + const output = vm.vm.hasNextPage({ + trees: { pageInfo: { hasNextPage: false } }, + submodules: { pageInfo: { hasNextPage: false } }, + blobs: { pageInfo: { hasNextPage: false } }, + }); + + expect(output).toBe(undefined); + }); + + it('returns pageInfo object when hasNextPage is true', () => { + const output = vm.vm.hasNextPage({ + trees: { pageInfo: { hasNextPage: false } }, + submodules: { pageInfo: { hasNextPage: false } }, + blobs: { pageInfo: { hasNextPage: true, nextCursor: 'test' } }, + }); - expect(vm.find(GlLoadingIcon).exists()).toBe(true); + expect(output).toEqual({ hasNextPage: true, nextCursor: 'test' }); + }); }); }); diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 216128dce25..6b4508c418e 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -29,9 +29,10 @@ describe('Repository table row component', () => { it('renders table row', () => { factory({ - id: 1, + id: '1', path: 'test', type: 'file', + currentPath: '/', }); expect(vm.element).toMatchSnapshot(); @@ -39,14 +40,15 @@ describe('Repository table row component', () => { it.each` type | component | componentName - ${'folder'} | ${RouterLinkStub} | ${'RouterLink'} + ${'tree'} | ${RouterLinkStub} | ${'RouterLink'} ${'file'} | ${'a'} | ${'hyperlink'} ${'commit'} | ${'a'} | ${'hyperlink'} `('renders a $componentName for type $type', ({ type, component }) => { factory({ - id: 1, + id: '1', path: 'test', type, + currentPath: '/', }); expect(vm.find(component).exists()).toBe(true); @@ -54,14 +56,15 @@ describe('Repository table row component', () => { it.each` type | pushes - ${'folder'} | ${true} + ${'tree'} | ${true} ${'file'} | ${false} ${'commit'} | ${false} - `('pushes new router if type $type is folder', ({ type, pushes }) => { + `('pushes new router if type $type is tree', ({ type, pushes }) => { factory({ - id: 1, + id: '1', path: 'test', type, + currentPath: '/', }); vm.trigger('click'); @@ -75,9 +78,10 @@ describe('Repository table row component', () => { it('renders commit ID for submodule', () => { factory({ - id: 1, + id: '1', path: 'test', type: 'commit', + currentPath: '/', }); expect(vm.find('.commit-sha').text()).toContain('1'); diff --git a/spec/frontend/repository/utils/icon_spec.js b/spec/frontend/repository/utils/icon_spec.js index 52787327bef..3d84705f7ea 100644 --- a/spec/frontend/repository/utils/icon_spec.js +++ b/spec/frontend/repository/utils/icon_spec.js @@ -6,7 +6,7 @@ describe('getIconName', () => { // file types it.each` type | path | icon - ${'folder'} | ${''} | ${'folder'} + ${'tree'} | ${''} | ${'folder'} ${'commit'} | ${''} | ${'archive'} ${'file'} | ${'test.pdf'} | ${'file-pdf-o'} ${'file'} | ${'test.jpg'} | ${'file-image-o'} -- cgit v1.2.1 From 234bc46b245321599ac1ce93007a8c080ad50b94 Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Fri, 24 May 2019 13:42:30 +0100 Subject: Replaces a hard-coded date in the job app spec --- spec/javascripts/jobs/components/job_app_spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index cef40117304..f28d2c2a882 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -90,9 +90,12 @@ describe('Job App ', () => { describe('triggered job', () => { beforeEach(() => { + const aYearAgo = new Date(); + aYearAgo.setFullYear(aYearAgo.getFullYear() - 1); + mock .onGet(props.endpoint) - .replyOnce(200, Object.assign({}, job, { started: '2017-05-24T10:59:52.000+01:00' })); + .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() })); vm = mountComponentWithStore(Component, { props, store }); }); -- cgit v1.2.1 From ecf54c2316e7c62d6afe35885e79d97e6e6c13c7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 24 May 2019 15:48:29 +0100 Subject: Update document title when repository router changes --- spec/frontend/repository/utils/title_spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 spec/frontend/repository/utils/title_spec.js (limited to 'spec') diff --git a/spec/frontend/repository/utils/title_spec.js b/spec/frontend/repository/utils/title_spec.js new file mode 100644 index 00000000000..c4879716fd7 --- /dev/null +++ b/spec/frontend/repository/utils/title_spec.js @@ -0,0 +1,15 @@ +import { setTitle } from '~/repository/utils/title'; + +describe('setTitle', () => { + it.each` + path | title + ${'/'} | ${'Files'} + ${'app'} | ${'app'} + ${'app/assets'} | ${'app/assets'} + ${'app/assets/javascripts'} | ${'app/assets/javascripts'} + `('sets document title as $title for $path', ({ path, title }) => { + setTitle(path, 'master', 'GitLab'); + + expect(document.title).toEqual(`${title} · master · GitLab`); + }); +}); -- cgit v1.2.1 From 1b1a960bf3e86d946f24ecb5de5b2f011c0d3846 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Thu, 23 May 2019 16:45:39 +0200 Subject: Remove the circuit breaker API The circuit breaker itself was removed in 11.5, this removes the corresponding API endpoints which returned empty data since then. --- spec/requests/api/circuit_breakers_spec.rb | 46 ------------------------------ 1 file changed, 46 deletions(-) delete mode 100644 spec/requests/api/circuit_breakers_spec.rb (limited to 'spec') diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb deleted file mode 100644 index 6c7cb151c74..00000000000 --- a/spec/requests/api/circuit_breakers_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' - -describe API::CircuitBreakers do - set(:user) { create(:user) } - set(:admin) { create(:admin) } - - describe 'GET circuit_breakers/repository_storage' do - it 'returns a 401 for anonymous users' do - get api('/circuit_breakers/repository_storage') - - expect(response).to have_gitlab_http_status(401) - end - - it 'returns a 403 for users' do - get api('/circuit_breakers/repository_storage', user) - - expect(response).to have_gitlab_http_status(403) - end - - it 'returns an Array of storages' do - get api('/circuit_breakers/repository_storage', admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_kind_of(Array) - expect(json_response).to be_empty - end - - describe 'GET circuit_breakers/repository_storage/failing' do - it 'returns an array of failing storages' do - get api('/circuit_breakers/repository_storage/failing', admin) - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_kind_of(Array) - expect(json_response).to be_empty - end - end - end - - describe 'DELETE circuit_breakers/repository_storage' do - it 'clears all circuit_breakers' do - delete api('/circuit_breakers/repository_storage', admin) - - expect(response).to have_gitlab_http_status(204) - end - end -end -- cgit v1.2.1 From a76fdcb7a30c6244ffb11a2e672e16d1e5b413b2 Mon Sep 17 00:00:00 2001 From: Kerri Miller Date: Mon, 20 May 2019 13:24:22 -0700 Subject: Reject slug+uri concat if slug is deemed unsafe First reported: https://gitlab.com/gitlab-org/gitlab-ce/issues/60143 When the page slug is "javascript:" and we attempt to link to a relative path (using `.` or `..`) the code will concatenate the slug and the uri. This MR adds a guard to that concat step that will return `nil` if the incoming slug matches against any of the "unsafe" slug regexes; currently this is only for the slug "javascript:" but can be extended if needed. Manually tested against a non-exhaustive list from OWASP of common javascript XSS exploits that have to to with mangling the "javascript:" method, and all are caught by this change or by existing code that ingests the user-specified slug. --- spec/lib/banzai/filter/wiki_link_filter_spec.rb | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'spec') diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb index b9059b85fdc..cce1cd0b284 100644 --- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb +++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb @@ -70,5 +70,47 @@ describe Banzai::Filter::WikiLinkFilter do expect(filtered_link.attribute('href').value).to eq(invalid_link) end end + + context "when the slug is deemed unsafe or invalid" do + let(:link) { "alert(1);" } + + invalid_slugs = [ + "javascript:", + "JaVaScRiPt:", + "\u0001java\u0003script:", + "javascript :", + "javascript: ", + "javascript : ", + ":javascript:", + "javascript:", + "javascript:", + "javascript:", + "javascript:", + "java\0script:", + "  javascript:" + ] + + invalid_slugs.each do |slug| + context "with the slug #{slug}" do + it "doesn't rewrite a (.) relative link" do + filtered_link = filter( + "Link", + project_wiki: wiki, + page_slug: slug).children[0] + + expect(filtered_link.attribute('href').value).not_to include(slug) + end + + it "doesn't rewrite a (..) relative link" do + filtered_link = filter( + "Link", + project_wiki: wiki, + page_slug: slug).children[0] + + expect(filtered_link.attribute('href').value).not_to include(slug) + end + end + end + end end end -- cgit v1.2.1 From e6442ddef75d298599b51f4e14ec14072fafa76d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 24 May 2019 18:28:23 +0300 Subject: Move some project routes under - scope Affected routes are: * autocomplete_sources * project_members * deploy_keys * deploy_tokens Signed-off-by: Dmitriy Zaporozhets --- spec/routing/project_routing_spec.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index dd9540c11d1..6f40e88d26f 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -138,9 +138,11 @@ describe 'project routing' do describe Projects::AutocompleteSourcesController, 'routing' do [:members, :issues, :merge_requests, :labels, :milestones, :commands, :snippets].each do |action| it "to ##{action}" do - expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get("/gitlab/gitlabhq/-/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq') end end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/autocomplete_sources/labels", "/gitlab/gitlabhq/-/autocomplete_sources/labels" end # pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages @@ -239,7 +241,10 @@ describe 'project routing' do it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :new, :create, :edit, :update] } let(:controller) { 'deploy_keys' } + let(:controller_path) { '/-/deploy_keys' } end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/deploy_keys", "/gitlab/gitlabhq/-/deploy_keys" end # project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index @@ -447,7 +452,10 @@ describe 'project routing' do it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :update, :destroy] } let(:controller) { 'project_members' } + let(:controller_path) { '/-/project_members' } end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/project_members", "/gitlab/gitlabhq/-/project_members" end # project_milestones GET /:project_id/milestones(.:format) milestones#index -- cgit v1.2.1 From 3141b800b74576d5b5dfc84a96353fea47ae974b Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Tue, 21 May 2019 10:50:59 +0800 Subject: Fix dropdown position when loading remote data Also fixes flaky spec --- spec/features/boards/sidebar_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spec') diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 87c0dc40e5c..b1798c11361 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -352,6 +352,8 @@ describe 'Issue Boards', :js do page.within('.labels') do click_link 'Edit' + wait_for_requests + click_link 'Create project label' fill_in 'new_label_name', with: 'test label' first('.suggest-colors-dropdown a').click @@ -368,6 +370,8 @@ describe 'Issue Boards', :js do page.within('.labels') do click_link 'Edit' + wait_for_requests + click_link 'Create project label' fill_in 'new_label_name', with: 'test label' first('.suggest-colors-dropdown a').click -- cgit v1.2.1 From a9ddd9498e208d8830f47ba2361691c416e41035 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Mon, 27 May 2019 07:35:07 +0000 Subject: Add expand/collapse button Add ability to expand/collapse error tracking settings --- spec/features/projects/settings/operations_settings_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb index af56cb0d4ee..d96e243d96b 100644 --- a/spec/features/projects/settings/operations_settings_spec.rb +++ b/spec/features/projects/settings/operations_settings_spec.rb @@ -46,6 +46,9 @@ describe 'Projects > Settings > For a forked project', :js do wait_for_requests + within '.js-error-tracking-settings' do + click_button('Expand') + end expect(page).to have_content('Sentry API URL') expect(page.body).to include('Error Tracking') expect(page).to have_button('Connect') @@ -86,6 +89,9 @@ describe 'Projects > Settings > For a forked project', :js do wait_for_requests + within '.js-error-tracking-settings' do + click_button('Expand') + end check('Active') fill_in('error-tracking-api-host', with: 'http://sentry.example.com') fill_in('error-tracking-token', with: 'token') -- cgit v1.2.1 From 3c913df28eb390bc2746b05b4911a62b76593d8b Mon Sep 17 00:00:00 2001 From: Jacques Erasmus Date: Mon, 27 May 2019 08:23:17 +0000 Subject: Add changelog entry Added a changelog entry for the feature --- spec/features/clusters/cluster_detail_page_spec.rb | 3 + spec/features/groups/clusters/user_spec.rb | 1 + spec/features/projects/clusters/user_spec.rb | 1 + spec/frontend/clusters/clusters_bundle_spec.js | 16 ++ spec/models/clusters/cluster_spec.rb | 163 ++++++++++++++++++--- spec/presenters/clusters/cluster_presenter_spec.rb | 40 ----- 6 files changed, 160 insertions(+), 64 deletions(-) (limited to 'spec') diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index d2e46d15730..683c57a97f8 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe 'Clusterable > Show page' do + include KubernetesHelpers + let(:current_user) { create(:user) } let(:cluster_ingress_help_text_selector) { '.js-ingress-domain-help-text' } let(:hide_modifier_selector) { '.hide' } @@ -83,6 +85,7 @@ describe 'Clusterable > Show page' do shared_examples 'editing a user-provided cluster' do before do + stub_kubeclient_discover(cluster.platform.api_url) clusterable.add_maintainer(current_user) visit cluster_path end diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index b661b5cbaef..84a8691a7f2 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -14,6 +14,7 @@ describe 'User Cluster', :js do allow(Groups::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) + allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected) end context 'when user does not have a cluster and visits cluster index page' do diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index fe4f737a7da..31cc09ae911 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -12,6 +12,7 @@ describe 'User Cluster', :js do allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) + allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected) end context 'when user does not have a cluster and visits cluster index page' do diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index 73897107f67..66b22fa2681 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -209,6 +209,22 @@ describe('Clusters', () => { expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy(); }); }); + + describe('when cluster is unreachable', () => { + it('should show the unreachable warning container', () => { + cluster.updateContainer(null, 'unreachable'); + + expect(cluster.unreachableContainer.classList.contains('hidden')).toBe(false); + }); + }); + + describe('when cluster has an authentication failure', () => { + it('should show the authentication failure warning container', () => { + cluster.updateContainer(null, 'authentication_failure'); + + expect(cluster.authenticationFailureContainer.classList.contains('hidden')).toBe(false); + }); + }); }); describe('installApplication', () => { diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index f066ed6b620..4739e62289a 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -2,7 +2,10 @@ require 'spec_helper' -describe Clusters::Cluster do +describe Clusters::Cluster, :use_clean_rails_memory_store_caching do + include ReactiveCachingHelpers + include KubernetesHelpers + it_behaves_like 'having unique enum values' subject { build(:cluster) } @@ -23,7 +26,6 @@ describe Clusters::Cluster do it { is_expected.to delegate_method(:status).to(:provider) } it { is_expected.to delegate_method(:status_reason).to(:provider) } - it { is_expected.to delegate_method(:status_name).to(:provider) } it { is_expected.to delegate_method(:on_creation?).to(:provider) } it { is_expected.to delegate_method(:active?).to(:platform_kubernetes).with_prefix } it { is_expected.to delegate_method(:rbac?).to(:platform_kubernetes).with_prefix } @@ -501,28 +503,6 @@ describe Clusters::Cluster do end end - describe '#created?' do - let(:cluster) { create(:cluster, :provided_by_gcp) } - - subject { cluster.created? } - - context 'when status_name is :created' do - before do - allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:created) - end - - it { is_expected.to eq(true) } - end - - context 'when status_name is not :created' do - before do - allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:creating) - end - - it { is_expected.to eq(false) } - end - end - describe '#allow_user_defined_namespace?' do let(:cluster) { create(:cluster, :provided_by_gcp) } @@ -617,4 +597,139 @@ describe Clusters::Cluster do it { is_expected.to be_truthy } end end + + describe '#status_name' do + subject { cluster.status_name } + + context 'the cluster has a provider' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + before do + cluster.provider.make_errored! + end + + it { is_expected.to eq :errored } + end + + context 'there is a cached connection status' do + let(:cluster) { create(:cluster, :provided_by_user) } + + before do + allow(cluster).to receive(:connection_status).and_return(:connected) + end + + it { is_expected.to eq :connected } + end + + context 'there is no connection status in the cache' do + let(:cluster) { create(:cluster, :provided_by_user) } + + before do + allow(cluster).to receive(:connection_status).and_return(nil) + end + + it { is_expected.to eq :created } + end + end + + describe '#connection_status' do + let(:cluster) { create(:cluster) } + let(:status) { :connected } + + subject { cluster.connection_status } + + it { is_expected.to be_nil } + + context 'with a cached status' do + before do + stub_reactive_cache(cluster, connection_status: status) + end + + it { is_expected.to eq(status) } + end + end + + describe '#calculate_reactive_cache' do + subject { cluster.calculate_reactive_cache } + + context 'cluster is disabled' do + let(:cluster) { create(:cluster, :disabled) } + + it 'does not populate the cache' do + expect(cluster).not_to receive(:retrieve_connection_status) + + is_expected.to be_nil + end + end + + context 'cluster is enabled' do + let(:cluster) { create(:cluster, :provided_by_user, :group) } + + context 'connection to the cluster is successful' do + before do + stub_kubeclient_discover(cluster.platform.api_url) + end + + it { is_expected.to eq(connection_status: :connected) } + end + + context 'cluster cannot be reached' do + before do + allow(cluster.kubeclient.core_client).to receive(:discover) + .and_raise(SocketError) + end + + it { is_expected.to eq(connection_status: :unreachable) } + end + + context 'cluster cannot be authenticated to' do + before do + allow(cluster.kubeclient.core_client).to receive(:discover) + .and_raise(OpenSSL::X509::CertificateError.new("Certificate error")) + end + + it { is_expected.to eq(connection_status: :authentication_failure) } + end + + describe 'Kubeclient::HttpError' do + let(:error_code) { 403 } + let(:error_message) { "Forbidden" } + + before do + allow(cluster.kubeclient.core_client).to receive(:discover) + .and_raise(Kubeclient::HttpError.new(error_code, error_message, nil)) + end + + it { is_expected.to eq(connection_status: :authentication_failure) } + + context 'generic timeout' do + let(:error_message) { 'Timed out connecting to server'} + + it { is_expected.to eq(connection_status: :unreachable) } + end + + context 'gateway timeout' do + let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1'} + + it { is_expected.to eq(connection_status: :unreachable) } + end + end + + context 'an uncategorised error is raised' do + before do + allow(cluster.kubeclient.core_client).to receive(:discover) + .and_raise(StandardError) + end + + it { is_expected.to eq(connection_status: :unknown_failure) } + + it 'notifies Sentry' do + expect(Gitlab::Sentry).to receive(:track_acceptable_exception) + .with(instance_of(StandardError), hash_including(extra: { cluster_id: cluster.id })) + + subject + end + end + end + end end diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index 42701a5f8d1..7054a70e2ed 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -158,46 +158,6 @@ describe Clusters::ClusterPresenter do it { is_expected.to include(cluster.name) } end - describe '#can_toggle_cluster' do - let(:user) { create(:user) } - - before do - allow(cluster).to receive(:current_user).and_return(user) - end - - subject { described_class.new(cluster).can_toggle_cluster? } - - context 'when user can update' do - before do - allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(true) - end - - context 'when cluster is created' do - before do - allow(cluster).to receive(:created?).and_return(true) - end - - it { is_expected.to eq(true) } - end - - context 'when cluster is not created' do - before do - allow(cluster).to receive(:created?).and_return(false) - end - - it { is_expected.to eq(false) } - end - end - - context 'when user can not update' do - before do - allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(false) - end - - it { is_expected.to eq(false) } - end - end - describe '#cluster_type_description' do subject { described_class.new(cluster).cluster_type_description } -- cgit v1.2.1 From 13f37264349d1c9a016335ba37c6f5bd2d5910df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Mon, 27 May 2019 12:44:02 +0200 Subject: Rephrase specs description for cycle analytics --- spec/serializers/analytics_stage_serializer_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb index 907d1b35e74..5b05c2f2ef3 100644 --- a/spec/serializers/analytics_stage_serializer_spec.rb +++ b/spec/serializers/analytics_stage_serializer_spec.rb @@ -27,7 +27,7 @@ describe AnalyticsStageSerializer do allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0) end - it 'value is nil' do + it 'sets the value to nil' do expect(subject.fetch(:value)).to be_nil end end @@ -37,18 +37,18 @@ describe AnalyticsStageSerializer do allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12) end - it 'value is equal to median' do + it 'sets the value to equal to median' do expect(subject.fetch(:value)).to eq('less than a minute') end end context 'when median is above 1' do before do - allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12) + allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(60.12) end - it 'value is equal to median' do - expect(subject.fetch(:value)).to eq('less than a minute') + it 'sets the value to equal to median' do + expect(subject.fetch(:value)).to eq('1 minute') end end end -- cgit v1.2.1 From a9827e0e18b532fb5cc3f227ce6c6bddaf7a960b Mon Sep 17 00:00:00 2001 From: Jacopo Date: Tue, 29 Jan 2019 19:10:37 +0100 Subject: Removes duplicated members from api/projects/:id/members/all When using the members/all api the same user was returned multiple times when he was a member of the project/group and also of one of the ancestor groups. Now the member is returned only once giving priority to the membership on the project and maintaining the same behaviour of the members UI. --- spec/finders/members_finder_spec.rb | 44 +++++++++++++++++++++++++++++++------ spec/requests/api/members_spec.rb | 13 +++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb index db48f00cd74..83348457caa 100644 --- a/spec/finders/members_finder_spec.rb +++ b/spec/finders/members_finder_spec.rb @@ -1,13 +1,13 @@ require 'spec_helper' describe MembersFinder, '#execute' do - let(:group) { create(:group) } - let(:nested_group) { create(:group, :access_requestable, parent: group) } - let(:project) { create(:project, namespace: nested_group) } - let(:user1) { create(:user) } - let(:user2) { create(:user) } - let(:user3) { create(:user) } - let(:user4) { create(:user) } + set(:group) { create(:group) } + set(:nested_group) { create(:group, :access_requestable, parent: group) } + set(:project) { create(:project, namespace: nested_group) } + set(:user1) { create(:user) } + set(:user2) { create(:user) } + set(:user3) { create(:user) } + set(:user4) { create(:user) } it 'returns members for project and parent groups', :nested_groups do nested_group.request_access(user1) @@ -31,4 +31,34 @@ describe MembersFinder, '#execute' do expect(result.to_a).to match_array([member1, member2, member3]) end + + context 'when include_invited_groups_members == true', :nested_groups do + subject { described_class.new(project, user2).execute(include_invited_groups_members: true) } + + set(:linked_group) { create(:group, :public, :access_requestable) } + set(:nested_linked_group) { create(:group, parent: linked_group) } + set(:linked_group_member) { linked_group.add_developer(user1) } + set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) } + + it 'includes all the invited_groups members including members inherited from ancestor groups', :nested_groups do + create(:project_group_link, project: project, group: nested_linked_group) + + expect(subject).to contain_exactly(linked_group_member, nested_linked_group_member) + end + + it 'includes all the invited_groups members' do + create(:project_group_link, project: project, group: linked_group) + + expect(subject).to contain_exactly(linked_group_member) + end + + it 'excludes group_members not visible to the user' do + create(:project_group_link, project: project, group: linked_group) + private_linked_group = create(:group, :private) + private_linked_group.add_developer(user3) + create(:project_group_link, project: project, group: private_linked_group) + + expect(subject).to contain_exactly(linked_group_member) + end + end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 48869cab4da..55f38079b1f 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -132,6 +132,19 @@ describe API::Members do expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id, nested_user.id, project_user.id, linked_group_user.id] end + it 'returns only one member for each user without returning duplicated members' do + linked_group.add_developer(developer) + + get api("/projects/#{project.id}/members/all", developer) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |u| u['id'] }).to eq [developer.id, maintainer.id, nested_user.id, project_user.id, linked_group_user.id] + expect(json_response.map { |u| u['access_level'] }).to eq [Gitlab::Access::DEVELOPER, Gitlab::Access::OWNER, Gitlab::Access::DEVELOPER, + Gitlab::Access::DEVELOPER, Gitlab::Access::DEVELOPER] + end + it 'finds all group members including inherited members' do get api("/groups/#{nested_group.id}/members/all", developer) -- cgit v1.2.1 From 26878c087a13417620a11375f4091dd008307876 Mon Sep 17 00:00:00 2001 From: Tiger Date: Thu, 23 May 2019 17:11:52 -0500 Subject: Add Kubernetes logs to monitoring UI --- spec/features/admin/admin_browses_logs_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb index 02f50d7e27f..1f83d04d9aa 100644 --- a/spec/features/admin/admin_browses_logs_spec.rb +++ b/spec/features/admin/admin_browses_logs_spec.rb @@ -13,5 +13,6 @@ describe 'Admin browses logs' do expect(page).to have_link 'test.log' expect(page).to have_link 'sidekiq.log' expect(page).to have_link 'repocheck.log' + expect(page).to have_link 'kubernetes.log' end end -- cgit v1.2.1 From 218dd512393b34f9a26adc81ccb99bc969674a5b Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Mon, 27 May 2019 22:30:36 +0000 Subject: Migrate the monitoring dashboard store to vuex This changes the monitoring javascript store from an object based to a vuex one --- spec/javascripts/monitoring/charts/area_spec.js | 14 +- spec/javascripts/monitoring/dashboard_spec.js | 97 +- spec/javascripts/monitoring/helpers.js | 8 + spec/javascripts/monitoring/mock_data.js | 5871 +------------------- .../monitoring/monitoring_store_spec.js | 59 - spec/javascripts/monitoring/store/actions_spec.js | 158 + .../javascripts/monitoring/store/mutations_spec.js | 92 + 7 files changed, 382 insertions(+), 5917 deletions(-) create mode 100644 spec/javascripts/monitoring/helpers.js delete mode 100644 spec/javascripts/monitoring/monitoring_store_spec.js create mode 100644 spec/javascripts/monitoring/store/actions_spec.js create mode 100644 spec/javascripts/monitoring/store/mutations_spec.js (limited to 'spec') diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index 41a6c04efb9..56609665b88 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -2,7 +2,8 @@ import { shallowMount } from '@vue/test-utils'; import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; import Area from '~/monitoring/components/charts/area.vue'; -import MonitoringStore from '~/monitoring/stores/monitoring_store'; +import { createStore } from '~/monitoring/stores'; +import * as types from '~/monitoring/stores/mutation_types'; import MonitoringMock, { deploymentData } from '../mock_data'; describe('Area component', () => { @@ -13,17 +14,18 @@ describe('Area component', () => { let spriteSpy; beforeEach(() => { - const store = new MonitoringStore(); - store.storeMetrics(MonitoringMock.data); - store.storeDeploymentData(deploymentData); + const store = createStore(); - [mockGraphData] = store.groups[0].metrics; + store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data); + store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); + + [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics; areaChart = shallowMount(Area, { propsData: { graphData: mockGraphData, containerWidth: 0, - deploymentData: store.deploymentData, + deploymentData: store.state.monitoringDashboard.deploymentData, }, slots: { default: mockWidgets, diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index e9bd6050d68..58bcd916739 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -2,8 +2,15 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; +import * as types from '~/monitoring/stores/mutation_types'; +import { createStore } from '~/monitoring/stores'; import axios from '~/lib/utils/axios_utils'; -import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; +import { + metricsGroupsAPIResponse, + mockApiEndpoint, + environmentData, + singleGroupResponse, +} from './mock_data'; const propsData = { hasMetrics: false, @@ -30,6 +37,7 @@ export default propsData; describe('Dashboard', () => { let DashboardComponent; let mock; + let store; beforeEach(() => { setFixtures(` @@ -45,6 +53,7 @@ describe('Dashboard', () => { }, }; + store = createStore(); mock = new MockAdapter(axios); DashboardComponent = Vue.extend(Dashboard); }); @@ -58,10 +67,11 @@ describe('Dashboard', () => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, showTimeWindowDropdown: false }, + store, }); expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); - expect(component.state).toEqual('gettingStarted'); + expect(component.emptyState).toEqual('gettingStarted'); }); }); @@ -74,10 +84,11 @@ describe('Dashboard', () => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false }, + store, }); Vue.nextTick(() => { - expect(component.state).toEqual('loading'); + expect(component.emptyState).toEqual('loading'); done(); }); }); @@ -91,6 +102,7 @@ describe('Dashboard', () => { showLegend: false, showTimeWindowDropdown: false, }, + store, }); setTimeout(() => { @@ -110,6 +122,7 @@ describe('Dashboard', () => { showPanels: false, showTimeWindowDropdown: false, }, + store, }); setTimeout(() => { @@ -129,16 +142,24 @@ describe('Dashboard', () => { showPanels: false, showTimeWindowDropdown: false, }, + store, }); - component.store.storeEnvironmentsData(environmentData); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + singleGroupResponse, + ); setTimeout(() => { const dropdownMenuEnvironments = component.$el.querySelectorAll( '.js-environments-dropdown .dropdown-item', ); - expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length); + expect(dropdownMenuEnvironments.length).toEqual(component.environments.length); done(); }); }); @@ -152,18 +173,29 @@ describe('Dashboard', () => { showPanels: false, showTimeWindowDropdown: false, }, + store, }); - component.store.storeEnvironmentsData([]); + component.$store.commit( + `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`, + '/environments', + ); + component.$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, []); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + singleGroupResponse, + ); - setTimeout(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item', - ); + Vue.nextTick() + .then(() => { + const dropdownMenuEnvironments = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item', + ); - expect(dropdownMenuEnvironments.length).toEqual(0); - done(); - }); + expect(dropdownMenuEnvironments.length).toEqual(0); + done(); + }) + .catch(done.fail); }); it('renders the environments dropdown with a single active element', done => { @@ -175,19 +207,32 @@ describe('Dashboard', () => { showPanels: false, showTimeWindowDropdown: false, }, + store, }); - component.store.storeEnvironmentsData(environmentData); + component.$store.commit( + `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`, + '/environments', + ); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + singleGroupResponse, + ); - setTimeout(() => { - const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item[active="true"]', - ); + Vue.nextTick() + .then(() => { + const dropdownItems = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item[active="true"]', + ); - expect(dropdownItems.length).toEqual(1); - expect(dropdownItems[0].textContent.trim()).toEqual(component.currentEnvironmentName); - done(); - }); + expect(dropdownItems.length).toEqual(1); + done(); + }) + .catch(done.fail); }); it('hides the dropdown', done => { @@ -200,6 +245,7 @@ describe('Dashboard', () => { environmentsEndpoint: '', showTimeWindowDropdown: false, }, + store, }); Vue.nextTick(() => { @@ -219,6 +265,7 @@ describe('Dashboard', () => { showPanels: false, showTimeWindowDropdown: false, }, + store, }); setTimeout(() => { @@ -239,6 +286,7 @@ describe('Dashboard', () => { showPanels: false, showTimeWindowDropdown: true, }, + store, }); const numberOfTimeWindows = Object.keys(timeWindows).length; @@ -261,6 +309,7 @@ describe('Dashboard', () => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + store, }); setTimeout(() => { @@ -281,6 +330,7 @@ describe('Dashboard', () => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + store, }); Vue.nextTick(() => { @@ -310,6 +360,7 @@ describe('Dashboard', () => { showPanels: false, showTimeWindowDropdown: false, }, + store, }); expect(component.elWidth).toEqual(0); @@ -352,6 +403,7 @@ describe('Dashboard', () => { showTimeWindowDropdown: false, externalDashboardPath: '/mockPath', }, + store, }); }); @@ -377,6 +429,7 @@ describe('Dashboard', () => { showTimeWindowDropdown: false, externalDashboardPath: '', }, + store, }); }); diff --git a/spec/javascripts/monitoring/helpers.js b/spec/javascripts/monitoring/helpers.js new file mode 100644 index 00000000000..672e3b948c4 --- /dev/null +++ b/spec/javascripts/monitoring/helpers.js @@ -0,0 +1,8 @@ +// eslint-disable-next-line import/prefer-default-export +export const resetStore = store => { + store.replaceState({ + showEmptyState: true, + emptyState: 'loading', + groups: [], + }); +}; diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 6d4ef960c1a..d9d8cb66749 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -685,6 +685,47 @@ export const metricsGroupsAPIResponse = { last_update: '2017-05-25T13:18:34.949Z', }; +export const singleGroupResponse = [ + { + group: 'System metrics (Kubernetes)', + priority: 5, + metrics: [ + { + title: 'Memory Usage (Total)', + weight: 0, + y_label: 'Total Memory Used', + queries: [ + { + query_range: + 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^production-(.*)",namespace="autodevops-deploy-33"}) by (job)) without (job) /1024/1024/1024', + unit: 'GB', + label: 'Total', + result: [ + { + metric: {}, + values: [ + [1558453960.079, '0.0357666015625'], + [1558454020.079, '0.035675048828125'], + [1558454080.079, '0.035152435302734375'], + [1558454140.079, '0.035221099853515625'], + [1558454200.079, '0.0352325439453125'], + [1558454260.079, '0.03479766845703125'], + [1558454320.079, '0.034793853759765625'], + [1558454380.079, '0.034931182861328125'], + [1558454440.079, '0.034816741943359375'], + [1558454500.079, '0.034816741943359375'], + [1558454560.079, '0.034816741943359375'], + ], + }, + ], + }, + ], + id: 15, + }, + ], + }, +]; + export default metricsGroupsAPIResponse; export const deploymentData = [ @@ -738,5836 +779,6 @@ export const statePaths = { documentationPath: '/help/administration/monitoring/prometheus/index.md', }; -export const singleRowMetricsMultipleSeries = [ - { - title: 'Multiple Time Series', - weight: 1, - y_label: 'Request Rates', - queries: [ - { - query_range: - 'sum(rate(nginx_responses_total{environment="production"}[2m])) by (status_code)', - label: 'Requests', - unit: 'Req/sec', - result: [ - { - metric: { - status_code: '1xx', - }, - values: [ - { - time: '2017-08-27T11:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T11:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T12:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T13:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T14:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T15:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T16:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T17:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:01:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:02:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:03:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:04:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:05:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:06:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:07:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:08:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:09:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:10:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:11:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:12:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:13:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:14:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:15:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:16:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:17:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:18:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:19:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:20:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:21:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:22:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:23:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:24:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:25:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:26:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:27:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:28:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:29:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:30:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:31:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:32:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:33:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:34:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:35:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:36:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:37:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:38:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:39:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:40:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:41:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:42:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:43:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:44:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:45:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:46:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:47:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:48:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:49:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:50:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:51:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:52:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:53:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:54:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:55:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:56:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:57:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:58:51.462Z', - value: '0', - }, - { - time: '2017-08-27T18:59:51.462Z', - value: '0', - }, - { - time: '2017-08-27T19:00:51.462Z', - value: '0', - }, - { - time: '2017-08-27T19:01:51.462Z', - value: '0', - }, - ], - }, - { - metric: { - status_code: '2xx', - }, - values: [ - { - time: '2017-08-27T11:01:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:02:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T11:03:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:04:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:05:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:06:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:07:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:08:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:09:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:10:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:11:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:12:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:13:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:14:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:15:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:16:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:17:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:18:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:19:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:20:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:21:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:22:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:23:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:24:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:25:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:26:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:27:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:28:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:29:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:30:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:31:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:32:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:33:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:34:51.462Z', - value: '1.333320635041571', - }, - { - time: '2017-08-27T11:35:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:36:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:37:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:38:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:39:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:40:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:41:51.462Z', - value: '1.3333587306424883', - }, - { - time: '2017-08-27T11:42:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:43:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:44:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:45:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:46:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:47:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:48:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:49:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T11:50:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:51:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:52:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:53:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:54:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:55:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:56:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:57:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T11:58:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T11:59:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:00:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:01:51.462Z', - value: '1.3333460318669703', - }, - { - time: '2017-08-27T12:02:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:03:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:04:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:05:51.462Z', - value: '1.31427319739812', - }, - { - time: '2017-08-27T12:06:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:07:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:08:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:09:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:10:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:11:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:12:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:13:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:14:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:15:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:16:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:17:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:18:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:19:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:20:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:21:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:22:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:23:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:24:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:25:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:26:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:27:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:28:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:29:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:30:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:31:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:32:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:33:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:34:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:35:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:36:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:37:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:38:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:39:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:40:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:41:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:42:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:43:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:44:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:45:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:46:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:47:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:48:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:49:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:50:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:51:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:52:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:53:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:54:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T12:55:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:56:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:57:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T12:58:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T12:59:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:00:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:01:51.462Z', - value: '1.295225759754669', - }, - { - time: '2017-08-27T13:02:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:03:51.462Z', - value: '1.2952627669098458', - }, - { - time: '2017-08-27T13:04:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:05:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:06:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:07:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:08:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:09:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:10:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:11:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:12:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:13:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:14:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:15:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T13:16:51.462Z', - value: '1.3333587306424883', - }, - { - time: '2017-08-27T13:17:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:18:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:19:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:20:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:21:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:22:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:23:51.462Z', - value: '1.276190476190476', - }, - { - time: '2017-08-27T13:24:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T13:25:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:26:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:27:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:28:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:29:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:30:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:31:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:32:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:33:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:34:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:35:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:36:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:37:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:38:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:39:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:40:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:41:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:42:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:43:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:44:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:45:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:46:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T13:47:51.462Z', - value: '1.276190476190476', - }, - { - time: '2017-08-27T13:48:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:49:51.462Z', - value: '1.295225759754669', - }, - { - time: '2017-08-27T13:50:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:51:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:52:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:53:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:54:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:55:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:56:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T13:57:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T13:58:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T13:59:51.462Z', - value: '1.295225759754669', - }, - { - time: '2017-08-27T14:00:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:01:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:02:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:03:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:04:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:05:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:06:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:07:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:08:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:09:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:10:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:11:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:12:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:13:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:14:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:15:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:16:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:17:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:18:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:19:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:20:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:21:51.462Z', - value: '1.3333079369916765', - }, - { - time: '2017-08-27T14:22:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:23:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:24:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:25:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:26:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:27:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:28:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:29:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:30:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:31:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:32:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:33:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T14:34:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:35:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:36:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:37:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:38:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:39:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:40:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:41:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:42:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:43:51.462Z', - value: '1.276190476190476', - }, - { - time: '2017-08-27T14:44:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T14:45:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:46:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:47:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:48:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:49:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:50:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:51:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:52:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:53:51.462Z', - value: '1.333320635041571', - }, - { - time: '2017-08-27T14:54:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:55:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T14:56:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:57:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T14:58:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T14:59:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:00:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:01:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:02:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:03:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:04:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T15:05:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:06:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:07:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:08:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:09:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:10:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:11:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:12:51.462Z', - value: '1.31427319739812', - }, - { - time: '2017-08-27T15:13:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:14:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:15:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:16:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:17:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:18:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:19:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:20:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:21:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:22:51.462Z', - value: '1.3333460318669703', - }, - { - time: '2017-08-27T15:23:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:24:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:25:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:26:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:27:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:28:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:29:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:30:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:31:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:32:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:33:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:34:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:35:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:36:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:37:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:38:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:39:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:40:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:41:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:42:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:43:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:44:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:45:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:46:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:47:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:48:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:49:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T15:50:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:51:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:52:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:53:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:54:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:55:51.462Z', - value: '1.3333587306424883', - }, - { - time: '2017-08-27T15:56:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T15:57:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:58:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T15:59:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:00:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:01:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:02:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:03:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:04:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:05:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:06:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:07:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:08:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:09:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:10:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:11:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:12:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:13:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:14:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:15:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:16:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:17:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:18:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:19:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:20:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:21:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:22:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:23:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:24:51.462Z', - value: '1.295225759754669', - }, - { - time: '2017-08-27T16:25:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:26:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:27:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:28:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:29:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:30:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:31:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:32:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:33:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:34:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:35:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:36:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:37:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:38:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:39:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:40:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:41:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:42:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:43:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:44:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:45:51.462Z', - value: '1.3142982314117277', - }, - { - time: '2017-08-27T16:46:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:47:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:48:51.462Z', - value: '1.333320635041571', - }, - { - time: '2017-08-27T16:49:51.462Z', - value: '1.31427319739812', - }, - { - time: '2017-08-27T16:50:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:51:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:52:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:53:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:54:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:55:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T16:56:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:57:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T16:58:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T16:59:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:00:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:01:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:02:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:03:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:04:51.462Z', - value: '1.2952504309564854', - }, - { - time: '2017-08-27T17:05:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:06:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:07:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:08:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:09:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:10:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:11:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:12:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:13:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:14:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:15:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:16:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:17:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:18:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:19:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:20:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:21:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:22:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:23:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:24:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:25:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:26:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:27:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:28:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:29:51.462Z', - value: '1.295225759754669', - }, - { - time: '2017-08-27T17:30:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:31:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:32:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:33:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:34:51.462Z', - value: '1.295225759754669', - }, - { - time: '2017-08-27T17:35:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:36:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:37:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:38:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:39:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:40:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:41:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:42:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:43:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:44:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:45:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:46:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:47:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:48:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:49:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:50:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:51:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:52:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:53:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:54:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:55:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T17:56:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:57:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T17:58:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T17:59:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T18:00:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:01:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:02:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:03:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:04:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:05:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:06:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:07:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:08:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:09:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:10:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:11:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:12:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T18:13:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:14:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:15:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:16:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:17:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:18:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:19:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:20:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:21:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:22:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:23:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:24:51.462Z', - value: '1.2571428571428571', - }, - { - time: '2017-08-27T18:25:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:26:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:27:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:28:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:29:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:30:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:31:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:32:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:33:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:34:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:35:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:36:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:37:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T18:38:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:39:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:40:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:41:51.462Z', - value: '1.580952380952381', - }, - { - time: '2017-08-27T18:42:51.462Z', - value: '1.7333333333333334', - }, - { - time: '2017-08-27T18:43:51.462Z', - value: '2.057142857142857', - }, - { - time: '2017-08-27T18:44:51.462Z', - value: '2.1904761904761902', - }, - { - time: '2017-08-27T18:45:51.462Z', - value: '1.8285714285714287', - }, - { - time: '2017-08-27T18:46:51.462Z', - value: '2.1142857142857143', - }, - { - time: '2017-08-27T18:47:51.462Z', - value: '1.619047619047619', - }, - { - time: '2017-08-27T18:48:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:49:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:50:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T18:51:51.462Z', - value: '1.2952504309564854', - }, - { - time: '2017-08-27T18:52:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:53:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:54:51.462Z', - value: '1.3333333333333333', - }, - { - time: '2017-08-27T18:55:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:56:51.462Z', - value: '1.314285714285714', - }, - { - time: '2017-08-27T18:57:51.462Z', - value: '1.295238095238095', - }, - { - time: '2017-08-27T18:58:51.462Z', - value: '1.7142857142857142', - }, - { - time: '2017-08-27T18:59:51.462Z', - value: '1.7333333333333334', - }, - { - time: '2017-08-27T19:00:51.462Z', - value: '1.3904761904761904', - }, - { - time: '2017-08-27T19:01:51.462Z', - value: '1.5047619047619047', - }, - ], - }, - ], - when: [ - { - value: 'hundred(s)', - color: 'green', - }, - ], - }, - ], - }, - { - title: 'Throughput', - weight: 1, - y_label: 'Requests / Sec', - queries: [ - { - query_range: - "sum(rate(nginx_requests_total{server_zone!='*', server_zone!='_', container_name!='POD',environment='production'}[2m]))", - label: 'Total', - unit: 'req / sec', - result: [ - { - metric: {}, - values: [ - { - time: '2017-08-27T11:01:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:02:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T11:03:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:04:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:05:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:06:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:07:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:08:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:09:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:10:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:11:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:12:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:13:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:14:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:15:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:16:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:17:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:18:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:19:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:20:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:21:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:22:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:23:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:24:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:25:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:26:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:27:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:28:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:29:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:30:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:31:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:32:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:33:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:34:51.462Z', - value: '0.4952333787297264', - }, - { - time: '2017-08-27T11:35:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:36:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:37:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:38:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:39:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:40:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:41:51.462Z', - value: '0.49524752852435283', - }, - { - time: '2017-08-27T11:42:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:43:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:44:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:45:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:46:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:47:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:48:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:49:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T11:50:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:51:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:52:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:53:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:54:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:55:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:56:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:57:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T11:58:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T11:59:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:00:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:01:51.462Z', - value: '0.49524281183630325', - }, - { - time: '2017-08-27T12:02:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:03:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:04:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:05:51.462Z', - value: '0.4857096599080009', - }, - { - time: '2017-08-27T12:06:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:07:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:08:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:09:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:10:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:11:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:12:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:13:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:14:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:15:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:16:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:17:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:18:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:19:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:20:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:21:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:22:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:23:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:24:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:25:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:26:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:27:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:28:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:29:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:30:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:31:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:32:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:33:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:34:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:35:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:36:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:37:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:38:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:39:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:40:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:41:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:42:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:43:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:44:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:45:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:46:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:47:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:48:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:49:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:50:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:51:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:52:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:53:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:54:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T12:55:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:56:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:57:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T12:58:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T12:59:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:00:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:01:51.462Z', - value: '0.4761859410862754', - }, - { - time: '2017-08-27T13:02:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:03:51.462Z', - value: '0.4761995466580315', - }, - { - time: '2017-08-27T13:04:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:05:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:06:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:07:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:08:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:09:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:10:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:11:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:12:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:13:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:14:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:15:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T13:16:51.462Z', - value: '0.49524752852435283', - }, - { - time: '2017-08-27T13:17:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:18:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:19:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:20:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:21:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:22:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:23:51.462Z', - value: '0.4666666666666667', - }, - { - time: '2017-08-27T13:24:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T13:25:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:26:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:27:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:28:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:29:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:30:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:31:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:32:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:33:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:34:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:35:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:36:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:37:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:38:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:39:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:40:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:41:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:42:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:43:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:44:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:45:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:46:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T13:47:51.462Z', - value: '0.4666666666666667', - }, - { - time: '2017-08-27T13:48:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:49:51.462Z', - value: '0.4761859410862754', - }, - { - time: '2017-08-27T13:50:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:51:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:52:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:53:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:54:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:55:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:56:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T13:57:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T13:58:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T13:59:51.462Z', - value: '0.4761859410862754', - }, - { - time: '2017-08-27T14:00:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:01:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:02:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:03:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:04:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:05:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:06:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:07:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:08:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:09:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:10:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:11:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:12:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:13:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:14:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:15:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:16:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:17:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:18:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:19:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:20:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:21:51.462Z', - value: '0.4952286623111941', - }, - { - time: '2017-08-27T14:22:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:23:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:24:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:25:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:26:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:27:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:28:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:29:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:30:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:31:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:32:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:33:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T14:34:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:35:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:36:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:37:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:38:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:39:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:40:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:41:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:42:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:43:51.462Z', - value: '0.4666666666666667', - }, - { - time: '2017-08-27T14:44:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T14:45:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:46:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:47:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:48:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:49:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:50:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:51:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:52:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:53:51.462Z', - value: '0.4952333787297264', - }, - { - time: '2017-08-27T14:54:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:55:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T14:56:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:57:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T14:58:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T14:59:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:00:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:01:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:02:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:03:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:04:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T15:05:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:06:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:07:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:08:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:09:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:10:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:11:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:12:51.462Z', - value: '0.4857096599080009', - }, - { - time: '2017-08-27T15:13:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:14:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:15:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:16:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:17:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:18:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:19:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:20:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:21:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:22:51.462Z', - value: '0.49524281183630325', - }, - { - time: '2017-08-27T15:23:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:24:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:25:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:26:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:27:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:28:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:29:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:30:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:31:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:32:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:33:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:34:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:35:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:36:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:37:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:38:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:39:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:40:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:41:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:42:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:43:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:44:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:45:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:46:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:47:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:48:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:49:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T15:50:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:51:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:52:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:53:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:54:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:55:51.462Z', - value: '0.49524752852435283', - }, - { - time: '2017-08-27T15:56:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T15:57:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:58:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T15:59:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:00:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:01:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:02:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:03:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:04:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:05:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:06:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:07:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:08:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:09:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:10:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:11:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:12:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:13:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:14:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:15:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:16:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:17:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:18:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:19:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:20:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:21:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:22:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:23:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:24:51.462Z', - value: '0.4761859410862754', - }, - { - time: '2017-08-27T16:25:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:26:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:27:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:28:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:29:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:30:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:31:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:32:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:33:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:34:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:35:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:36:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:37:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:38:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:39:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:40:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:41:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:42:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:43:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:44:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:45:51.462Z', - value: '0.485718911608682', - }, - { - time: '2017-08-27T16:46:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:47:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:48:51.462Z', - value: '0.4952333787297264', - }, - { - time: '2017-08-27T16:49:51.462Z', - value: '0.4857096599080009', - }, - { - time: '2017-08-27T16:50:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:51:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:52:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:53:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:54:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:55:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T16:56:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:57:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T16:58:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T16:59:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:00:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:01:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:02:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:03:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:04:51.462Z', - value: '0.47619501138106085', - }, - { - time: '2017-08-27T17:05:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:06:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:07:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:08:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:09:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:10:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:11:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:12:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:13:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:14:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:15:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:16:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:17:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:18:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:19:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:20:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:21:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:22:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:23:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:24:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:25:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:26:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:27:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:28:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:29:51.462Z', - value: '0.4761859410862754', - }, - { - time: '2017-08-27T17:30:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:31:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:32:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:33:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:34:51.462Z', - value: '0.4761859410862754', - }, - { - time: '2017-08-27T17:35:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:36:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:37:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:38:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:39:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:40:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:41:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:42:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:43:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:44:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:45:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:46:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:47:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:48:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:49:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:50:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:51:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:52:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:53:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:54:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:55:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T17:56:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:57:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T17:58:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T17:59:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T18:00:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:01:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:02:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:03:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:04:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:05:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:06:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:07:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:08:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:09:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:10:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:11:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:12:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T18:13:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:14:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:15:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:16:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:17:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:18:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:19:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:20:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:21:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:22:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:23:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:24:51.462Z', - value: '0.45714285714285713', - }, - { - time: '2017-08-27T18:25:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:26:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:27:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:28:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:29:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:30:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:31:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:32:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:33:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:34:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:35:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:36:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:37:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T18:38:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:39:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:40:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:41:51.462Z', - value: '0.6190476190476191', - }, - { - time: '2017-08-27T18:42:51.462Z', - value: '0.6952380952380952', - }, - { - time: '2017-08-27T18:43:51.462Z', - value: '0.857142857142857', - }, - { - time: '2017-08-27T18:44:51.462Z', - value: '0.9238095238095239', - }, - { - time: '2017-08-27T18:45:51.462Z', - value: '0.7428571428571429', - }, - { - time: '2017-08-27T18:46:51.462Z', - value: '0.8857142857142857', - }, - { - time: '2017-08-27T18:47:51.462Z', - value: '0.638095238095238', - }, - { - time: '2017-08-27T18:48:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:49:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:50:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T18:51:51.462Z', - value: '0.47619501138106085', - }, - { - time: '2017-08-27T18:52:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:53:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:54:51.462Z', - value: '0.4952380952380952', - }, - { - time: '2017-08-27T18:55:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:56:51.462Z', - value: '0.4857142857142857', - }, - { - time: '2017-08-27T18:57:51.462Z', - value: '0.47619047619047616', - }, - { - time: '2017-08-27T18:58:51.462Z', - value: '0.6857142857142856', - }, - { - time: '2017-08-27T18:59:51.462Z', - value: '0.6952380952380952', - }, - { - time: '2017-08-27T19:00:51.462Z', - value: '0.5238095238095237', - }, - { - time: '2017-08-27T19:01:51.462Z', - value: '0.5904761904761905', - }, - ], - }, - ], - }, - ], - }, -]; - export const queryWithoutData = { title: 'HTTP Error rate', weight: 10, diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js deleted file mode 100644 index 5bf6937c92e..00000000000 --- a/spec/javascripts/monitoring/monitoring_store_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import MonitoringStore from '~/monitoring/stores/monitoring_store'; -import MonitoringMock, { deploymentData, environmentData } from './mock_data'; - -describe('MonitoringStore', () => { - const store = new MonitoringStore(); - store.storeMetrics(MonitoringMock.data); - - it('contains two groups that contains, one of which has two queries sorted by priority', () => { - expect(store.groups).toBeDefined(); - expect(store.groups.length).toEqual(2); - expect(store.groups[0].metrics.length).toEqual(2); - }); - - it('gets the metrics count for every group', () => { - expect(store.getMetricsCount()).toEqual(3); - }); - - it('contains deployment data', () => { - store.storeDeploymentData(deploymentData); - - expect(store.deploymentData).toBeDefined(); - expect(store.deploymentData.length).toEqual(3); - expect(typeof store.deploymentData[0]).toEqual('object'); - }); - - it('only stores environment data that contains deployments', () => { - store.storeEnvironmentsData(environmentData); - - expect(store.environmentsData.length).toEqual(2); - }); - - it('removes the data if all the values from a query are not defined', () => { - expect(store.groups[1].metrics[0].queries[0].result.length).toEqual(0); - }); - - it('assigns queries a metric id', () => { - expect(store.groups[1].metrics[0].queries[0].metricId).toEqual('100'); - }); - - it('assigns metric id of null if metric has no id', () => { - const noId = MonitoringMock.data.map(group => ({ - ...group, - ...{ - metrics: group.metrics.map(metric => { - const { id, ...metricWithoutId } = metric; - - return metricWithoutId; - }), - }, - })); - store.storeMetrics(noId); - - store.groups.forEach(group => { - group.metrics.forEach(metric => { - expect(metric.queries.every(query => query.metricId === null)).toBe(true); - }); - }); - }); -}); diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js new file mode 100644 index 00000000000..a848cd24fe3 --- /dev/null +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -0,0 +1,158 @@ +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; +import store from '~/monitoring/stores'; +import * as types from '~/monitoring/stores/mutation_types'; +import { + fetchDeploymentsData, + fetchEnvironmentsData, + requestMetricsData, + setEndpoints, + setGettingStartedEmptyState, +} from '~/monitoring/stores/actions'; +import storeState from '~/monitoring/stores/state'; +import testAction from 'spec/helpers/vuex_action_helper'; +import { resetStore } from '../helpers'; +import { deploymentData, environmentData } from '../mock_data'; + +describe('Monitoring store actions', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + resetStore(store); + mock.restore(); + }); + + describe('requestMetricsData', () => { + it('sets emptyState to loading', () => { + const commit = jasmine.createSpy(); + const { state } = store; + + requestMetricsData({ state, commit }); + + expect(commit).toHaveBeenCalledWith(types.REQUEST_METRICS_DATA); + }); + }); + + describe('fetchDeploymentsData', () => { + it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => { + const dispatch = jasmine.createSpy(); + const { state } = store; + state.deploymentEndpoint = '/success'; + + mock.onGet(state.deploymentEndpoint).reply(200, { + deployments: deploymentData, + }); + + fetchDeploymentsData({ state, dispatch }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataSuccess', deploymentData); + done(); + }) + .catch(done.fail); + }); + + it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => { + const dispatch = jasmine.createSpy(); + const { state } = store; + state.deploymentEndpoint = '/error'; + + mock.onGet(state.deploymentEndpoint).reply(500); + + fetchDeploymentsData({ state, dispatch }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataFailure'); + done(); + }) + .catch(done.fail); + }); + }); + + describe('fetchEnvironmentsData', () => { + it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', done => { + const dispatch = jasmine.createSpy(); + const { state } = store; + state.environmentsEndpoint = '/success'; + + mock.onGet(state.environmentsEndpoint).reply(200, { + environments: environmentData, + }); + + fetchEnvironmentsData({ state, dispatch }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataSuccess', environmentData); + done(); + }) + .catch(done.fail); + }); + + it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', done => { + const dispatch = jasmine.createSpy(); + const { state } = store; + state.environmentsEndpoint = '/error'; + + mock.onGet(state.environmentsEndpoint).reply(500); + + fetchEnvironmentsData({ state, dispatch }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure'); + done(); + }) + .catch(done.fail); + }); + }); + + describe('Set endpoints', () => { + let mockedState; + + beforeEach(() => { + mockedState = storeState(); + }); + + it('should commit SET_ENDPOINTS mutation', done => { + testAction( + setEndpoints, + { + metricsEndpoint: 'additional_metrics.json', + deploymentsEndpoint: 'deployments.json', + environmentsEndpoint: 'deployments.json', + }, + mockedState, + [ + { + type: types.SET_ENDPOINTS, + payload: { + metricsEndpoint: 'additional_metrics.json', + deploymentsEndpoint: 'deployments.json', + environmentsEndpoint: 'deployments.json', + }, + }, + ], + [], + done, + ); + }); + }); + + describe('Set empty states', () => { + let mockedState; + + beforeEach(() => { + mockedState = storeState(); + }); + + it('should commit SET_METRICS_ENDPOINT mutation', done => { + testAction( + setGettingStartedEmptyState, + null, + mockedState, + [{ type: types.SET_GETTING_STARTED_EMPTY_STATE }], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js new file mode 100644 index 00000000000..882ee1dec14 --- /dev/null +++ b/spec/javascripts/monitoring/store/mutations_spec.js @@ -0,0 +1,92 @@ +import mutations from '~/monitoring/stores/mutations'; +import * as types from '~/monitoring/stores/mutation_types'; +import state from '~/monitoring/stores/state'; +import { metricsGroupsAPIResponse, deploymentData } from '../mock_data'; + +describe('Monitoring mutations', () => { + let stateCopy; + + beforeEach(() => { + stateCopy = state(); + }); + + describe(types.RECEIVE_METRICS_DATA_SUCCESS, () => { + beforeEach(() => { + stateCopy.groups = []; + const groups = metricsGroupsAPIResponse.data; + + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); + }); + + it('normalizes values', () => { + const expectedTimestamp = '2017-05-25T08:22:34.925Z'; + const expectedValue = 0.0010794445585559514; + const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0]; + + expect(timestamp).toEqual(expectedTimestamp); + expect(value).toEqual(expectedValue); + }); + + it('contains two groups that contains, one of which has two queries sorted by priority', () => { + expect(stateCopy.groups).toBeDefined(); + expect(stateCopy.groups.length).toEqual(2); + expect(stateCopy.groups[0].metrics.length).toEqual(2); + }); + + it('assigns queries a metric id', () => { + expect(stateCopy.groups[1].metrics[0].queries[0].metricId).toEqual('100'); + }); + + it('removes the data if all the values from a query are not defined', () => { + expect(stateCopy.groups[1].metrics[0].queries[0].result.length).toEqual(0); + }); + + it('assigns metric id of null if metric has no id', () => { + stateCopy.groups = []; + const groups = metricsGroupsAPIResponse.data; + const noId = groups.map(group => ({ + ...group, + ...{ + metrics: group.metrics.map(metric => { + const { id, ...metricWithoutId } = metric; + + return metricWithoutId; + }), + }, + })); + + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId); + + stateCopy.groups.forEach(group => { + group.metrics.forEach(metric => { + expect(metric.queries.every(query => query.metricId === null)).toBe(true); + }); + }); + }); + }); + + describe(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, () => { + it('stores the deployment data', () => { + stateCopy.deploymentData = []; + mutations[types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](stateCopy, deploymentData); + + expect(stateCopy.deploymentData).toBeDefined(); + expect(stateCopy.deploymentData.length).toEqual(3); + expect(typeof stateCopy.deploymentData[0]).toEqual('object'); + }); + }); + + describe('SET_ENDPOINTS', () => { + it('should set all the endpoints', () => { + mutations[types.SET_ENDPOINTS](stateCopy, { + metricsEndpoint: 'additional_metrics.json', + environmentsEndpoint: 'environments.json', + deploymentsEndpoint: 'deployments.json', + }); + + expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json'); + expect(stateCopy.environmentsEndpoint).toEqual('environments.json'); + expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json'); + }); + }); +}); -- cgit v1.2.1 From 7e05f3b78b6e2e892bc309105711316dc17a109d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 27 May 2019 13:44:39 +0700 Subject: Use source ref for pipeline webhook When user uses Pipelines for merge requests, the pipeline is a run on a merge request ref instead of branch ref. However, we should send source ref as a webhook in order to respect the original behavior. --- spec/lib/gitlab/data_builder/pipeline_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index 9ef987a0826..1f36fd5c6ef 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -50,5 +50,14 @@ describe Gitlab::DataBuilder::Pipeline do it { expect(attributes[:variables]).to be_a(Array) } it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) } end + + context 'when pipeline is a detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.first } + + it 'returns a source ref' do + expect(attributes[:ref]).to eq(merge_request.source_branch) + end + end end end -- cgit v1.2.1 From 4687ff7c9be789341e82a6440234fce43f30b5be Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Tue, 28 May 2019 04:47:34 +0000 Subject: Store Let's Encrypt private key in settings Storing this key in secrets.yml was a bad idea, it would require users using HA setups to manually replicate secrets across nodes during update, it also needed support from omnibus package * Revert "Generate Let's Encrypt private key" This reverts commit 444959bfa0b79e827a2a1a7a314acac19390f976. * Add Let's Encrypt private key to settings as encrypted attribute * Generate Let's Encrypt private key in database migration --- spec/initializers/secret_token_spec.rb | 11 ----------- spec/lib/gitlab/lets_encrypt/client_spec.rb | 4 +++- .../generate_lets_encrypt_private_key_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 spec/migrations/generate_lets_encrypt_private_key_spec.rb (limited to 'spec') diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb index 77bc28a6b07..726ce07a2d1 100644 --- a/spec/initializers/secret_token_spec.rb +++ b/spec/initializers/secret_token_spec.rb @@ -45,21 +45,11 @@ describe 'create_tokens' do expect(keys).to all(match(RSA_KEY)) end - it "generates private key for Let's Encrypt" do - create_tokens - - keys = secrets.values_at(:lets_encrypt_private_key) - - expect(keys.uniq).to eq(keys) - expect(keys).to all(match(RSA_KEY)) - end - it 'warns about the secrets to add to secrets.yml' do expect(self).to receive(:warn_missing_secret).with('secret_key_base') expect(self).to receive(:warn_missing_secret).with('otp_key_base') expect(self).to receive(:warn_missing_secret).with('db_key_base') expect(self).to receive(:warn_missing_secret).with('openid_connect_signing_key') - expect(self).to receive(:warn_missing_secret).with('lets_encrypt_private_key') create_tokens end @@ -88,7 +78,6 @@ describe 'create_tokens' do before do secrets.db_key_base = 'db_key_base' secrets.openid_connect_signing_key = 'openid_connect_signing_key' - secrets.lets_encrypt_private_key = 'lets_encrypt_private_key' allow(File).to receive(:exist?).with('.secret').and_return(true) allow(File).to receive(:read).with('.secret').and_return('file_key') diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb index 16a16acfd25..d63a2fbee04 100644 --- a/spec/lib/gitlab/lets_encrypt/client_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -5,12 +5,14 @@ require 'spec_helper' describe ::Gitlab::LetsEncrypt::Client do include LetsEncryptHelpers + set(:private_key) { OpenSSL::PKey::RSA.new(4096).to_pem } let(:client) { described_class.new } before do stub_application_setting( lets_encrypt_notification_email: 'myemail@test.example.com', - lets_encrypt_terms_of_service_accepted: true + lets_encrypt_terms_of_service_accepted: true, + lets_encrypt_private_key: private_key ) end diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb new file mode 100644 index 00000000000..f47cc0c36ef --- /dev/null +++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_private_key.rb') + +describe GenerateLetsEncryptPrivateKey, :migration do + describe '#up' do + let(:applications_settings) { table(:applications_settings) } + + it 'generates RSA private key and saves it in application settings' do + application_setting = described_class::ApplicationSetting.create! + + described_class.new.up + application_setting.reload + + expect(application_setting.lets_encrypt_private_key).to be_present + expect do + OpenSSL::PKey::RSA.new(application_setting.lets_encrypt_private_key) + end.not_to raise_error + end + end +end -- cgit v1.2.1 From e0c5deaf1b06787f7af9f376975925335dab20f7 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Tue, 28 May 2019 06:41:34 +0000 Subject: Add expand/collapse button Add ability to expand/collapse operation settings --- .../components/external_dashboard_spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'spec') diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js index de1dd219fe0..4d35d35965c 100644 --- a/spec/frontend/operation_settings/components/external_dashboard_spec.js +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -2,6 +2,9 @@ import { shallowMount } from '@vue/test-utils'; import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui'; import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue'; import { TEST_HOST } from 'helpers/test_constants'; +import initSettingsPanels from '~/settings_panels'; + +jest.mock('~/settings_panels'); describe('operation settings external dashboard component', () => { let wrapper; @@ -21,6 +24,18 @@ describe('operation settings external dashboard component', () => { expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard'); }); + describe('expand/collapse button', () => { + it('is properly instantiated as a settings panel', () => { + expect(initSettingsPanels).toHaveBeenCalled(); + }); + + it('defaults to collapsed state', () => { + const button = wrapper.find(GlButton); + + expect(button.text()).toBe('Expand'); + }); + }); + describe('sub-header', () => { let subHeader; -- cgit v1.2.1 From c92db3db71dc261d547bb08873fdafeff9be98dc Mon Sep 17 00:00:00 2001 From: Igor Date: Tue, 28 May 2019 07:12:29 +0000 Subject: Fix removing empty lines via suggestions Before this fix, a suggestion which just removes an empty line wasn't appliable --- spec/services/suggestions/apply_service_spec.rb | 68 +++++++++++++++++++++--- spec/services/suggestions/create_service_spec.rb | 20 +++++++ 2 files changed, 81 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index 7732767137c..bdbcb0fdb07 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -5,6 +5,16 @@ require 'spec_helper' describe Suggestions::ApplyService do include ProjectForksHelper + def build_position(args = {}) + default_args = { old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs } + + Gitlab::Diff::Position.new(default_args.merge(args)) + end + shared_examples 'successfully creates commit and updates suggestion' do def apply(suggestion) result = subject.execute(suggestion) @@ -43,13 +53,7 @@ describe Suggestions::ApplyService do let(:project) { create(:project, :repository) } let(:user) { create(:user, :commit_email) } - let(:position) do - Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 9, - diff_refs: merge_request.diff_refs) - end + let(:position) { build_position } let(:diff_note) do create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project) @@ -333,6 +337,56 @@ describe Suggestions::ApplyService do it_behaves_like 'successfully creates commit and updates suggestion' end + + context 'remove an empty line suggestion' do + let(:expected_content) do + <<~CONTENT + require 'fileutils' + require 'open3' + + module Popen + extend self + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + raise RuntimeError, "System commands must be given as an array of strings" + end + + path ||= Dir.pwd + vars = { + "PWD" => path + } + + options = { + chdir: path + } + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + @cmd_output = "" + @cmd_status = 0 + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus + end + + return @cmd_output, @cmd_status + end + end + CONTENT + end + + let(:position) { build_position(new_line: 13) } + let(:suggestion) do + create(:suggestion, :content_from_repo, note: diff_note, to_content: "") + end + + it_behaves_like 'successfully creates commit and updates suggestion' + end end context 'fork-project' do diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb index ce4990a34a4..ccd44e615a8 100644 --- a/spec/services/suggestions/create_service_spec.rb +++ b/spec/services/suggestions/create_service_spec.rb @@ -151,6 +151,26 @@ describe Suggestions::CreateService do subject.execute end end + + context 'when a patch removes an empty line' do + let(:markdown) do + <<-MARKDOWN.strip_heredoc + ```suggestion + ``` + MARKDOWN + end + let(:position) { build_position(new_line: 13) } + + it 'creates an appliable suggestion' do + subject.execute + + suggestion = note.suggestions.last + + expect(suggestion).to be_appliable + expect(suggestion.from_content).to eq("\n") + expect(suggestion.to_content).to eq("") + end + end end end end -- cgit v1.2.1 From dade5a44ec1ea8164c4363ba86f1806c70cd3642 Mon Sep 17 00:00:00 2001 From: Paul Gascou-Vaillancourt Date: Tue, 28 May 2019 07:53:44 +0000 Subject: Throw an error when formatDate's input is invalid --- spec/frontend/lib/utils/datetime_utility_spec.js | 436 +++++++++++++++++++++ .../javascripts/lib/utils/datetime_utility_spec.js | 416 -------------------- 2 files changed, 436 insertions(+), 416 deletions(-) create mode 100644 spec/frontend/lib/utils/datetime_utility_spec.js delete mode 100644 spec/javascripts/lib/utils/datetime_utility_spec.js (limited to 'spec') diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js new file mode 100644 index 00000000000..9f49e68cfe8 --- /dev/null +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -0,0 +1,436 @@ +import * as datetimeUtility from '~/lib/utils/datetime_utility'; + +describe('Date time utils', () => { + describe('timeFor', () => { + it('returns `past due` when in past', () => { + const date = new Date(); + date.setFullYear(date.getFullYear() - 1); + + expect(datetimeUtility.timeFor(date)).toBe('Past due'); + }); + + it('returns remaining time when in the future', () => { + const date = new Date(); + date.setFullYear(date.getFullYear() + 1); + + // Add a day to prevent a transient error. If date is even 1 second + // short of a full year, timeFor will return '11 months remaining' + date.setDate(date.getDate() + 1); + + expect(datetimeUtility.timeFor(date)).toBe('1 year remaining'); + }); + }); + + describe('get day name', () => { + it('should return Sunday', () => { + const day = datetimeUtility.getDayName(new Date('07/17/2016')); + + expect(day).toBe('Sunday'); + }); + + it('should return Monday', () => { + const day = datetimeUtility.getDayName(new Date('07/18/2016')); + + expect(day).toBe('Monday'); + }); + + it('should return Tuesday', () => { + const day = datetimeUtility.getDayName(new Date('07/19/2016')); + + expect(day).toBe('Tuesday'); + }); + + it('should return Wednesday', () => { + const day = datetimeUtility.getDayName(new Date('07/20/2016')); + + expect(day).toBe('Wednesday'); + }); + + it('should return Thursday', () => { + const day = datetimeUtility.getDayName(new Date('07/21/2016')); + + expect(day).toBe('Thursday'); + }); + + it('should return Friday', () => { + const day = datetimeUtility.getDayName(new Date('07/22/2016')); + + expect(day).toBe('Friday'); + }); + + it('should return Saturday', () => { + const day = datetimeUtility.getDayName(new Date('07/23/2016')); + + expect(day).toBe('Saturday'); + }); + }); + + describe('formatDate', () => { + it('should format date properly', () => { + const formattedDate = datetimeUtility.formatDate(new Date('07/23/2016')); + + expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000'); + }); + + it('should format ISO date properly', () => { + const formattedDate = datetimeUtility.formatDate('2016-07-23T00:00:00.559Z'); + + expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000'); + }); + + it('should throw an error if date is invalid', () => { + expect(() => { + datetimeUtility.formatDate('2016-07-23 00:00:00 UTC'); + }).toThrow(new Error('Invalid date')); + }); + }); + + describe('get day difference', () => { + it('should return 7', () => { + const firstDay = new Date('07/01/2016'); + const secondDay = new Date('07/08/2016'); + const difference = datetimeUtility.getDayDifference(firstDay, secondDay); + + expect(difference).toBe(7); + }); + + it('should return 31', () => { + const firstDay = new Date('07/01/2016'); + const secondDay = new Date('08/01/2016'); + const difference = datetimeUtility.getDayDifference(firstDay, secondDay); + + expect(difference).toBe(31); + }); + + it('should return 365', () => { + const firstDay = new Date('07/02/2015'); + const secondDay = new Date('07/01/2016'); + const difference = datetimeUtility.getDayDifference(firstDay, secondDay); + + expect(difference).toBe(365); + }); + }); +}); + +describe('timeIntervalInWords', () => { + it('should return string with number of minutes and seconds', () => { + expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds'); + expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second'); + expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); + expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); + }); +}); + +describe('dateInWords', () => { + const date = new Date('07/01/2016'); + + it('should return date in words', () => { + expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016'); + }); + + it('should return abbreviated month name', () => { + expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016'); + }); + + it('should return date in words without year', () => { + expect(datetimeUtility.dateInWords(date, true, true)).toEqual('Jul 1'); + }); +}); + +describe('monthInWords', () => { + const date = new Date('2017-01-20'); + + it('returns month name from provided date', () => { + expect(datetimeUtility.monthInWords(date)).toBe('January'); + }); + + it('returns abbreviated month name from provided date', () => { + expect(datetimeUtility.monthInWords(date, true)).toBe('Jan'); + }); +}); + +describe('totalDaysInMonth', () => { + it('returns number of days in a month for given date', () => { + // 1st Feb, 2016 (leap year) + expect(datetimeUtility.totalDaysInMonth(new Date(2016, 1, 1))).toBe(29); + + // 1st Feb, 2017 + expect(datetimeUtility.totalDaysInMonth(new Date(2017, 1, 1))).toBe(28); + + // 1st Jan, 2017 + expect(datetimeUtility.totalDaysInMonth(new Date(2017, 0, 1))).toBe(31); + }); +}); + +describe('getSundays', () => { + it('returns array of dates representing all Sundays of the month', () => { + // December, 2017 (it has 5 Sundays) + const dateOfSundays = [3, 10, 17, 24, 31]; + const sundays = datetimeUtility.getSundays(new Date(2017, 11, 1)); + + expect(sundays.length).toBe(5); + sundays.forEach((sunday, index) => { + expect(sunday.getDate()).toBe(dateOfSundays[index]); + }); + }); +}); + +describe('getTimeframeWindowFrom', () => { + it('returns array of date objects upto provided length (positive number) into the future starting from provided startDate', () => { + const startDate = new Date(2018, 0, 1); + const mockTimeframe = [ + new Date(2018, 0, 1), + new Date(2018, 1, 1), + new Date(2018, 2, 1), + new Date(2018, 3, 1), + new Date(2018, 4, 31), + ]; + const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, 5); + + expect(timeframe.length).toBe(5); + timeframe.forEach((timeframeItem, index) => { + expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear()); + expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth()); + expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate()); + }); + }); + + it('returns array of date objects upto provided length (negative number) into the past starting from provided startDate', () => { + const startDate = new Date(2018, 0, 1); + const mockTimeframe = [ + new Date(2018, 0, 1), + new Date(2017, 11, 1), + new Date(2017, 10, 1), + new Date(2017, 9, 1), + new Date(2017, 8, 1), + ]; + const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, -5); + + expect(timeframe.length).toBe(5); + timeframe.forEach((timeframeItem, index) => { + expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear()); + expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth()); + expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate()); + }); + }); +}); + +describe('formatTime', () => { + const expectedTimestamps = [ + [0, '00:00:00'], + [1000, '00:00:01'], + [42000, '00:00:42'], + [121000, '00:02:01'], + [10921000, '03:02:01'], + [108000000, '30:00:00'], + ]; + + expectedTimestamps.forEach(([milliseconds, expectedTimestamp]) => { + it(`formats ${milliseconds}ms as ${expectedTimestamp}`, () => { + expect(datetimeUtility.formatTime(milliseconds)).toBe(expectedTimestamp); + }); + }); +}); + +describe('datefix', () => { + describe('pad', () => { + it('should add a 0 when length is smaller than 2', () => { + expect(datetimeUtility.pad(2)).toEqual('02'); + }); + + it('should not add a zero when length matches the default', () => { + expect(datetimeUtility.pad(12)).toEqual('12'); + }); + + it('should add a 0 when length is smaller than the provided', () => { + expect(datetimeUtility.pad(12, 3)).toEqual('012'); + }); + }); + + describe('parsePikadayDate', () => { + // removed because of https://gitlab.com/gitlab-org/gitlab-ce/issues/39834 + }); + + describe('pikadayToString', () => { + it('should format a UTC date into yyyy-mm-dd format', () => { + expect(datetimeUtility.pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29'); + }); + }); +}); + +describe('prettyTime methods', () => { + const assertTimeUnits = (obj, minutes, hours, days, weeks) => { + expect(obj.minutes).toBe(minutes); + expect(obj.hours).toBe(hours); + expect(obj.days).toBe(days); + expect(obj.weeks).toBe(weeks); + }; + + describe('parseSeconds', () => { + it('should correctly parse a negative value', () => { + const zeroSeconds = datetimeUtility.parseSeconds(-1000); + + assertTimeUnits(zeroSeconds, 16, 0, 0, 0); + }); + + it('should correctly parse a zero value', () => { + const zeroSeconds = datetimeUtility.parseSeconds(0); + + assertTimeUnits(zeroSeconds, 0, 0, 0, 0); + }); + + it('should correctly parse a small non-zero second values', () => { + const subOneMinute = datetimeUtility.parseSeconds(10); + const aboveOneMinute = datetimeUtility.parseSeconds(100); + const manyMinutes = datetimeUtility.parseSeconds(1000); + + assertTimeUnits(subOneMinute, 0, 0, 0, 0); + assertTimeUnits(aboveOneMinute, 1, 0, 0, 0); + assertTimeUnits(manyMinutes, 16, 0, 0, 0); + }); + + it('should correctly parse large second values', () => { + const aboveOneHour = datetimeUtility.parseSeconds(4800); + const aboveOneDay = datetimeUtility.parseSeconds(110000); + const aboveOneWeek = datetimeUtility.parseSeconds(25000000); + + assertTimeUnits(aboveOneHour, 20, 1, 0, 0); + assertTimeUnits(aboveOneDay, 33, 6, 3, 0); + assertTimeUnits(aboveOneWeek, 26, 0, 3, 173); + }); + + it('should correctly accept a custom param for hoursPerDay', () => { + const config = { hoursPerDay: 24 }; + + const aboveOneHour = datetimeUtility.parseSeconds(4800, config); + const aboveOneDay = datetimeUtility.parseSeconds(110000, config); + const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config); + + assertTimeUnits(aboveOneHour, 20, 1, 0, 0); + assertTimeUnits(aboveOneDay, 33, 6, 1, 0); + assertTimeUnits(aboveOneWeek, 26, 8, 4, 57); + }); + + it('should correctly accept a custom param for daysPerWeek', () => { + const config = { daysPerWeek: 7 }; + + const aboveOneHour = datetimeUtility.parseSeconds(4800, config); + const aboveOneDay = datetimeUtility.parseSeconds(110000, config); + const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config); + + assertTimeUnits(aboveOneHour, 20, 1, 0, 0); + assertTimeUnits(aboveOneDay, 33, 6, 3, 0); + assertTimeUnits(aboveOneWeek, 26, 0, 0, 124); + }); + + it('should correctly accept custom params for daysPerWeek and hoursPerDay', () => { + const config = { daysPerWeek: 55, hoursPerDay: 14 }; + + const aboveOneHour = datetimeUtility.parseSeconds(4800, config); + const aboveOneDay = datetimeUtility.parseSeconds(110000, config); + const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config); + + assertTimeUnits(aboveOneHour, 20, 1, 0, 0); + assertTimeUnits(aboveOneDay, 33, 2, 2, 0); + assertTimeUnits(aboveOneWeek, 26, 0, 1, 9); + }); + }); + + describe('stringifyTime', () => { + it('should stringify values with all non-zero units', () => { + const timeObject = { + weeks: 1, + days: 4, + hours: 7, + minutes: 20, + }; + + const timeString = datetimeUtility.stringifyTime(timeObject); + + expect(timeString).toBe('1w 4d 7h 20m'); + }); + + it('should stringify values with some non-zero units', () => { + const timeObject = { + weeks: 0, + days: 4, + hours: 0, + minutes: 20, + }; + + const timeString = datetimeUtility.stringifyTime(timeObject); + + expect(timeString).toBe('4d 20m'); + }); + + it('should stringify values with no non-zero units', () => { + const timeObject = { + weeks: 0, + days: 0, + hours: 0, + minutes: 0, + }; + + const timeString = datetimeUtility.stringifyTime(timeObject); + + expect(timeString).toBe('0m'); + }); + + it('should return non-condensed representation of time object', () => { + const timeObject = { weeks: 1, days: 0, hours: 1, minutes: 0 }; + + expect(datetimeUtility.stringifyTime(timeObject, true)).toEqual('1 week 1 hour'); + }); + }); + + describe('abbreviateTime', () => { + it('should abbreviate stringified times for weeks', () => { + const fullTimeString = '1w 3d 4h 5m'; + + expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('1w'); + }); + + it('should abbreviate stringified times for non-weeks', () => { + const fullTimeString = '0w 3d 4h 5m'; + + expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('3d'); + }); + }); +}); + +describe('calculateRemainingMilliseconds', () => { + beforeEach(() => { + jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime()); + }); + + it('calculates the remaining time for a given end date', () => { + const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-04T01:44:03Z'); + + expect(milliseconds).toBe(3723000); + }); + + it('returns 0 if the end date has passed', () => { + const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-03T00:00:00Z'); + + expect(milliseconds).toBe(0); + }); +}); + +describe('newDate', () => { + it('returns new date instance from existing date instance', () => { + const initialDate = new Date(2019, 0, 1); + const copiedDate = datetimeUtility.newDate(initialDate); + + expect(copiedDate.getTime()).toBe(initialDate.getTime()); + + initialDate.setMonth(initialDate.getMonth() + 1); + + expect(copiedDate.getTime()).not.toBe(initialDate.getTime()); + }); + + it('returns date instance when provided date param is not of type date or is undefined', () => { + const initialDate = datetimeUtility.newDate(); + + expect(initialDate instanceof Date).toBe(true); + }); +}); diff --git a/spec/javascripts/lib/utils/datetime_utility_spec.js b/spec/javascripts/lib/utils/datetime_utility_spec.js deleted file mode 100644 index 5327ec9d2a0..00000000000 --- a/spec/javascripts/lib/utils/datetime_utility_spec.js +++ /dev/null @@ -1,416 +0,0 @@ -import * as datetimeUtility from '~/lib/utils/datetime_utility'; - -describe('Date time utils', () => { - describe('timeFor', () => { - it('returns `past due` when in past', () => { - const date = new Date(); - date.setFullYear(date.getFullYear() - 1); - - expect(datetimeUtility.timeFor(date)).toBe('Past due'); - }); - - it('returns remaining time when in the future', () => { - const date = new Date(); - date.setFullYear(date.getFullYear() + 1); - - // Add a day to prevent a transient error. If date is even 1 second - // short of a full year, timeFor will return '11 months remaining' - date.setDate(date.getDate() + 1); - - expect(datetimeUtility.timeFor(date)).toBe('1 year remaining'); - }); - }); - - describe('get day name', () => { - it('should return Sunday', () => { - const day = datetimeUtility.getDayName(new Date('07/17/2016')); - - expect(day).toBe('Sunday'); - }); - - it('should return Monday', () => { - const day = datetimeUtility.getDayName(new Date('07/18/2016')); - - expect(day).toBe('Monday'); - }); - - it('should return Tuesday', () => { - const day = datetimeUtility.getDayName(new Date('07/19/2016')); - - expect(day).toBe('Tuesday'); - }); - - it('should return Wednesday', () => { - const day = datetimeUtility.getDayName(new Date('07/20/2016')); - - expect(day).toBe('Wednesday'); - }); - - it('should return Thursday', () => { - const day = datetimeUtility.getDayName(new Date('07/21/2016')); - - expect(day).toBe('Thursday'); - }); - - it('should return Friday', () => { - const day = datetimeUtility.getDayName(new Date('07/22/2016')); - - expect(day).toBe('Friday'); - }); - - it('should return Saturday', () => { - const day = datetimeUtility.getDayName(new Date('07/23/2016')); - - expect(day).toBe('Saturday'); - }); - }); - - describe('get day difference', () => { - it('should return 7', () => { - const firstDay = new Date('07/01/2016'); - const secondDay = new Date('07/08/2016'); - const difference = datetimeUtility.getDayDifference(firstDay, secondDay); - - expect(difference).toBe(7); - }); - - it('should return 31', () => { - const firstDay = new Date('07/01/2016'); - const secondDay = new Date('08/01/2016'); - const difference = datetimeUtility.getDayDifference(firstDay, secondDay); - - expect(difference).toBe(31); - }); - - it('should return 365', () => { - const firstDay = new Date('07/02/2015'); - const secondDay = new Date('07/01/2016'); - const difference = datetimeUtility.getDayDifference(firstDay, secondDay); - - expect(difference).toBe(365); - }); - }); -}); - -describe('timeIntervalInWords', () => { - it('should return string with number of minutes and seconds', () => { - expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds'); - expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second'); - expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); - expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); - }); -}); - -describe('dateInWords', () => { - const date = new Date('07/01/2016'); - - it('should return date in words', () => { - expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016'); - }); - - it('should return abbreviated month name', () => { - expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016'); - }); - - it('should return date in words without year', () => { - expect(datetimeUtility.dateInWords(date, true, true)).toEqual('Jul 1'); - }); -}); - -describe('monthInWords', () => { - const date = new Date('2017-01-20'); - - it('returns month name from provided date', () => { - expect(datetimeUtility.monthInWords(date)).toBe('January'); - }); - - it('returns abbreviated month name from provided date', () => { - expect(datetimeUtility.monthInWords(date, true)).toBe('Jan'); - }); -}); - -describe('totalDaysInMonth', () => { - it('returns number of days in a month for given date', () => { - // 1st Feb, 2016 (leap year) - expect(datetimeUtility.totalDaysInMonth(new Date(2016, 1, 1))).toBe(29); - - // 1st Feb, 2017 - expect(datetimeUtility.totalDaysInMonth(new Date(2017, 1, 1))).toBe(28); - - // 1st Jan, 2017 - expect(datetimeUtility.totalDaysInMonth(new Date(2017, 0, 1))).toBe(31); - }); -}); - -describe('getSundays', () => { - it('returns array of dates representing all Sundays of the month', () => { - // December, 2017 (it has 5 Sundays) - const dateOfSundays = [3, 10, 17, 24, 31]; - const sundays = datetimeUtility.getSundays(new Date(2017, 11, 1)); - - expect(sundays.length).toBe(5); - sundays.forEach((sunday, index) => { - expect(sunday.getDate()).toBe(dateOfSundays[index]); - }); - }); -}); - -describe('getTimeframeWindowFrom', () => { - it('returns array of date objects upto provided length (positive number) into the future starting from provided startDate', () => { - const startDate = new Date(2018, 0, 1); - const mockTimeframe = [ - new Date(2018, 0, 1), - new Date(2018, 1, 1), - new Date(2018, 2, 1), - new Date(2018, 3, 1), - new Date(2018, 4, 31), - ]; - const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, 5); - - expect(timeframe.length).toBe(5); - timeframe.forEach((timeframeItem, index) => { - expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear()); - expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth()); - expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate()); - }); - }); - - it('returns array of date objects upto provided length (negative number) into the past starting from provided startDate', () => { - const startDate = new Date(2018, 0, 1); - const mockTimeframe = [ - new Date(2018, 0, 1), - new Date(2017, 11, 1), - new Date(2017, 10, 1), - new Date(2017, 9, 1), - new Date(2017, 8, 1), - ]; - const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, -5); - - expect(timeframe.length).toBe(5); - timeframe.forEach((timeframeItem, index) => { - expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear()); - expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth()); - expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate()); - }); - }); -}); - -describe('formatTime', () => { - const expectedTimestamps = [ - [0, '00:00:00'], - [1000, '00:00:01'], - [42000, '00:00:42'], - [121000, '00:02:01'], - [10921000, '03:02:01'], - [108000000, '30:00:00'], - ]; - - expectedTimestamps.forEach(([milliseconds, expectedTimestamp]) => { - it(`formats ${milliseconds}ms as ${expectedTimestamp}`, () => { - expect(datetimeUtility.formatTime(milliseconds)).toBe(expectedTimestamp); - }); - }); -}); - -describe('datefix', () => { - describe('pad', () => { - it('should add a 0 when length is smaller than 2', () => { - expect(datetimeUtility.pad(2)).toEqual('02'); - }); - - it('should not add a zero when length matches the default', () => { - expect(datetimeUtility.pad(12)).toEqual('12'); - }); - - it('should add a 0 when length is smaller than the provided', () => { - expect(datetimeUtility.pad(12, 3)).toEqual('012'); - }); - }); - - describe('parsePikadayDate', () => { - // removed because of https://gitlab.com/gitlab-org/gitlab-ce/issues/39834 - }); - - describe('pikadayToString', () => { - it('should format a UTC date into yyyy-mm-dd format', () => { - expect(datetimeUtility.pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29'); - }); - }); -}); - -describe('prettyTime methods', () => { - const assertTimeUnits = (obj, minutes, hours, days, weeks) => { - expect(obj.minutes).toBe(minutes); - expect(obj.hours).toBe(hours); - expect(obj.days).toBe(days); - expect(obj.weeks).toBe(weeks); - }; - - describe('parseSeconds', () => { - it('should correctly parse a negative value', () => { - const zeroSeconds = datetimeUtility.parseSeconds(-1000); - - assertTimeUnits(zeroSeconds, 16, 0, 0, 0); - }); - - it('should correctly parse a zero value', () => { - const zeroSeconds = datetimeUtility.parseSeconds(0); - - assertTimeUnits(zeroSeconds, 0, 0, 0, 0); - }); - - it('should correctly parse a small non-zero second values', () => { - const subOneMinute = datetimeUtility.parseSeconds(10); - const aboveOneMinute = datetimeUtility.parseSeconds(100); - const manyMinutes = datetimeUtility.parseSeconds(1000); - - assertTimeUnits(subOneMinute, 0, 0, 0, 0); - assertTimeUnits(aboveOneMinute, 1, 0, 0, 0); - assertTimeUnits(manyMinutes, 16, 0, 0, 0); - }); - - it('should correctly parse large second values', () => { - const aboveOneHour = datetimeUtility.parseSeconds(4800); - const aboveOneDay = datetimeUtility.parseSeconds(110000); - const aboveOneWeek = datetimeUtility.parseSeconds(25000000); - - assertTimeUnits(aboveOneHour, 20, 1, 0, 0); - assertTimeUnits(aboveOneDay, 33, 6, 3, 0); - assertTimeUnits(aboveOneWeek, 26, 0, 3, 173); - }); - - it('should correctly accept a custom param for hoursPerDay', () => { - const config = { hoursPerDay: 24 }; - - const aboveOneHour = datetimeUtility.parseSeconds(4800, config); - const aboveOneDay = datetimeUtility.parseSeconds(110000, config); - const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config); - - assertTimeUnits(aboveOneHour, 20, 1, 0, 0); - assertTimeUnits(aboveOneDay, 33, 6, 1, 0); - assertTimeUnits(aboveOneWeek, 26, 8, 4, 57); - }); - - it('should correctly accept a custom param for daysPerWeek', () => { - const config = { daysPerWeek: 7 }; - - const aboveOneHour = datetimeUtility.parseSeconds(4800, config); - const aboveOneDay = datetimeUtility.parseSeconds(110000, config); - const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config); - - assertTimeUnits(aboveOneHour, 20, 1, 0, 0); - assertTimeUnits(aboveOneDay, 33, 6, 3, 0); - assertTimeUnits(aboveOneWeek, 26, 0, 0, 124); - }); - - it('should correctly accept custom params for daysPerWeek and hoursPerDay', () => { - const config = { daysPerWeek: 55, hoursPerDay: 14 }; - - const aboveOneHour = datetimeUtility.parseSeconds(4800, config); - const aboveOneDay = datetimeUtility.parseSeconds(110000, config); - const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config); - - assertTimeUnits(aboveOneHour, 20, 1, 0, 0); - assertTimeUnits(aboveOneDay, 33, 2, 2, 0); - assertTimeUnits(aboveOneWeek, 26, 0, 1, 9); - }); - }); - - describe('stringifyTime', () => { - it('should stringify values with all non-zero units', () => { - const timeObject = { - weeks: 1, - days: 4, - hours: 7, - minutes: 20, - }; - - const timeString = datetimeUtility.stringifyTime(timeObject); - - expect(timeString).toBe('1w 4d 7h 20m'); - }); - - it('should stringify values with some non-zero units', () => { - const timeObject = { - weeks: 0, - days: 4, - hours: 0, - minutes: 20, - }; - - const timeString = datetimeUtility.stringifyTime(timeObject); - - expect(timeString).toBe('4d 20m'); - }); - - it('should stringify values with no non-zero units', () => { - const timeObject = { - weeks: 0, - days: 0, - hours: 0, - minutes: 0, - }; - - const timeString = datetimeUtility.stringifyTime(timeObject); - - expect(timeString).toBe('0m'); - }); - - it('should return non-condensed representation of time object', () => { - const timeObject = { weeks: 1, days: 0, hours: 1, minutes: 0 }; - - expect(datetimeUtility.stringifyTime(timeObject, true)).toEqual('1 week 1 hour'); - }); - }); - - describe('abbreviateTime', () => { - it('should abbreviate stringified times for weeks', () => { - const fullTimeString = '1w 3d 4h 5m'; - - expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('1w'); - }); - - it('should abbreviate stringified times for non-weeks', () => { - const fullTimeString = '0w 3d 4h 5m'; - - expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('3d'); - }); - }); -}); - -describe('calculateRemainingMilliseconds', () => { - beforeEach(() => { - spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime()); - }); - - it('calculates the remaining time for a given end date', () => { - const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-04T01:44:03Z'); - - expect(milliseconds).toBe(3723000); - }); - - it('returns 0 if the end date has passed', () => { - const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-03T00:00:00Z'); - - expect(milliseconds).toBe(0); - }); -}); - -describe('newDate', () => { - it('returns new date instance from existing date instance', () => { - const initialDate = new Date(2019, 0, 1); - const copiedDate = datetimeUtility.newDate(initialDate); - - expect(copiedDate.getTime()).toBe(initialDate.getTime()); - - initialDate.setMonth(initialDate.getMonth() + 1); - - expect(copiedDate.getTime()).not.toBe(initialDate.getTime()); - }); - - it('returns date instance when provided date param is not of type date or is undefined', () => { - const initialDate = datetimeUtility.newDate(); - - expect(initialDate instanceof Date).toBe(true); - }); -}); -- cgit v1.2.1 From 11f85ae8c3b8ec5d864edd079e7c420a49cae72e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 9 May 2019 10:27:07 +0100 Subject: Enables GraphQL batch requests Enabling GraphQL batch requests allows for multiple queries to be sent in 1 request reducing the amount of requests we send to the server. Responses come come back in the same order as the queries were provided. --- spec/graphql/gitlab_schema_spec.rb | 4 +- spec/requests/api/graphql/gitlab_schema_spec.rb | 85 ++++++++++++++++------ .../api/graphql/multiplexed_queries_spec.rb | 39 ++++++++++ spec/support/helpers/graphql_helpers.rb | 13 +++- 4 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 spec/requests/api/graphql/multiplexed_queries_spec.rb (limited to 'spec') diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index c138c87c4ac..e9149f4250f 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -56,10 +56,10 @@ describe GitlabSchema do described_class.execute('query', context: {}) end - it 'returns ANONYMOUS_MAX_DEPTH' do + it 'returns DEFAULT_MAX_DEPTH' do expect(GraphQL::Schema) .to receive(:execute) - .with('query', hash_including(max_depth: GitlabSchema::ANONYMOUS_MAX_DEPTH)) + .with('query', hash_including(max_depth: GitlabSchema::DEFAULT_MAX_DEPTH)) described_class.execute('query', context: {}) end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index dd518274f82..a724c5c3f1c 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -3,41 +3,82 @@ require 'spec_helper' describe 'GitlabSchema configurations' do include GraphqlHelpers - let(:project) { create(:project, :repository) } - let(:query) { graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) } - let(:current_user) { create(:user) } + let(:project) { create(:project) } - describe '#max_complexity' do - context 'when complexity is too high' do - it 'shows an error' do - allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 + shared_examples 'imposing query limits' do + describe '#max_complexity' do + context 'when complexity is too high' do + it 'shows an error' do + allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 - post_graphql(query, current_user: nil) + subject - expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') + expect(graphql_errors.flatten.first['message']).to include('which exceeds max complexity of 1') + end end end - end - describe '#max_depth' do - context 'when query depth is too high' do - it 'shows error' do - errors = [{ "message" => "Query has depth of 2, which exceeds max depth of 1" }] - allow(GitlabSchema).to receive(:max_query_depth).and_return 1 + describe '#max_depth' do + context 'when query depth is too high' do + it 'shows error' do + errors = { "message" => "Query has depth of 2, which exceeds max depth of 1" } + allow(GitlabSchema).to receive(:max_query_depth).and_return 1 - post_graphql(query) + subject - expect(graphql_errors).to eq(errors) + expect(graphql_errors.flatten).to include(errors) + end end + + context 'when query depth is within range' do + it 'has no error' do + allow(GitlabSchema).to receive(:max_query_depth).and_return 5 + + subject + + expect(Array.wrap(graphql_errors).compact).to be_empty + end + end + end + end + + context 'regular queries' do + subject do + query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) + post_graphql(query) end - context 'when query depth is within range' do - it 'has no error' do - allow(GitlabSchema).to receive(:max_query_depth).and_return 5 + it_behaves_like 'imposing query limits' + end + + context 'multiplexed queries' do + subject do + queries = [ + { query: graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) }, + { query: graphql_query_for('echo', { 'text' => "$test" }, []), variables: { "test" => "Hello world" } } + ] + + post_multiplex(queries) + end + + it_behaves_like 'imposing query limits' do + it "fails all queries when only one of the queries is too complex" do + # The `project` query above has a complexity of 5 + allow(GitlabSchema).to receive(:max_query_complexity).and_return 4 + + subject - post_graphql(query) + # Expect a response for each query, even though it will be empty + expect(json_response.size).to eq(2) + json_response.each do |single_query_response| + expect(single_query_response).not_to have_key('data') + end - expect(graphql_errors).to be_nil + # Expect errors for each query + expect(graphql_errors.size).to eq(2) + graphql_errors.each do |single_query_errors| + expect(single_query_errors.first['message']).to include('which exceeds max complexity of 4') + end end end end diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb new file mode 100644 index 00000000000..844fd979285 --- /dev/null +++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'Multiplexed queries' do + include GraphqlHelpers + + it 'returns responses for multiple queries' do + queries = [ + { query: 'query($text: String) { echo(text: $text) }', + variables: { 'text' => 'Hello' } }, + { query: 'query($text: String) { echo(text: $text) }', + variables: { 'text' => 'World' } } + ] + + post_multiplex(queries) + + first_response = json_response.first['data']['echo'] + second_response = json_response.last['data']['echo'] + + expect(first_response).to eq('nil says: Hello') + expect(second_response).to eq('nil says: World') + end + + it 'returns error and data combinations' do + queries = [ + { query: 'query($text: String) { broken query }' }, + { query: 'query working($text: String) { echo(text: $text) }', + variables: { 'text' => 'World' } } + ] + + post_multiplex(queries) + + first_response = json_response.first['errors'] + second_response = json_response.last['data']['echo'] + + expect(first_response).not_to be_empty + expect(second_response).to eq('nil says: World') + end +end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 44ed9da25fc..e95c7f2a6d6 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -134,6 +134,10 @@ module GraphqlHelpers end.join(", ") end + def post_multiplex(queries, current_user: nil, headers: {}) + post api('/', current_user, version: 'graphql'), params: { _json: queries }, headers: headers + end + def post_graphql(query, current_user: nil, variables: nil, headers: {}) post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables }, headers: headers end @@ -147,7 +151,14 @@ module GraphqlHelpers end def graphql_errors - json_response['errors'] + case json_response + when Hash # regular query + json_response['errors'] + when Array # multiplexed queries + json_response.map { |response| response['errors'] } + else + raise "Unkown GraphQL response type #{json_response.class}" + end end def graphql_mutation_response(mutation_name) -- cgit v1.2.1 From b9ee324a00afa66ef76f5223f627ac29e2e4b8b8 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 28 May 2019 09:57:46 +0000 Subject: Revert "Merge branch '61511-add-expand-collapse-to-project-operation-setttings-2' into 'master'" This reverts merge request !28620 --- .../components/external_dashboard_spec.js | 15 --------------- 1 file changed, 15 deletions(-) (limited to 'spec') diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js index 4d35d35965c..de1dd219fe0 100644 --- a/spec/frontend/operation_settings/components/external_dashboard_spec.js +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -2,9 +2,6 @@ import { shallowMount } from '@vue/test-utils'; import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui'; import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue'; import { TEST_HOST } from 'helpers/test_constants'; -import initSettingsPanels from '~/settings_panels'; - -jest.mock('~/settings_panels'); describe('operation settings external dashboard component', () => { let wrapper; @@ -24,18 +21,6 @@ describe('operation settings external dashboard component', () => { expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard'); }); - describe('expand/collapse button', () => { - it('is properly instantiated as a settings panel', () => { - expect(initSettingsPanels).toHaveBeenCalled(); - }); - - it('defaults to collapsed state', () => { - const button = wrapper.find(GlButton); - - expect(button.text()).toBe('Expand'); - }); - }); - describe('sub-header', () => { let subHeader; -- cgit v1.2.1 From d480fc51922e0a43da963493c80770f5c37a69dc Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Tue, 28 May 2019 17:18:29 +0800 Subject: Increase timeout of flaky specs --- spec/javascripts/notes/components/note_app_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index ef876dc2941..f04af04f852 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -195,7 +195,7 @@ describe('note_app', () => { setTimeout(() => { done(); }); - }); + }, 2000); }); describe('discussion note', () => { @@ -230,7 +230,7 @@ describe('note_app', () => { setTimeout(() => { done(); }); - }); + }, 2000); }); }); -- cgit v1.2.1 From 4df0e2599bc9bd7c50b6acea02278c1fb97d3ead Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 28 May 2019 10:05:20 +0000 Subject: Fix display of promote to group label Since label presenter is used in label index view, label class check doesn't work as expected because the class is now LabelPresenter. Also `label.subject` doesn't work as expected now because Label's model `subject` method is shadowed by Gitlab's presenter's method which uses `subject` for referencing the original object. Instead we use a presenter's method for both checks now. `label_deletion_confirm_text` is not used anywhere so it's removed --- .../projects/labels/user_promotes_label_spec.rb | 34 ++++++++++++++++++++++ .../projects/labels/user_removes_labels_spec.rb | 5 +++- spec/helpers/labels_helper_spec.rb | 19 +++++++++++- spec/presenters/label_presenter_spec.rb | 28 ++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 spec/features/projects/labels/user_promotes_label_spec.rb (limited to 'spec') diff --git a/spec/features/projects/labels/user_promotes_label_spec.rb b/spec/features/projects/labels/user_promotes_label_spec.rb new file mode 100644 index 00000000000..fdecafd4c50 --- /dev/null +++ b/spec/features/projects/labels/user_promotes_label_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'User promotes label' do + set(:group) { create(:group) } + set(:user) { create(:user) } + set(:project) { create(:project, namespace: group) } + set(:label) { create(:label, project: project) } + + context 'when user can admin group labels' do + before do + group.add_developer(user) + sign_in(user) + visit(project_labels_path(project)) + end + + it "shows label promote button" do + expect(page).to have_selector('.js-promote-project-label-button') + end + end + + context 'when user cannot admin group labels' do + before do + project.add_developer(user) + sign_in(user) + visit(project_labels_path(project)) + end + + it "does not show label promote button" do + expect(page).not_to have_selector('.js-promote-project-label-button') + end + end +end diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb index b0ce03a1c31..c231e54decd 100644 --- a/spec/features/projects/labels/user_removes_labels_spec.rb +++ b/spec/features/projects/labels/user_removes_labels_spec.rb @@ -21,8 +21,11 @@ describe "User removes labels" do page.first(".label-list-item") do first('.js-label-options-dropdown').click first(".remove-row").click - first(:link, "Delete label").click end + + expect(page).to have_content("#{label.title} will be permanently deleted from #{project.name}. This cannot be undone.") + + first(:link, "Delete label").click end expect(page).to have_content("Label was removed").and have_no_content(label.title) diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 58eaf991d6e..314305d7a8e 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -6,7 +6,7 @@ describe LabelsHelper do let(:context_project) { project } context "when asking for a #{issuables_type} link" do - subject { show_label_issuables_link?(label, issuables_type, project: context_project) } + subject { show_label_issuables_link?(label.present(issuable_subject: nil), issuables_type, project: context_project) } context "when #{issuables_type} are enabled for the project" do let(:project) { create(:project, "#{issuables_type}_access_level": ProjectFeature::ENABLED) } @@ -279,4 +279,21 @@ describe LabelsHelper do expect(label.color).to eq('bar') end end + + describe '#label_status_tooltip' do + let(:status) { 'unsubscribed'.inquiry } + subject { label_status_tooltip(label.present(issuable_subject: nil), status) } + + context 'with a project label' do + let(:label) { create(:label, title: 'bug') } + + it { is_expected.to eq('Subscribe at project level') } + end + + context 'with a group label' do + let(:label) { create(:group_label, title: 'bug') } + + it { is_expected.to eq('Subscribe at group level') } + end + end end diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb index fae8188670f..d566da7c872 100644 --- a/spec/presenters/label_presenter_spec.rb +++ b/spec/presenters/label_presenter_spec.rb @@ -62,4 +62,32 @@ describe LabelPresenter do expect(label.can_subscribe_to_label_in_different_levels?).to be_falsey end end + + describe '#project_label?' do + context 'with group label' do + subject { group_label.project_label? } + + it { is_expected.to be_falsey } + end + + context 'with project label' do + subject { label.project_label? } + + it { is_expected.to be_truthy } + end + end + + describe '#subject_name' do + context 'with group label' do + subject { group_label.subject_name } + + it { is_expected.to eq(group_label.group.name) } + end + + context 'with project label' do + subject { label.subject_name } + + it { is_expected.to eq(label.project.name) } + end + end end -- cgit v1.2.1 From 3895e547415cf502ff6eafc18e98f245a058afcd Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Tue, 28 May 2019 10:14:26 +0000 Subject: CE port of Move EE specific lines in API::Users --- spec/requests/api/users_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index b84202364e1..bab1520b960 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -276,6 +276,18 @@ describe API::Users do expect(response).to have_gitlab_http_status(400) end end + + context "when authenticated and ldap is enabled" do + it "returns non-ldap user" do + create :omniauth_user, provider: "ldapserver1" + + get api("/users", user), params: { skip_ldap: "true" } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first["username"]).to eq user.username + end + end end describe "GET /users/:id" do -- cgit v1.2.1 From 5ddb32f3820a16294948fad34095eb4b009597ba Mon Sep 17 00:00:00 2001 From: Liam McAndrew Date: Tue, 28 May 2019 10:31:51 +0000 Subject: Fix order dependency with user params during imports --- spec/lib/gitlab/import_export/members_mapper_spec.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index c663cf42a83..b95b5dfe791 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -12,7 +12,6 @@ describe Gitlab::ImportExport::MembersMapper do "access_level" => 40, "source_id" => 14, "source_type" => "Project", - "user_id" => 19, "notification_level" => 3, "created_at" => "2016-03-11T10:21:44.822Z", "updated_at" => "2016-03-11T10:21:44.822Z", @@ -25,7 +24,8 @@ describe Gitlab::ImportExport::MembersMapper do "id" => exported_user_id, "email" => user2.email, "username" => 'test' - } + }, + "user_id" => 19 }, { "id" => 3, @@ -80,6 +80,15 @@ describe Gitlab::ImportExport::MembersMapper do expect(ProjectMember.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER) end + it 'removes old user_id from member_hash to avoid conflict with user key' do + expect(ProjectMember).to receive(:create) + .twice + .with(hash_excluding('user_id')) + .and_call_original + + members_mapper.map + end + context 'user is not an admin' do let(:user) { create(:user) } -- cgit v1.2.1 From 8f53e9ccc8771f1c8542a0b013a6e99ad6158ecd Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Sun, 12 May 2019 11:19:02 +0200 Subject: Add notify_only_default_branch option to PipelinesEmailService --- .../pipelines_email_service_spec.rb | 76 +++++++++++++++++----- 1 file changed, 59 insertions(+), 17 deletions(-) (limited to 'spec') diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb index ca17e7453b8..b85565e0c25 100644 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ b/spec/models/project_services/pipelines_email_service_spec.rb @@ -4,7 +4,11 @@ require 'spec_helper' describe PipelinesEmailService, :mailer do let(:pipeline) do - create(:ci_pipeline, project: project, sha: project.commit('master').sha) + create(:ci_pipeline, :failed, + project: project, + sha: project.commit('master').sha, + ref: project.default_branch + ) end let(:project) { create(:project, :repository) } @@ -84,12 +88,7 @@ describe PipelinesEmailService, :mailer do subject.test(data) end - context 'when pipeline is failed' do - before do - data[:object_attributes][:status] = 'failed' - pipeline.update(status: 'failed') - end - + context 'when pipeline is failed and on default branch' do it_behaves_like 'sending email' end @@ -101,6 +100,25 @@ describe PipelinesEmailService, :mailer do it_behaves_like 'sending email' end + + context 'when pipeline is failed and on a non-default branch' do + before do + data[:object_attributes][:ref] = 'not-the-default-branch' + pipeline.update(ref: 'not-the-default-branch') + end + + context 'with notify_only_default branch on' do + before do + subject.notify_only_default_branch = true + end + + it_behaves_like 'sending email' + end + + context 'with notify_only_default_branch off' do + it_behaves_like 'sending email' + end + end end describe '#execute' do @@ -110,11 +128,6 @@ describe PipelinesEmailService, :mailer do context 'with recipients' do context 'with failed pipeline' do - before do - data[:object_attributes][:status] = 'failed' - pipeline.update(status: 'failed') - end - it_behaves_like 'sending email' end @@ -133,11 +146,6 @@ describe PipelinesEmailService, :mailer do end context 'with failed pipeline' do - before do - data[:object_attributes][:status] = 'failed' - pipeline.update(status: 'failed') - end - it_behaves_like 'sending email' end @@ -150,6 +158,40 @@ describe PipelinesEmailService, :mailer do it_behaves_like 'not sending email' end end + + context 'with notify_only_default_branch off' do + context 'with default branch' do + it_behaves_like 'sending email' + end + + context 'with non default branch' do + before do + data[:object_attributes][:ref] = 'not-the-default-branch' + pipeline.update(ref: 'not-the-default-branch') + end + + it_behaves_like 'sending email' + end + end + + context 'with notify_only_default_branch on' do + before do + subject.notify_only_default_branch = true + end + + context 'with default branch' do + it_behaves_like 'sending email' + end + + context 'with non default branch' do + before do + data[:object_attributes][:ref] = 'not-the-default-branch' + pipeline.update(ref: 'not-the-default-branch') + end + + it_behaves_like 'not sending email' + end + end end context 'with empty recipients list' do -- cgit v1.2.1 From 93a5071a3bde5db57f8f763a1c958a00fa6b4bf8 Mon Sep 17 00:00:00 2001 From: Tiger Date: Tue, 28 May 2019 09:44:45 -0500 Subject: Remove unused fixture lines --- spec/lib/gitlab/import_export/project.json | 2 -- 1 file changed, 2 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 43afc067e08..fb7bddb386c 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -184,8 +184,6 @@ "note": "Est reprehenderit quas aut aspernatur autem recusandae voluptatem.", "noteable_type": "Issue", "author_id": 25, - "note_html": "

      dodgy html

      ", - "cached_markdown_version": 121212, "created_at": "2016-06-14T15:02:47.795Z", "updated_at": "2016-06-14T15:02:47.795Z", "project_id": 5, -- cgit v1.2.1 From 51183ad3bdad507325c14c916d70fe3ab9857bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 1 May 2019 17:31:04 -0400 Subject: Add all reports scope to Ci::JobArtifact --- spec/models/ci/job_artifact_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'spec') diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 5964f66c398..d3809f0a460 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -23,6 +23,21 @@ describe Ci::JobArtifact do it_behaves_like 'having unique enum values' + describe '.with_all_reports' do + let!(:artifact) { create(:ci_job_artifact, :archive) } + + subject { described_class.with_all_reports } + + it { is_expected.to be_empty } + + context 'when there are reports' do + let!(:metrics_report) { create(:ci_job_artifact, :junit) } + let!(:codequality_report) { create(:ci_job_artifact, :codequality) } + + it { is_expected.to eq([metrics_report, codequality_report]) } + end + end + describe '.test_reports' do subject { described_class.test_reports } -- cgit v1.2.1 From 31bd09a3924d1b76cac38996267ffe6ec08cad46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 1 May 2019 18:29:15 -0400 Subject: Expose report download path in build details Exposes report download paths in build details. --- spec/serializers/build_details_entity_spec.rb | 8 ++++++++ spec/serializers/job_artifact_entity_spec.rb | 28 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 spec/serializers/job_artifact_entity_spec.rb (limited to 'spec') diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 9c2e5c79a9d..0b8ed873bde 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -146,5 +146,13 @@ describe BuildDetailsEntity do end end end + + context 'when the build has reports' do + let!(:report) { create(:ci_job_artifact, :codequality, job: build) } + + it 'exposes the report artifacts' do + expect(subject[:reports]).not_to be_empty + end + end end end diff --git a/spec/serializers/job_artifact_entity_spec.rb b/spec/serializers/job_artifact_entity_spec.rb new file mode 100644 index 00000000000..4188be56473 --- /dev/null +++ b/spec/serializers/job_artifact_entity_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JobArtifactEntity do + let(:report) { create(:ci_job_artifact, :codequality) } + let(:entity) { described_class.new(report, request: double) } + + describe '#as_json' do + subject { entity.as_json } + + it 'exposes file_type' do + expect(subject[:file_type]).to eq(report.file_type) + end + + it 'exposes file_format' do + expect(subject[:file_format]).to eq(report.file_format) + end + + it 'exposes size' do + expect(subject[:size]).to eq(report.size) + end + + it 'exposes download path' do + expect(subject[:download_path]).to include("jobs/#{report.job.id}/artifacts/download") + end + end +end -- cgit v1.2.1 From 9e6f37744a7f30a181879a1e2799e129c5e795c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 1 May 2019 19:16:26 -0400 Subject: Move JobArtifactEntity to JobArtifactReportEntity --- spec/serializers/job_artifact_entity_spec.rb | 28 ---------------------- .../serializers/job_artifact_report_entity_spec.rb | 28 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 spec/serializers/job_artifact_entity_spec.rb create mode 100644 spec/serializers/job_artifact_report_entity_spec.rb (limited to 'spec') diff --git a/spec/serializers/job_artifact_entity_spec.rb b/spec/serializers/job_artifact_entity_spec.rb deleted file mode 100644 index 4188be56473..00000000000 --- a/spec/serializers/job_artifact_entity_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe JobArtifactEntity do - let(:report) { create(:ci_job_artifact, :codequality) } - let(:entity) { described_class.new(report, request: double) } - - describe '#as_json' do - subject { entity.as_json } - - it 'exposes file_type' do - expect(subject[:file_type]).to eq(report.file_type) - end - - it 'exposes file_format' do - expect(subject[:file_format]).to eq(report.file_format) - end - - it 'exposes size' do - expect(subject[:size]).to eq(report.size) - end - - it 'exposes download path' do - expect(subject[:download_path]).to include("jobs/#{report.job.id}/artifacts/download") - end - end -end diff --git a/spec/serializers/job_artifact_report_entity_spec.rb b/spec/serializers/job_artifact_report_entity_spec.rb new file mode 100644 index 00000000000..eef5c16d0fb --- /dev/null +++ b/spec/serializers/job_artifact_report_entity_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JobArtifactReportEntity do + let(:report) { create(:ci_job_artifact, :codequality) } + let(:entity) { described_class.new(report, request: double) } + + describe '#as_json' do + subject { entity.as_json } + + it 'exposes file_type' do + expect(subject[:file_type]).to eq(report.file_type) + end + + it 'exposes file_format' do + expect(subject[:file_format]).to eq(report.file_format) + end + + it 'exposes size' do + expect(subject[:size]).to eq(report.size) + end + + it 'exposes download path' do + expect(subject[:download_path]).to include("jobs/#{report.job.id}/artifacts/download") + end + end +end -- cgit v1.2.1 From c86fa0e45e4a053090b5c17b9678120e39f696a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 21 May 2019 00:56:19 +0200 Subject: Rename with_all_reports to with_reports --- spec/models/ci/job_artifact_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index d3809f0a460..e6d682c24d9 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -23,10 +23,10 @@ describe Ci::JobArtifact do it_behaves_like 'having unique enum values' - describe '.with_all_reports' do + describe '.with_reports' do let!(:artifact) { create(:ci_job_artifact, :archive) } - subject { described_class.with_all_reports } + subject { described_class.with_reports } it { is_expected.to be_empty } -- cgit v1.2.1 From 1542b160f18b30a8ec28fbdc9c52694b5dfd6f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 25 May 2019 02:08:53 +0200 Subject: Extract Ci::Build#report_artifacts into method Extracts combining the job_artifacts relation with the with_reports scope for getting report artifacts into a method. --- spec/models/ci/build_spec.rb | 12 ++++++++++++ spec/serializers/build_details_entity_spec.rb | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index bc81c34f7ab..32eef9e0e01 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -3490,6 +3490,18 @@ describe Ci::Build do end end + describe '#report_artifacts' do + subject { build.report_artifacts } + + context 'when the build has reports' do + let!(:report) { create(:ci_job_artifact, :codequality, job: build) } + + it 'returns the artifacts with reports' do + expect(subject).to contain_exactly(report) + end + end + end + describe '#artifacts_metadata_entry' do set(:build) { create(:ci_build, project: project) } let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' } diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 0b8ed873bde..d922e8246c7 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -151,7 +151,8 @@ describe BuildDetailsEntity do let!(:report) { create(:ci_job_artifact, :codequality, job: build) } it 'exposes the report artifacts' do - expect(subject[:reports]).not_to be_empty + expect(subject[:reports].count).to eq(1) + expect(subject[:reports].first[:file_type]).to eq('codequality') end end end -- cgit v1.2.1 From 40b67a4f6a91e718721857f4d34cc600d1f90b9b Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Tue, 21 May 2019 17:46:12 -0500 Subject: Allow issues to be sorted by relative_position - adding a "Manual" option to the dropdown - show 100 issues list when manually sorting --- .../controllers/projects/issues_controller_spec.rb | 31 ++++++++++++++++++++++ spec/models/issue_spec.rb | 15 +++++++++++ 2 files changed, 46 insertions(+) (limited to 'spec') diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 0c46b43f080..32607fc5f56 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -130,6 +130,37 @@ describe Projects::IssuesController do end end + context 'with relative_position sorting' do + let!(:issue_list) { create_list(:issue, 2, project: project) } + + before do + sign_in(user) + project.add_developer(user) + allow(Kaminari.config).to receive(:default_per_page).and_return(1) + end + + it 'overrides the number allowed on the page' do + get :index, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + sort: 'relative_position' + } + + expect(assigns(:issues).count).to eq 2 + end + + it 'allows the default number on the page' do + get :index, + params: { + namespace_id: project.namespace.to_param, + project_id: project + } + + expect(assigns(:issues).count).to eq 1 + end + end + context 'external authorization' do before do sign_in user diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index cc777cbf749..a5c7e9db2a1 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -93,6 +93,21 @@ describe Issue do end end + describe '#sort' do + let(:project) { create(:project) } + + context "by relative_position" do + let!(:issue) { create(:issue, project: project) } + let!(:issue2) { create(:issue, project: project, relative_position: 2) } + let!(:issue3) { create(:issue, project: project, relative_position: 1) } + + it "sorts asc with nulls at the end" do + issues = project.issues.sort_by_attribute('relative_position') + expect(issues).to eq([issue3, issue2, issue]) + end + end + end + describe '#card_attributes' do it 'includes the author name' do allow(subject).to receive(:author).and_return(double(name: 'Robert')) -- cgit v1.2.1 From ede97703ea306b0bcb2672703e635692da70b194 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Wed, 29 May 2019 11:38:00 +0800 Subject: Increase global karma timeout Revert useless timeout added in karma spec --- spec/javascripts/notes/components/note_app_spec.js | 4 ++-- spec/javascripts/test_bundle.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index f04af04f852..ef876dc2941 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -195,7 +195,7 @@ describe('note_app', () => { setTimeout(() => { done(); }); - }, 2000); + }); }); describe('discussion note', () => { @@ -230,7 +230,7 @@ describe('note_app', () => { setTimeout(() => { done(); }); - }, 2000); + }); }); }); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 87ef0885d8c..8c80a425581 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -111,7 +111,7 @@ let longRunningTestTimeoutHandle; beforeEach(done => { longRunningTestTimeoutHandle = setTimeout(() => { done.fail('Test is running too long!'); - }, 2000); + }, 4000); done(); }); -- cgit v1.2.1 From 1224aa516eefe7be1c8ca306625d5d0b6d524d76 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 May 2019 10:24:35 +0100 Subject: Created repository list breadcrumbs Vue app --- .../project_owner_creates_license_file_spec.rb | 1 + .../projects/files/user_creates_files_spec.rb | 1 + .../show/user_sees_collaboration_links_spec.rb | 1 + .../repository/components/breadcrumbs_spec.js | 44 ++++++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 spec/frontend/repository/components/breadcrumbs_spec.js (limited to 'spec') diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index 6762460971f..44715261b8b 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -5,6 +5,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do let(:project_maintainer) { project.owner } before do + stub_feature_flags(vue_file_list: false) project.repository.delete_file(project_maintainer, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master') sign_in(project_maintainer) diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb index dd2964c2186..69f8bd4d319 100644 --- a/spec/features/projects/files/user_creates_files_spec.rb +++ b/spec/features/projects/files/user_creates_files_spec.rb @@ -12,6 +12,7 @@ describe 'Projects > Files > User creates files' do let(:user) { create(:user) } before do + stub_feature_flags(vue_file_list: false) stub_feature_flags(web_ide_default: false) project.add_maintainer(user) diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb index 24777788248..46586b891e7 100644 --- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb +++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb @@ -5,6 +5,7 @@ describe 'Projects > Show > Collaboration links' do let(:user) { create(:user) } before do + stub_feature_flags(vue_file_list: false) project.add_developer(user) sign_in(user) end diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js new file mode 100644 index 00000000000..068fa317a87 --- /dev/null +++ b/spec/frontend/repository/components/breadcrumbs_spec.js @@ -0,0 +1,44 @@ +import { shallowMount, RouterLinkStub } from '@vue/test-utils'; +import Breadcrumbs from '~/repository/components/breadcrumbs.vue'; + +let vm; + +function factory(currentPath) { + vm = shallowMount(Breadcrumbs, { + propsData: { + currentPath, + }, + stubs: { + RouterLink: RouterLinkStub, + }, + }); +} + +describe('Repository breadcrumbs component', () => { + afterEach(() => { + vm.destroy(); + }); + + it.each` + path | linkCount + ${'/'} | ${1} + ${'app'} | ${2} + ${'app/assets'} | ${3} + ${'app/assets/javascripts'} | ${4} + `('renders $linkCount links for path $path', ({ path, linkCount }) => { + factory(path); + + expect(vm.findAll(RouterLinkStub).length).toEqual(linkCount); + }); + + it('renders last link as active', () => { + factory('app/assets'); + + expect( + vm + .findAll(RouterLinkStub) + .at(2) + .attributes('aria-current'), + ).toEqual('page'); + }); +}); -- cgit v1.2.1 From a2aa160cea36fd8969e38eafb352154ee7d8f6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Tue, 9 Apr 2019 20:45:58 +0100 Subject: Adapt functions to work for external Knative Remove Kn services cache from Clusters::Application::Knative Knative function can exist even if user did not installed Knative via GitLab managed apps. -> Move responsibility of finding services into the Cluster -> Responsability is inside Clusters::Cluster::KnativeServiceFinder -> Projects::Serverless::FunctionsFinder now calls depends solely on a cluster to find the Kn services. -> Detect Knative by resource presence instead of service presence -> Mock knative_installed response temporarily for frontend to develop Display loader while `installed === 'checking'` Added frontend work to determine if Knative is installed Memoize with_reactive_cache(*args, &block) to avoid race conditions When calling with_reactive_cache more than once, it's possible that the second call will already have the value populated. Therefore, in cases where we need the sequential calls to have consistent results, we'd fall under a race condition. Check knative installation via Knative resource presence Only load pods if Knative is discovered Always return a response in FunctionsController#index - Always indicate if Knative is installed, not installed or checking - Always indicate the partial response for functions. Final response is guaranteed when knative_installed is either true | false. Adds specs for Clusters::Cluster#knative_services_finder Fix method name when calling on specs Add an explicit check for functions Added an explicit check to see if there are any functions available Fix Serverless feature spec - we don't find knative installation via database anymore, rather via Knative resource Display error message for request timeouts Display an error message if the request times out Adds feature specs for when functions exist Remove a test purposed hardcoded flag Add ability to partially load functions Added the ability to partially load functions on the frontend Add frontend unit tests Added tests for the new frontend additions Generate new translations Generated new frontend translations Address review comments Cleaned up the frontend unit test. Added computed prop for `isInstalled`. Move string to constant Simplify nil to array conversion Put knative_installed states in a frozen hash for better read Pluralize list of Knative states Quey services and pods filtering name This way we don't need to filter the namespace in memory. Also, the data we get from the network is much smaller. Simplify cache_key and fix bug - Simplifies the cache_key by removing namespace duplicate - Fixes a bug with reactive_cache memoization --- .../serverless/functions_controller_spec.rb | 92 +++++++++++++---- .../features/projects/serverless/functions_spec.rb | 59 ++++++++--- .../cluster/knative_services_finder_spec.rb | 105 ++++++++++++++++++++ .../projects/serverless/functions_finder_spec.rb | 68 ++++++++----- .../serverless/components/environment_row_spec.js | 8 +- .../serverless/components/functions_spec.js | 27 ++++- spec/frontend/serverless/mock_data.js | 110 +++++++++++---------- spec/frontend/serverless/store/getters_spec.js | 2 +- spec/frontend/serverless/store/mutations_spec.js | 4 +- spec/models/clusters/applications/knative_spec.rb | 76 -------------- spec/models/clusters/cluster_spec.rb | 5 + spec/support/helpers/kubernetes_helpers.rb | 46 ++++++--- 12 files changed, 397 insertions(+), 205 deletions(-) create mode 100644 spec/finders/clusters/cluster/knative_services_finder_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 782f5f272d9..18c594acae0 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -8,9 +8,8 @@ describe Projects::Serverless::FunctionsController do let(:user) { create(:user) } let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } let(:service) { cluster.platform_kubernetes } - let(:project) { cluster.project} + let(:project) { cluster.project } let(:namespace) do create(:cluster_kubernetes_namespace, @@ -30,17 +29,69 @@ describe Projects::Serverless::FunctionsController do end describe 'GET #index' do - context 'empty cache' do - it 'has no data' do + let(:expected_json) { { 'knative_installed' => knative_state, 'functions' => functions } } + + context 'when cache is being read' do + let(:knative_state) { 'checking' } + let(:functions) { [] } + + before do get :index, params: params({ format: :json }) + end - expect(response).to have_gitlab_http_status(204) + it 'returns checking' do + expect(json_response).to eq expected_json end - it 'renders an html page' do - get :index, params: params + it { expect(response).to have_gitlab_http_status(200) } + end + + context 'when cache is ready' do + let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } + let(:knative_state) { true } - expect(response).to have_gitlab_http_status(200) + before do + allow_any_instance_of(Clusters::Cluster) + .to receive(:knative_services_finder) + .and_return(knative_services_finder) + synchronous_reactive_cache(knative_services_finder) + stub_kubeclient_service_pods( + kube_response({ "kind" => "PodList", "items" => [] }), + namespace: namespace.namespace + ) + end + + context 'when no functions were found' do + let(:functions) { [] } + + before do + stub_kubeclient_knative_services( + namespace: namespace.namespace, + response: kube_response({ "kind" => "ServiceList", "items" => [] }) + ) + get :index, params: params({ format: :json }) + end + + it 'returns checking' do + expect(json_response).to eq expected_json + end + + it { expect(response).to have_gitlab_http_status(200) } + end + + context 'when functions were found' do + let(:functions) { ["asdf"] } + + before do + stub_kubeclient_knative_services(namespace: namespace.namespace) + get :index, params: params({ format: :json }) + end + + it 'returns functions' do + expect(json_response["functions"]).not_to be_empty + end + + it { expect(response).to have_gitlab_http_status(200) } end end end @@ -56,11 +107,12 @@ describe Projects::Serverless::FunctionsController do context 'valid data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods - stub_reactive_cache(knative, + stub_reactive_cache(cluster.knative_services_finder(project), { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }) + }, + *cluster.knative_services_finder(project).cache_args) end it 'has a valid function name' do @@ -88,11 +140,12 @@ describe Projects::Serverless::FunctionsController do describe 'GET #index with data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods - stub_reactive_cache(knative, + stub_reactive_cache(cluster.knative_services_finder(project), { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }) + }, + *cluster.knative_services_finder(project).cache_args) end it 'has data' do @@ -100,11 +153,16 @@ describe Projects::Serverless::FunctionsController do expect(response).to have_gitlab_http_status(200) - expect(json_response).to contain_exactly( - a_hash_including( - "name" => project.name, - "url" => "http://#{project.name}.#{namespace.namespace}.example.com" - ) + expect(json_response).to match( + { + "knative_installed" => "checking", + "functions" => [ + a_hash_including( + "name" => project.name, + "url" => "http://#{project.name}.#{namespace.namespace}.example.com" + ) + ] + } ) end diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb index e14934b1672..9865dbbfb3c 100644 --- a/spec/features/projects/serverless/functions_spec.rb +++ b/spec/features/projects/serverless/functions_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe 'Functions', :js do include KubernetesHelpers + include ReactiveCachingHelpers let(:project) { create(:project) } let(:user) { create(:user) } @@ -13,44 +14,70 @@ describe 'Functions', :js do gitlab_sign_in(user) end - context 'when user does not have a cluster and visits the serverless page' do + shared_examples "it's missing knative installation" do before do visit project_serverless_functions_path(project) end - it 'sees an empty state' do + it 'sees an empty state require Knative installation' do expect(page).to have_link('Install Knative') expect(page).to have_selector('.empty-state') end end + context 'when user does not have a cluster and visits the serverless page' do + it_behaves_like "it's missing knative installation" + end + context 'when the user does have a cluster and visits the serverless page' do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - before do - visit project_serverless_functions_path(project) - end - - it 'sees an empty state' do - expect(page).to have_link('Install Knative') - expect(page).to have_selector('.empty-state') - end + it_behaves_like "it's missing knative installation" end context 'when the user has a cluster and knative installed and visits the serverless page' do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:service) { cluster.platform_kubernetes } - let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } - let(:project) { knative.cluster.project } + let(:project) { cluster.project } + let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } + let(:namespace) do + create(:cluster_kubernetes_namespace, + cluster: cluster, + cluster_project: cluster.cluster_project, + project: cluster.cluster_project.project) + end before do - stub_kubeclient_knative_services - stub_kubeclient_service_pods + allow_any_instance_of(Clusters::Cluster) + .to receive(:knative_services_finder) + .and_return(knative_services_finder) + synchronous_reactive_cache(knative_services_finder) + stub_kubeclient_knative_services(stub_get_services_options) + stub_kubeclient_service_pods(nil, namespace: namespace.namespace) visit project_serverless_functions_path(project) end - it 'sees an empty listing of serverless functions' do - expect(page).to have_selector('.empty-state') + context 'when there are no functions' do + let(:stub_get_services_options) do + { + namespace: namespace.namespace, + response: kube_response({ "kind" => "ServiceList", "items" => [] }) + } + end + + it 'sees an empty listing of serverless functions' do + expect(page).to have_selector('.empty-state') + expect(page).not_to have_selector('.content-list') + end + end + + context 'when there are functions' do + let(:stub_get_services_options) { { namespace: namespace.namespace } } + + it 'does not see an empty listing of serverless functions' do + expect(page).not_to have_selector('.empty-state') + expect(page).to have_selector('.content-list') + end end end end diff --git a/spec/finders/clusters/cluster/knative_services_finder_spec.rb b/spec/finders/clusters/cluster/knative_services_finder_spec.rb new file mode 100644 index 00000000000..277200d06f4 --- /dev/null +++ b/spec/finders/clusters/cluster/knative_services_finder_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Cluster::KnativeServicesFinder do + include KubernetesHelpers + include ReactiveCachingHelpers + + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:service) { cluster.platform_kubernetes } + let(:project) { cluster.cluster_project.project } + let(:namespace) do + create(:cluster_kubernetes_namespace, + cluster: cluster, + cluster_project: cluster.cluster_project, + project: project) + end + + before do + stub_kubeclient_knative_services(namespace: namespace.namespace) + stub_kubeclient_service_pods( + kube_response( + kube_knative_pods_body( + project.name, namespace.namespace + ) + ), + namespace: namespace.namespace + ) + end + + shared_examples 'a cached data' do + it 'has an unintialized cache' do + is_expected.to be_blank + end + + context 'when using synchronous reactive cache' do + before do + synchronous_reactive_cache(cluster.knative_services_finder(project)) + end + + context 'when there are functions for cluster namespace' do + it { is_expected.not_to be_blank } + end + + context 'when there are no functions for cluster namespace' do + before do + stub_kubeclient_knative_services( + namespace: namespace.namespace, + response: kube_response({ "kind" => "ServiceList", "items" => [] }) + ) + stub_kubeclient_service_pods( + kube_response({ "kind" => "PodList", "items" => [] }), + namespace: namespace.namespace + ) + end + + it { is_expected.to be_blank } + end + end + end + + describe '#service_pod_details' do + subject { cluster.knative_services_finder(project).service_pod_details(project.name) } + + it_behaves_like 'a cached data' + end + + describe '#services' do + subject { cluster.knative_services_finder(project).services } + + it_behaves_like 'a cached data' + end + + describe '#knative_detected' do + subject { cluster.knative_services_finder(project).knative_detected } + before do + synchronous_reactive_cache(cluster.knative_services_finder(project)) + end + + context 'when knative is installed' do + before do + stub_kubeclient_discover(service.api_url) + end + + it { is_expected.to be_truthy } + it "discovers knative installation" do + expect { subject } + .to change { cluster.kubeclient.knative_client.discovered } + .from(false) + .to(true) + end + end + + context 'when knative is not installed' do + before do + stub_kubeclient_discover_knative_not_found(service.api_url) + end + + it { is_expected.to be_falsy } + it "does not discover knative installation" do + expect { subject }.not_to change { cluster.kubeclient.knative_client.discovered } + end + end + end +end diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb index 3ad38207da4..8aea45b457c 100644 --- a/spec/finders/projects/serverless/functions_finder_spec.rb +++ b/spec/finders/projects/serverless/functions_finder_spec.rb @@ -10,7 +10,7 @@ describe Projects::Serverless::FunctionsFinder do let(:user) { create(:user) } let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:service) { cluster.platform_kubernetes } - let(:project) { cluster.project} + let(:project) { cluster.project } let(:namespace) do create(:cluster_kubernetes_namespace, @@ -23,9 +23,45 @@ describe Projects::Serverless::FunctionsFinder do project.add_maintainer(user) end + describe '#installed' do + it 'when reactive_caching is still fetching data' do + expect(described_class.new(project).knative_installed).to eq 'checking' + end + + context 'when reactive_caching has finished' do + let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } + + before do + allow_any_instance_of(Clusters::Cluster) + .to receive(:knative_services_finder) + .and_return(knative_services_finder) + synchronous_reactive_cache(knative_services_finder) + end + + context 'when knative is not installed' do + it 'returns false' do + stub_kubeclient_discover_knative_not_found(service.api_url) + + expect(described_class.new(project).knative_installed).to eq false + end + end + + context 'reactive_caching is finished and knative is installed' do + let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } + + it 'returns true' do + stub_kubeclient_knative_services(namespace: namespace.namespace) + stub_kubeclient_service_pods(nil, namespace: namespace.namespace) + + expect(described_class.new(project).knative_installed).to be true + end + end + end + end + describe 'retrieve data from knative' do - it 'does not have knative installed' do - expect(described_class.new(project).execute).to be_empty + context 'does not have knative installed' do + it { expect(described_class.new(project).execute).to be_empty } end context 'has knative installed' do @@ -38,22 +74,24 @@ describe Projects::Serverless::FunctionsFinder do it 'there are functions', :use_clean_rails_memory_store_caching do stub_kubeclient_service_pods - stub_reactive_cache(knative, + stub_reactive_cache(cluster.knative_services_finder(project), { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }) + }, + *cluster.knative_services_finder(project).cache_args) expect(finder.execute).not_to be_empty end it 'has a function', :use_clean_rails_memory_store_caching do stub_kubeclient_service_pods - stub_reactive_cache(knative, + stub_reactive_cache(cluster.knative_services_finder(project), { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }) + }, + *cluster.knative_services_finder(project).cache_args) result = finder.service(cluster.environment_scope, cluster.project.name) expect(result).not_to be_empty @@ -84,20 +122,4 @@ describe Projects::Serverless::FunctionsFinder do end end end - - describe 'verify if knative is installed' do - context 'knative is not installed' do - it 'does not have knative installed' do - expect(described_class.new(project).installed?).to be false - end - end - - context 'knative is installed' do - let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } - - it 'does have knative installed' do - expect(described_class.new(project).installed?).to be true - end - end - end end diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js index 161a637dd75..0ad85e218dc 100644 --- a/spec/frontend/serverless/components/environment_row_spec.js +++ b/spec/frontend/serverless/components/environment_row_spec.js @@ -14,7 +14,7 @@ describe('environment row component', () => { beforeEach(() => { localVue = createLocalVue(); - vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*'); + vm = createComponent(localVue, translate(mockServerlessFunctions.functions)['*'], '*'); }); afterEach(() => vm.$destroy()); @@ -48,7 +48,11 @@ describe('environment row component', () => { beforeEach(() => { localVue = createLocalVue(); - vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test'); + vm = createComponent( + localVue, + translate(mockServerlessFunctionsDiffEnv.functions).test, + 'test', + ); }); afterEach(() => vm.$destroy()); diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js index 6924fb9e91f..d8a80f8031e 100644 --- a/spec/frontend/serverless/components/functions_spec.js +++ b/spec/frontend/serverless/components/functions_spec.js @@ -34,11 +34,11 @@ describe('functionsComponent', () => { }); it('should render empty state when Knative is not installed', () => { + store.dispatch('receiveFunctionsSuccess', { knative_installed: false }); component = shallowMount(functionsComponent, { localVue, store, propsData: { - installed: false, clustersPath: '', helpPath: '', statusPath: '', @@ -55,7 +55,6 @@ describe('functionsComponent', () => { localVue, store, propsData: { - installed: true, clustersPath: '', helpPath: '', statusPath: '', @@ -67,12 +66,11 @@ describe('functionsComponent', () => { }); it('should render empty state when there is no function data', () => { - store.dispatch('receiveFunctionsNoDataSuccess'); + store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true }); component = shallowMount(functionsComponent, { localVue, store, propsData: { - installed: true, clustersPath: '', helpPath: '', statusPath: '', @@ -91,12 +89,31 @@ describe('functionsComponent', () => { ); }); + it('should render functions and a loader when functions are partially fetched', () => { + store.dispatch('receiveFunctionsPartial', { + ...mockServerlessFunctions, + knative_installed: 'checking', + }); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect(component.find('.js-functions-wrapper').exists()).toBe(true); + expect(component.find('.js-functions-loader').exists()).toBe(true); + }); + it('should render the functions list', () => { component = shallowMount(functionsComponent, { localVue, store, propsData: { - installed: true, clustersPath: 'clustersPath', helpPath: 'helpPath', statusPath, diff --git a/spec/frontend/serverless/mock_data.js b/spec/frontend/serverless/mock_data.js index a2c18616324..ef616ceb37f 100644 --- a/spec/frontend/serverless/mock_data.js +++ b/spec/frontend/serverless/mock_data.js @@ -1,56 +1,62 @@ -export const mockServerlessFunctions = [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, -]; +export const mockServerlessFunctions = { + knative_installed: true, + functions: [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, + ], +}; -export const mockServerlessFunctionsDiffEnv = [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: 'test', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, -]; +export const mockServerlessFunctionsDiffEnv = { + knative_installed: true, + functions: [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: 'test', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, + ], +}; export const mockServerlessFunction = { name: 'testfunc1', diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js index fb549c8f153..92853fda37c 100644 --- a/spec/frontend/serverless/store/getters_spec.js +++ b/spec/frontend/serverless/store/getters_spec.js @@ -32,7 +32,7 @@ describe('Serverless Store Getters', () => { describe('getFunctions', () => { it('should translate the raw function array to group the functions per environment scope', () => { - state.functions = mockServerlessFunctions; + state.functions = mockServerlessFunctions.functions; const funcs = getters.getFunctions(state); diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js index ca3053e5c38..e2771c7e5fd 100644 --- a/spec/frontend/serverless/store/mutations_spec.js +++ b/spec/frontend/serverless/store/mutations_spec.js @@ -19,13 +19,13 @@ describe('ServerlessMutations', () => { expect(state.isLoading).toEqual(false); expect(state.hasFunctionData).toEqual(true); - expect(state.functions).toEqual(mockServerlessFunctions); + expect(state.functions).toEqual(mockServerlessFunctions.functions); }); it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { const state = {}; - mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state); + mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, { knative_installed: true }); expect(state.isLoading).toEqual(false); expect(state.hasFunctionData).toEqual(false); diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index d5974f47190..b38cf96de7e 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -3,9 +3,6 @@ require 'rails_helper' describe Clusters::Applications::Knative do - include KubernetesHelpers - include ReactiveCachingHelpers - let(:knative) { create(:clusters_applications_knative) } include_examples 'cluster application core specs', :clusters_applications_knative @@ -146,77 +143,4 @@ describe Clusters::Applications::Knative do describe 'validations' do it { is_expected.to validate_presence_of(:hostname) } end - - describe '#service_pod_details' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:service) { cluster.platform_kubernetes } - let(:knative) { create(:clusters_applications_knative, cluster: cluster) } - - let(:namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) - end - - before do - stub_kubeclient_discover(service.api_url) - stub_kubeclient_knative_services - stub_kubeclient_service_pods - stub_reactive_cache(knative, - { - services: kube_response(kube_knative_services_body), - pods: kube_response(kube_knative_pods_body(cluster.cluster_project.project.name, namespace.namespace)) - }) - synchronous_reactive_cache(knative) - end - - it 'is able k8s core for pod details' do - expect(knative.service_pod_details(namespace.namespace, cluster.cluster_project.project.name)).not_to be_nil - end - end - - describe '#services' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:service) { cluster.platform_kubernetes } - let(:knative) { create(:clusters_applications_knative, cluster: cluster) } - - let(:namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) - end - - subject { knative.services } - - before do - stub_kubeclient_discover(service.api_url) - stub_kubeclient_knative_services - stub_kubeclient_service_pods - end - - it 'has an unintialized cache' do - is_expected.to be_nil - end - - context 'when using synchronous reactive cache' do - before do - stub_reactive_cache(knative, - { - services: kube_response(kube_knative_services_body), - pods: kube_response(kube_knative_pods_body(cluster.cluster_project.project.name, namespace.namespace)) - }) - synchronous_reactive_cache(knative) - end - - it 'has cached services' do - is_expected.not_to be_nil - end - - it 'matches our namespace' do - expect(knative.services_for(ns: namespace)).not_to be_nil - end - end - end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 4739e62289a..60a19ccd48a 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -38,6 +38,11 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do it { is_expected.to respond_to :project } + it do + expect(subject.knative_services_finder(subject.project)) + .to be_instance_of(Clusters::Cluster::KnativeServicesFinder) + end + describe '.enabled' do subject { described_class.enabled } diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index 78b7ae9c00c..011c4df0fe5 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -17,17 +17,38 @@ module KubernetesHelpers kube_response(kube_deployments_body) end - def stub_kubeclient_discover(api_url) + def stub_kubeclient_discover_base(api_url) WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body)) - WebMock.stub_request(:get, api_url + '/apis/extensions/v1beta1').to_return(kube_response(kube_v1beta1_discovery_body)) - WebMock.stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1').to_return(kube_response(kube_v1_rbac_authorization_discovery_body)) - WebMock.stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1').to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body)) + WebMock + .stub_request(:get, api_url + '/apis/extensions/v1beta1') + .to_return(kube_response(kube_v1beta1_discovery_body)) + WebMock + .stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1') + .to_return(kube_response(kube_v1_rbac_authorization_discovery_body)) + end + + def stub_kubeclient_discover(api_url) + stub_kubeclient_discover_base(api_url) + + WebMock + .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1') + .to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body)) + end + + def stub_kubeclient_discover_knative_not_found(api_url) + stub_kubeclient_discover_base(api_url) + + WebMock + .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1') + .to_return(status: [404, "Resource Not Found"]) end - def stub_kubeclient_service_pods(status: nil) + def stub_kubeclient_service_pods(response = nil, options = {}) stub_kubeclient_discover(service.api_url) - pods_url = service.api_url + "/api/v1/pods" - response = { status: status } if status + + namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : "" + + pods_url = service.api_url + "/api/v1/#{namespace_path}pods" WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) end @@ -56,15 +77,18 @@ module KubernetesHelpers WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response) end - def stub_kubeclient_knative_services(**options) + def stub_kubeclient_knative_services(options = {}) + namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : "" + options[:name] ||= "kubetest" - options[:namespace] ||= "default" options[:domain] ||= "example.com" + options[:response] ||= kube_response(kube_knative_services_body(options)) stub_kubeclient_discover(service.api_url) - knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/services" - WebMock.stub_request(:get, knative_url).to_return(kube_response(kube_knative_services_body(options))) + knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/#{namespace_path}services" + + WebMock.stub_request(:get, knative_url).to_return(options[:response]) end def stub_kubeclient_get_secret(api_url, **options) -- cgit v1.2.1 From 495f11e7d5bfb4b2c32a425d80fe9cb6485af18d Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Tue, 28 May 2019 15:38:15 +1000 Subject: Fix warning in spec from committing unused mutation --- spec/javascripts/monitoring/dashboard_spec.js | 8 -------- 1 file changed, 8 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 58bcd916739..6e16ab64be2 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -176,10 +176,6 @@ describe('Dashboard', () => { store, }); - component.$store.commit( - `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`, - '/environments', - ); component.$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, []); component.$store.commit( `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, @@ -210,10 +206,6 @@ describe('Dashboard', () => { store, }); - component.$store.commit( - `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`, - '/environments', - ); component.$store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData, -- cgit v1.2.1 From 3d4821a8e76d49b388b218824714d3bcb8c54dbf Mon Sep 17 00:00:00 2001 From: Igor Drozdov Date: Thu, 11 Apr 2019 18:26:16 +0300 Subject: Hide password on import by url form --- .../controllers/concerns/import_url_params_spec.rb | 44 ++++++++++++++++++++++ .../projects/imports_controller_spec.rb | 15 ++++++++ spec/lib/gitlab/url_sanitizer_spec.rb | 34 +++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 spec/controllers/concerns/import_url_params_spec.rb (limited to 'spec') diff --git a/spec/controllers/concerns/import_url_params_spec.rb b/spec/controllers/concerns/import_url_params_spec.rb new file mode 100644 index 00000000000..fc5dfb5263f --- /dev/null +++ b/spec/controllers/concerns/import_url_params_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ImportUrlParams do + let(:import_url_params) do + controller = OpenStruct.new(params: params).extend(described_class) + controller.import_url_params + end + + context 'url and password separately provided' do + let(:params) do + ActionController::Parameters.new(project: { + import_url: 'https://url.com', + import_url_user: 'user', import_url_password: 'password' + }) + end + + describe '#import_url_params' do + it 'returns hash with import_url' do + expect(import_url_params).to eq( + import_url: "https://user:password@url.com" + ) + end + end + end + + context 'url with provided empty credentials' do + let(:params) do + ActionController::Parameters.new(project: { + import_url: 'https://user:password@url.com', + import_url_user: '', import_url_password: '' + }) + end + + describe '#import_url_params' do + it 'does not change the url' do + expect(import_url_params).to eq( + import_url: "https://user:password@url.com" + ) + end + end + end +end diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 8d88ee7dfd6..bdc81efe3bc 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -122,4 +122,19 @@ describe Projects::ImportsController do end end end + + describe 'POST #create' do + let(:params) { { import_url: 'https://github.com/vim/vim.git', import_url_user: 'user', import_url_password: 'password' } } + let(:project) { create(:project) } + + before do + allow(RepositoryImportWorker).to receive(:perform_async) + + post :create, params: { project: params, namespace_id: project.namespace.to_param, project_id: project } + end + + it 'sets import_url to the project' do + expect(project.reload.import_url).to eq('https://user:password@github.com/vim/vim.git') + end + end end diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 5861e6955a6..7242255d535 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -115,6 +115,40 @@ describe Gitlab::UrlSanitizer do end end + describe '#user' do + context 'credentials in hash' do + it 'overrides URL-provided user' do + sanitizer = described_class.new('http://a:b@example.com', credentials: { user: 'c', password: 'd' }) + + expect(sanitizer.user).to eq('c') + end + end + + context 'credentials in URL' do + where(:url, :user) do + 'http://foo:bar@example.com' | 'foo' + 'http://foo:bar:baz@example.com' | 'foo' + 'http://:bar@example.com' | nil + 'http://foo:@example.com' | 'foo' + 'http://foo@example.com' | 'foo' + 'http://:@example.com' | nil + 'http://@example.com' | nil + 'http://example.com' | nil + + # Other invalid URLs + nil | nil + '' | nil + 'no' | nil + end + + with_them do + subject { described_class.new(url).user } + + it { is_expected.to eq(user) } + end + end + end + describe '#full_url' do context 'credentials in hash' do where(:credentials, :userinfo) do -- cgit v1.2.1 From c7903542683eaa5427a5d30adad8550f0754bdfa Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Sat, 13 Apr 2019 12:00:10 -0400 Subject: Handling password on import by url page --- spec/javascripts/projects/project_new_spec.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js index b61e0ac872f..106a3ba94e4 100644 --- a/spec/javascripts/projects/project_new_spec.js +++ b/spec/javascripts/projects/project_new_spec.js @@ -10,7 +10,17 @@ describe('New Project', () => { setFixtures(`
      - +
      + +
      +
      +
      + +
      +
      + +
      +
      @@ -119,7 +129,7 @@ describe('New Project', () => { }); it('changes project path for HTTPS URL in $projectImportUrl', () => { - $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git'); + $projectImportUrl.val('https://gitlab.company.com/group/project.git'); projectNew.deriveProjectPathFromUrl($projectImportUrl); -- cgit v1.2.1 From 0d7afb9561eae91b86f9fcc3a2a6d54ac527b177 Mon Sep 17 00:00:00 2001 From: Denys Mishunov Date: Wed, 29 May 2019 12:13:27 +0000 Subject: Updated heading for default state in Web IDE Resolves https://gitlab.com/gitlab-org/gitlab-ce/issues/60107 --- spec/frontend/ide/stores/mutations/branch_spec.js | 35 ++++ spec/frontend/ide/stores/mutations/project_spec.js | 23 +++ spec/javascripts/ide/components/ide_spec.js | 92 +++++++--- .../ide/components/ide_tree_list_spec.js | 59 ++++-- .../javascripts/ide/stores/actions/project_spec.js | 203 +++++++++++++-------- spec/javascripts/ide/stores/actions/tree_spec.js | 32 ---- spec/javascripts/ide/stores/actions_spec.js | 64 +++++++ .../ide/stores/modules/commit/actions_spec.js | 59 ++++++ 8 files changed, 416 insertions(+), 151 deletions(-) create mode 100644 spec/frontend/ide/stores/mutations/project_spec.js (limited to 'spec') diff --git a/spec/frontend/ide/stores/mutations/branch_spec.js b/spec/frontend/ide/stores/mutations/branch_spec.js index 29eb859ddaf..0900b25d5d3 100644 --- a/spec/frontend/ide/stores/mutations/branch_spec.js +++ b/spec/frontend/ide/stores/mutations/branch_spec.js @@ -37,4 +37,39 @@ describe('Multi-file store branch mutations', () => { expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit'); }); }); + + describe('SET_BRANCH_WORKING_REFERENCE', () => { + beforeEach(() => { + localState.projects = { + Foo: { + branches: { + bar: {}, + }, + }, + }; + }); + + it('sets workingReference for existing branch', () => { + mutations.SET_BRANCH_WORKING_REFERENCE(localState, { + projectId: 'Foo', + branchId: 'bar', + reference: 'foo-bar-ref', + }); + + expect(localState.projects.Foo.branches.bar.workingReference).toBe('foo-bar-ref'); + }); + + it('does not fail on non-existent just yet branch', () => { + expect(localState.projects.Foo.branches.unknown).toBeUndefined(); + + mutations.SET_BRANCH_WORKING_REFERENCE(localState, { + projectId: 'Foo', + branchId: 'unknown', + reference: 'fun-fun-ref', + }); + + expect(localState.projects.Foo.branches.unknown).not.toBeUndefined(); + expect(localState.projects.Foo.branches.unknown.workingReference).toBe('fun-fun-ref'); + }); + }); }); diff --git a/spec/frontend/ide/stores/mutations/project_spec.js b/spec/frontend/ide/stores/mutations/project_spec.js new file mode 100644 index 00000000000..b3ce39c33d2 --- /dev/null +++ b/spec/frontend/ide/stores/mutations/project_spec.js @@ -0,0 +1,23 @@ +import mutations from '~/ide/stores/mutations/project'; +import state from '~/ide/stores/state'; + +describe('Multi-file store branch mutations', () => { + let localState; + + beforeEach(() => { + localState = state(); + localState.projects = { abcproject: { empty_repo: true } }; + }); + + describe('TOGGLE_EMPTY_STATE', () => { + it('sets empty_repo for project to passed value', () => { + mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: false }); + + expect(localState.projects.abcproject.empty_repo).toBe(false); + + mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: true }); + + expect(localState.projects.abcproject.empty_repo).toBe(true); + }); + }); +}); diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index dc5790f6562..de4becec1cd 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -5,21 +5,53 @@ import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helpe import { file, resetStore } from '../helpers'; import { projectData } from '../mock_data'; -describe('ide component', () => { +function bootstrap(projData) { + const Component = Vue.extend(ide); + + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = Object.assign({}, projData); + Vue.set(store.state.trees, 'abcproject/master', { + tree: [], + loading: false, + }); + + return createComponentWithStore(Component, store, { + emptyStateSvgPath: 'svg', + noChangesStateSvgPath: 'svg', + committedStateSvgPath: 'svg', + }); +} + +describe('ide component, empty repo', () => { let vm; beforeEach(() => { - const Component = Vue.extend(ide); + const emptyProjData = Object.assign({}, projectData, { empty_repo: true, branches: {} }); + vm = bootstrap(emptyProjData); + vm.$mount(); + }); - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = Object.assign({}, projectData); + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); - vm = createComponentWithStore(Component, store, { - emptyStateSvgPath: 'svg', - noChangesStateSvgPath: 'svg', - committedStateSvgPath: 'svg', - }).$mount(); + it('renders "New file" button in empty repo', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).not.toBeNull(); + done(); + }); + }); +}); + +describe('ide component, non-empty repo', () => { + let vm; + + beforeEach(() => { + vm = bootstrap(projectData); + vm.$mount(); }); afterEach(() => { @@ -28,17 +60,15 @@ describe('ide component', () => { resetStore(vm.$store); }); - it('does not render right when no files open', () => { - expect(vm.$el.querySelector('.panel-right')).toBeNull(); - }); + it('shows error message when set', done => { + expect(vm.$el.querySelector('.flash-container')).toBe(null); - it('renders right panel when files are open', done => { - vm.$store.state.trees['abcproject/mybranch'] = { - tree: [file()], + vm.$store.state.errorMessage = { + text: 'error', }; - Vue.nextTick(() => { - expect(vm.$el.querySelector('.panel-right')).toBeNull(); + vm.$nextTick(() => { + expect(vm.$el.querySelector('.flash-container')).not.toBe(null); done(); }); @@ -71,17 +101,25 @@ describe('ide component', () => { }); }); - it('shows error message when set', done => { - expect(vm.$el.querySelector('.flash-container')).toBe(null); - - vm.$store.state.errorMessage = { - text: 'error', - }; + describe('non-existent branch', () => { + it('does not render "New file" button for non-existent branch when repo is not empty', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull(); + done(); + }); + }); + }); - vm.$nextTick(() => { - expect(vm.$el.querySelector('.flash-container')).not.toBe(null); + describe('branch with files', () => { + beforeEach(() => { + store.state.trees['abcproject/master'].tree = [file()]; + }); - done(); + it('does not render "New file" button', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull(); + done(); + }); }); }); }); diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js index 4ecbdb8a55e..f63007c7dd2 100644 --- a/spec/javascripts/ide/components/ide_tree_list_spec.js +++ b/spec/javascripts/ide/components/ide_tree_list_spec.js @@ -7,25 +7,23 @@ import { projectData } from '../mock_data'; describe('IDE tree list', () => { const Component = Vue.extend(IdeTreeList); + const normalBranchTree = [file('fileName')]; + const emptyBranchTree = []; let vm; - beforeEach(() => { + const bootstrapWithTree = (tree = normalBranchTree) => { store.state.currentProjectId = 'abcproject'; store.state.currentBranchId = 'master'; store.state.projects.abcproject = Object.assign({}, projectData); Vue.set(store.state.trees, 'abcproject/master', { - tree: [file('fileName')], + tree, loading: false, }); vm = createComponentWithStore(Component, store, { viewerType: 'edit', }); - - spyOn(vm, 'updateViewer').and.callThrough(); - - vm.$mount(); - }); + }; afterEach(() => { vm.$destroy(); @@ -33,22 +31,47 @@ describe('IDE tree list', () => { resetStore(vm.$store); }); - it('updates viewer on mount', () => { - expect(vm.updateViewer).toHaveBeenCalledWith('edit'); - }); + describe('normal branch', () => { + beforeEach(() => { + bootstrapWithTree(); + + spyOn(vm, 'updateViewer').and.callThrough(); + + vm.$mount(); + }); + + it('updates viewer on mount', () => { + expect(vm.updateViewer).toHaveBeenCalledWith('edit'); + }); + + it('renders loading indicator', done => { + store.state.trees['abcproject/master'].loading = true; - it('renders loading indicator', done => { - store.state.trees['abcproject/master'].loading = true; + vm.$nextTick(() => { + expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull(); + expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3); - vm.$nextTick(() => { - expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull(); - expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3); + done(); + }); + }); - done(); + it('renders list of files', () => { + expect(vm.$el.textContent).toContain('fileName'); }); }); - it('renders list of files', () => { - expect(vm.$el.textContent).toContain('fileName'); + describe('empty-branch state', () => { + beforeEach(() => { + bootstrapWithTree(emptyBranchTree); + + spyOn(vm, 'updateViewer').and.callThrough(); + + vm.$mount(); + }); + + it('does not load files if the branch is empty', () => { + expect(vm.$el.textContent).not.toContain('fileName'); + expect(vm.$el.textContent).toContain('No files'); + }); }); }); diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index cd519eaed7c..8ecb6129c63 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -4,7 +4,7 @@ import { refreshLastCommitData, showBranchNotFoundError, createNewBranchFromDefault, - getBranchData, + showEmptyState, openBranch, } from '~/ide/stores/actions'; import store from '~/ide/stores'; @@ -196,39 +196,44 @@ describe('IDE store project actions', () => { }); }); - describe('getBranchData', () => { - describe('error', () => { - it('dispatches branch not found action when response is 404', done => { - const dispatch = jasmine.createSpy('dispatchSpy'); - - mock.onGet(/(.*)/).replyOnce(404); - - getBranchData( + describe('showEmptyState', () => { + it('commits proper mutations when supplied error is 404', done => { + testAction( + showEmptyState, + { + err: { + response: { + status: 404, + }, + }, + projectId: 'abc/def', + branchId: 'master', + }, + store.state, + [ { - commit() {}, - dispatch, - state: store.state, + type: 'CREATE_TREE', + payload: { + treePath: 'abc/def/master', + }, }, { - projectId: 'abc/def', - branchId: 'master-testing', + type: 'TOGGLE_LOADING', + payload: { + entry: store.state.trees['abc/def/master'], + forceValue: false, + }, }, - ) - .then(done.fail) - .catch(() => { - expect(dispatch.calls.argsFor(0)).toEqual([ - 'showBranchNotFoundError', - 'master-testing', - ]); - done(); - }); - }); + ], + [], + done, + ); }); }); describe('openBranch', () => { const branch = { - projectId: 'feature/lorem-ipsum', + projectId: 'abc/def', branchId: '123-lorem', }; @@ -238,63 +243,113 @@ describe('IDE store project actions', () => { 'foo/bar-pending': { pending: true }, 'foo/bar': { pending: false }, }; - - spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); }); - it('dispatches branch actions', done => { - openBranch(store, branch) - .then(() => { - expect(store.dispatch.calls.allArgs()).toEqual([ - ['setCurrentBranchId', branch.branchId], - ['getBranchData', branch], - ['getFiles', branch], - ['getMergeRequestsForBranch', branch], - ]); - }) - .then(done) - .catch(done.fail); - }); + describe('empty repo', () => { + beforeEach(() => { + spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); - it('handles tree entry action, if basePath is given', done => { - openBranch(store, { ...branch, basePath: 'foo/bar/' }) - .then(() => { - expect(store.dispatch).toHaveBeenCalledWith( - 'handleTreeEntryAction', - store.state.entries['foo/bar'], - ); - }) - .then(done) - .catch(done.fail); + store.state.currentProjectId = 'abc/def'; + store.state.projects['abc/def'] = { + empty_repo: true, + }; + }); + + afterEach(() => { + resetStore(store); + }); + + it('dispatches showEmptyState action right away', done => { + openBranch(store, branch) + .then(() => { + expect(store.dispatch.calls.allArgs()).toEqual([ + ['setCurrentBranchId', branch.branchId], + ['showEmptyState', branch], + ]); + done(); + }) + .catch(done.fail); + }); }); - it('does not handle tree entry action, if entry is pending', done => { - openBranch(store, { ...branch, basePath: 'foo/bar-pending' }) - .then(() => { - expect(store.dispatch).not.toHaveBeenCalledWith( - 'handleTreeEntryAction', - jasmine.anything(), - ); - }) - .then(done) - .catch(done.fail); + describe('existing branch', () => { + beforeEach(() => { + spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); + }); + + it('dispatches branch actions', done => { + openBranch(store, branch) + .then(() => { + expect(store.dispatch.calls.allArgs()).toEqual([ + ['setCurrentBranchId', branch.branchId], + ['getBranchData', branch], + ['getMergeRequestsForBranch', branch], + ['getFiles', branch], + ]); + }) + .then(done) + .catch(done.fail); + }); + + it('handles tree entry action, if basePath is given', done => { + openBranch(store, { ...branch, basePath: 'foo/bar/' }) + .then(() => { + expect(store.dispatch).toHaveBeenCalledWith( + 'handleTreeEntryAction', + store.state.entries['foo/bar'], + ); + }) + .then(done) + .catch(done.fail); + }); + + it('does not handle tree entry action, if entry is pending', done => { + openBranch(store, { ...branch, basePath: 'foo/bar-pending' }) + .then(() => { + expect(store.dispatch).not.toHaveBeenCalledWith( + 'handleTreeEntryAction', + jasmine.anything(), + ); + }) + .then(done) + .catch(done.fail); + }); + + it('creates a new file supplied via URL if the file does not exist yet', done => { + openBranch(store, { ...branch, basePath: 'not-existent.md' }) + .then(() => { + expect(store.dispatch).not.toHaveBeenCalledWith( + 'handleTreeEntryAction', + jasmine.anything(), + ); + + expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', { + name: 'not-existent.md', + type: 'blob', + }); + }) + .then(done) + .catch(done.fail); + }); }); - it('creates a new file supplied via URL if the file does not exist yet', done => { - openBranch(store, { ...branch, basePath: 'not-existent.md' }) - .then(() => { - expect(store.dispatch).not.toHaveBeenCalledWith( - 'handleTreeEntryAction', - jasmine.anything(), - ); + describe('non-existent branch', () => { + beforeEach(() => { + spyOn(store, 'dispatch').and.returnValue(Promise.reject()); + }); - expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', { - name: 'not-existent.md', - type: 'blob', - }); - }) - .then(done) - .catch(done.fail); + it('dispatches correct branch actions', done => { + openBranch(store, branch) + .then(() => { + expect(store.dispatch.calls.allArgs()).toEqual([ + ['setCurrentBranchId', branch.branchId], + ['getBranchData', branch], + ['showBranchNotFoundError', branch.branchId], + ]); + }) + .then(done) + .catch(done.fail); + }); }); }); }); diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js index 5ed9b9003a7..674ecdc6764 100644 --- a/spec/javascripts/ide/stores/actions/tree_spec.js +++ b/spec/javascripts/ide/stores/actions/tree_spec.js @@ -93,38 +93,6 @@ describe('Multi-file store tree actions', () => { }); describe('error', () => { - it('dispatches branch not found actions when response is 404', done => { - const dispatch = jasmine.createSpy('dispatchSpy'); - - store.state.projects = { - 'abc/def': { - web_url: `${gl.TEST_HOST}/files`, - }, - }; - - mock.onGet(/(.*)/).replyOnce(404); - - getFiles( - { - commit() {}, - dispatch, - state: store.state, - }, - { - projectId: 'abc/def', - branchId: 'master-testing', - }, - ) - .then(done.fail) - .catch(() => { - expect(dispatch.calls.argsFor(0)).toEqual([ - 'showBranchNotFoundError', - 'master-testing', - ]); - done(); - }); - }); - it('dispatches error action', done => { const dispatch = jasmine.createSpy('dispatchSpy'); diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 0b5587d02ae..04e236fb042 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -9,12 +9,15 @@ import actions, { setErrorMessage, deleteEntry, renameEntry, + getBranchData, } from '~/ide/stores/actions'; +import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; import * as types from '~/ide/stores/mutation_types'; import router from '~/ide/ide_router'; import { resetStore, file } from '../helpers'; import testAction from '../../helpers/vuex_action_helper'; +import MockAdapter from 'axios-mock-adapter'; describe('Multi-file store actions', () => { beforeEach(() => { @@ -560,4 +563,65 @@ describe('Multi-file store actions', () => { ); }); }); + + describe('getBranchData', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('error', () => { + let dispatch; + const callParams = [ + { + commit() {}, + state: store.state, + }, + { + projectId: 'abc/def', + branchId: 'master-testing', + }, + ]; + + beforeEach(() => { + dispatch = jasmine.createSpy('dispatchSpy'); + document.body.innerHTML += '
      '; + }); + + afterEach(() => { + document.querySelector('.flash-container').remove(); + }); + + it('passes the error further unchanged without dispatching any action when response is 404', done => { + mock.onGet(/(.*)/).replyOnce(404); + + getBranchData(...callParams) + .then(done.fail) + .catch(e => { + expect(dispatch.calls.count()).toEqual(0); + expect(e.response.status).toEqual(404); + expect(document.querySelector('.flash-alert')).toBeNull(); + done(); + }); + }); + + it('does not pass the error further and flashes an alert if error is not 404', done => { + mock.onGet(/(.*)/).replyOnce(418); + + getBranchData(...callParams) + .then(done.fail) + .catch(e => { + expect(dispatch.calls.count()).toEqual(0); + expect(e.response).toBeUndefined(); + expect(document.querySelector('.flash-alert')).not.toBeNull(); + done(); + }); + }); + }); + }); }); diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index cdeb9b4b896..4413a12fac4 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -272,6 +272,7 @@ describe('IDE commit module actions', () => { short_id: '123', message: 'test message', committed_date: 'date', + parent_ids: '321', stats: { additions: '1', deletions: '2', @@ -463,5 +464,63 @@ describe('IDE commit module actions', () => { .catch(done.fail); }); }); + + describe('first commit of a branch', () => { + const COMMIT_RESPONSE = { + id: '123456', + short_id: '123', + message: 'test message', + committed_date: 'date', + parent_ids: [], + stats: { + additions: '1', + deletions: '2', + }, + }; + + it('commits TOGGLE_EMPTY_STATE mutation on empty repo', done => { + spyOn(service, 'commit').and.returnValue( + Promise.resolve({ + data: COMMIT_RESPONSE, + }), + ); + + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/commitChanges') + .then(() => { + expect(store.commit.calls.allArgs()).toEqual( + jasmine.arrayContaining([ + ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)], + ]), + ); + done(); + }) + .catch(done.fail); + }); + + it('does not commmit TOGGLE_EMPTY_STATE mutation on existing project', done => { + COMMIT_RESPONSE.parent_ids.push('1234'); + spyOn(service, 'commit').and.returnValue( + Promise.resolve({ + data: COMMIT_RESPONSE, + }), + ); + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/commitChanges') + .then(() => { + expect(store.commit.calls.allArgs()).not.toEqual( + jasmine.arrayContaining([ + ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)], + ]), + ); + done(); + }) + .catch(done.fail); + }); + }); }); }); -- cgit v1.2.1 From a5adc6a024012f727ac32c440bb42f4634ae0605 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Wed, 29 May 2019 12:27:44 +0000 Subject: Add Puma sampler This sampler gathers Puma-specific metrics which can be used by Prometheus then. --- .../gitlab/metrics/samplers/puma_sampler_spec.rb | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb new file mode 100644 index 00000000000..c471c30a194 --- /dev/null +++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Samplers::PumaSampler do + subject { described_class.new(5) } + let(:null_metric) { double('null_metric', set: nil, observe: nil) } + + before do + allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric) + end + + describe '#sample' do + before do + expect(subject).to receive(:puma_stats).and_return(puma_stats) + end + + context 'in cluster mode' do + let(:puma_stats) do + <<~EOS + { + "workers": 2, + "phase": 2, + "booted_workers": 2, + "old_workers": 0, + "worker_status": [{ + "pid": 32534, + "index": 0, + "phase": 1, + "booted": true, + "last_checkin": "2019-05-15T07:57:55Z", + "last_status": { + "backlog":0, + "running":1, + "pool_capacity":4, + "max_threads": 4 + } + }] + } + EOS + end + + it 'samples master statistics' do + labels = { worker: 'master' } + + expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 2) + expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 2) + expect(subject.metrics[:puma_stale_workers]).to receive(:set).with(labels, 0) + expect(subject.metrics[:puma_phase]).to receive(:set).once.with(labels, 2) + expect(subject.metrics[:puma_phase]).to receive(:set).once.with({ worker: 'worker_0' }, 1) + + subject.sample + end + + it 'samples worker statistics' do + labels = { worker: 'worker_0' } + + expect_worker_stats(labels) + + subject.sample + end + end + + context 'in single mode' do + let(:puma_stats) do + <<~EOS + { + "backlog":0, + "running":1, + "pool_capacity":4, + "max_threads": 4 + } + EOS + end + + it 'samples worker statistics' do + labels = {} + + expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 1) + expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 1) + expect_worker_stats(labels) + + subject.sample + end + end + end + + def expect_worker_stats(labels) + expect(subject.metrics[:puma_queued_connections]).to receive(:set).with(labels, 0) + expect(subject.metrics[:puma_active_connections]).to receive(:set).with(labels, 0) + expect(subject.metrics[:puma_running]).to receive(:set).with(labels, 1) + expect(subject.metrics[:puma_pool_capacity]).to receive(:set).with(labels, 4) + expect(subject.metrics[:puma_max_threads]).to receive(:set).with(labels, 4) + expect(subject.metrics[:puma_idle_threads]).to receive(:set).with(labels, 1) + end +end -- cgit v1.2.1 From bcf01adaa089f258a7660eff740c9bf2eddfadc8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 May 2019 14:23:08 +0100 Subject: Added spec for authenticating multiplex queries --- spec/requests/api/graphql/gitlab_schema_spec.rb | 29 ++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index a724c5c3f1c..9beea2e2594 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -52,13 +52,22 @@ describe 'GitlabSchema configurations' do end context 'multiplexed queries' do + let(:current_user) { nil } + subject do queries = [ - { query: graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) }, - { query: graphql_query_for('echo', { 'text' => "$test" }, []), variables: { "test" => "Hello world" } } + { query: graphql_query_for('project', { 'fullPath' => '$fullPath' }, %w(id name description)) }, + { query: graphql_query_for('echo', { 'text' => "$test" }, []), variables: { "test" => "Hello world" } }, + { query: graphql_query_for('project', { 'fullPath' => project.full_path }, "userPermissions { createIssue }") } ] - post_multiplex(queries) + post_multiplex(queries, current_user: current_user) + end + + it 'does not authenticate all queries' do + subject + + expect(json_response.last['data']['project']).to be_nil end it_behaves_like 'imposing query limits' do @@ -69,18 +78,28 @@ describe 'GitlabSchema configurations' do subject # Expect a response for each query, even though it will be empty - expect(json_response.size).to eq(2) + expect(json_response.size).to eq(3) json_response.each do |single_query_response| expect(single_query_response).not_to have_key('data') end # Expect errors for each query - expect(graphql_errors.size).to eq(2) + expect(graphql_errors.size).to eq(3) graphql_errors.each do |single_query_errors| expect(single_query_errors.first['message']).to include('which exceeds max complexity of 4') end end end + + context 'authentication' do + let(:current_user) { project.owner } + + it 'authenticates all queries' do + subject + + expect(json_response.last['data']['project']['userPermissions']['createIssue']).to be(true) + end + end end context 'when IntrospectionQuery' do -- cgit v1.2.1 From 8249fcf54b77e6ed32ded4ef0f49bf8e811f9fdc Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 28 May 2019 16:01:39 +0300 Subject: Move some project routes under - scope Affected project routes are: * services * boards * releases * forks * group_links * import * avatar * labels * milestones Signed-off-by: Dmitriy Zaporozhets --- spec/routing/project_routing_spec.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 6f40e88d26f..83775b1040e 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -470,18 +470,23 @@ describe 'project routing' do it_behaves_like 'RESTful project resources' do let(:controller) { 'milestones' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } + let(:controller_path) { '/-/milestones' } end it 'to #promote' do - expect(post('/gitlab/gitlabhq/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1") + expect(post('/gitlab/gitlabhq/-/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1") end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/milestones", "/gitlab/gitlabhq/-/milestones" end # project_labels GET /:project_id/labels(.:format) labels#index describe Projects::LabelsController, 'routing' do it 'to #index' do - expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/-/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/labels", "/gitlab/gitlabhq/-/labels" end # sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort @@ -623,20 +628,24 @@ describe 'project routing' do describe Projects::ForksController, 'routing' do it 'to #new' do - expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/-/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') end it 'to #create' do - expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(post('/gitlab/gitlabhq/-/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/forks", "/gitlab/gitlabhq/-/forks" end # project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy describe Projects::AvatarsController, 'routing' do it 'to #destroy' do - expect(delete('/gitlab/gitlabhq/avatar')).to route_to( + expect(delete('/gitlab/gitlabhq/-/avatar')).to route_to( 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/avatar", "/gitlab/gitlabhq/-/avatar" end describe Projects::PagesDomainsController, 'routing' do -- cgit v1.2.1 From c1b142b86744e7709e8557931f4e8adba706410a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 May 2019 13:45:27 +0300 Subject: Fix specs after routing change Signed-off-by: Dmitriy Zaporozhets --- spec/helpers/gitlab_routing_helper_spec.rb | 2 +- spec/javascripts/boards/mock_data.js | 8 ++++---- spec/lib/gitlab/url_builder_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index 143b28728a3..027480143bd 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -101,7 +101,7 @@ describe GitlabRoutingHelper do it 'returns project milestone edit path when given entity parent is not a Group' do milestone = create(:milestone, group: nil) - expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/milestones/#{milestone.iid}/edit") + expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/-/milestones/#{milestone.iid}/edit") end end end diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 93a0f29af0a..9854cf49e97 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -47,7 +47,7 @@ export const BoardsMockData = { }, ], }, - '/test/issue-boards/milestones.json': [ + '/test/issue-boards/-/milestones.json': [ { id: 1, title: 'test', @@ -58,10 +58,10 @@ export const BoardsMockData = { '/test/-/boards/1/lists': listObj, }, PUT: { - '/test/issue-boards/board/1/lists{/id}': {}, + '/test/issue-boards/-/board/1/lists{/id}': {}, }, DELETE: { - '/test/issue-boards/board/1/lists{/id}': {}, + '/test/issue-boards/-/board/1/lists{/id}': {}, }, }; @@ -71,7 +71,7 @@ export const boardsMockInterceptor = config => { }; export const mockBoardService = (opts = {}) => { - const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/boards.json'; + const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json'; const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists'; const bulkUpdatePath = opts.bulkUpdatePath || ''; const boardId = opts.boardId || '1'; diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index 9f495a5d50b..bbcb92608d8 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(milestone) - expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/milestones/#{milestone.iid}" + expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/-/milestones/#{milestone.iid}" end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 08662231fdf..453f9761602 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1147,7 +1147,7 @@ describe Project do allow(project).to receive(:avatar_in_git) { true } end - let(:avatar_path) { "/#{project.full_path}/avatar" } + let(:avatar_path) { "/#{project.full_path}/-/avatar" } it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" } end -- cgit v1.2.1 From 6f4b48e288f6a0459ef2882fb171bc4a0467bd87 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 May 2019 14:49:48 +0300 Subject: Add specs for Milestone reference methods Signed-off-by: Dmitriy Zaporozhets --- spec/models/milestone_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'spec') diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index b82368318f2..5131ccbb505 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -519,4 +519,20 @@ describe Milestone do end end end + + describe '.reference_pattern' do + subject { described_class.reference_pattern } + + it { is_expected.to match('gitlab-org/gitlab-ce%123') } + it { is_expected.to match('gitlab-org/gitlab-ce%"my-milestone"') } + end + + describe '.link_reference_pattern' do + subject { described_class.link_reference_pattern } + + it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/milestones/123") } + it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/-/milestones/123") } + it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/issues/123") } + it { is_expected.not_to match("gitlab-org/gitlab-ce/milestones/123") } + end end -- cgit v1.2.1 From 5c088584eccf89f389866d37a1c18400c002eaf8 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Wed, 29 May 2019 13:36:36 +0000 Subject: Make external_dashboard_url available to frontend - On Operations settings page - On Metrics dashboard page --- spec/helpers/environments_helper_spec.rb | 49 ++++++++++++++++++++++++++++++++ spec/helpers/projects_helper_spec.rb | 22 ++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 spec/helpers/environments_helper_spec.rb (limited to 'spec') diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb new file mode 100644 index 00000000000..0c8a8d2f032 --- /dev/null +++ b/spec/helpers/environments_helper_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe EnvironmentsHelper do + set(:environment) { create(:environment) } + set(:project) { environment.project } + set(:user) { create(:user) } + + describe '#metrics_data' do + before do + # This is so that this spec also passes in EE. + allow(helper).to receive(:current_user).and_return(user) + allow(helper).to receive(:can?).and_return(true) + end + + let(:metrics_data) { helper.metrics_data(project, environment) } + + it 'returns data' do + expect(metrics_data).to include( + 'settings-path' => edit_project_service_path(project, 'prometheus'), + 'clusters-path' => project_clusters_path(project), + 'current-environment-name': environment.name, + 'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'), + 'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'), + 'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'), + 'empty-no-data-svg-path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'), + 'empty-unable-to-connect-svg-path' => match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'), + 'metrics-endpoint' => additional_metrics_project_environment_path(project, environment, format: :json), + 'deployment-endpoint' => project_environment_deployments_path(project, environment, format: :json), + 'environments-endpoint': project_environments_path(project, format: :json), + 'project-path' => project_path(project), + 'tags-path' => project_tags_path(project), + 'has-metrics' => "#{environment.has_metrics?}", + 'external-dashboard-url' => nil + ) + end + + context 'with metrics_setting' do + before do + create(:project_metrics_setting, project: project, external_dashboard_url: 'http://gitlab.com') + end + + it 'adds external_dashboard_url' do + expect(metrics_data['external-dashboard-url']).to eq('http://gitlab.com') + end + end + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 83271aa24a3..3716879c458 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -819,4 +819,26 @@ describe ProjectsHelper do expect(helper.can_import_members?).to eq true end end + + describe '#metrics_external_dashboard_url' do + let(:project) { create(:project) } + + before do + helper.instance_variable_set(:@project, project) + end + + context 'metrics_setting exists' do + it 'returns external_dashboard_url' do + metrics_setting = create(:project_metrics_setting, project: project) + + expect(helper.metrics_external_dashboard_url).to eq(metrics_setting.external_dashboard_url) + end + end + + context 'metrics_setting does not exist' do + it 'returns nil' do + expect(helper.metrics_external_dashboard_url).to eq(nil) + end + end + end end -- cgit v1.2.1 From 40490cc4922fedbf4512109429cdc2c5aed65ded Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Wed, 13 Feb 2019 23:38:11 +0100 Subject: Add wiki size to project statistics --- .../admin/admin_sees_project_statistics_spec.rb | 2 +- spec/helpers/storage_helper_spec.rb | 3 +- .../schedule_calculate_wiki_sizes_spec.rb | 62 ++++++++++++++++++++++ spec/models/namespace_spec.rb | 6 ++- spec/models/project_statistics_spec.rb | 30 +++++++++-- spec/requests/api/groups_spec.rb | 3 +- .../projects/update_statistics_service_spec.rb | 12 +---- spec/workers/project_cache_worker_spec.rb | 5 +- 8 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb (limited to 'spec') diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb index 95d1fc5b57a..b5323a1c76d 100644 --- a/spec/features/admin/admin_sees_project_statistics_spec.rb +++ b/spec/features/admin/admin_sees_project_statistics_spec.rb @@ -15,7 +15,7 @@ describe "Admin > Admin sees project statistics" do let(:project) { create(:project, :repository) } it "shows project statistics" do - expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes build artifacts, 0 Bytes LFS)") + expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes wikis, 0 Bytes build artifacts, 0 Bytes LFS)") end end diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index 50c74a7c2f9..62c00964524 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -26,11 +26,12 @@ describe StorageHelper do namespace: namespace, statistics: build(:project_statistics, repository_size: 10.kilobytes, + wiki_size: 10.bytes, lfs_objects_size: 20.gigabytes, build_artifacts_size: 30.megabytes)) end - let(:message) { '10 KB repositories, 30 MB build artifacts, 20 GB LFS' } + let(:message) { '10 KB repositories, 10 Bytes wikis, 30 MB build artifacts, 20 GB LFS' } it 'works on ProjectStatistics' do expect(helper.storage_counters_details(project.statistics)).to eq(message) diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb new file mode 100644 index 00000000000..d494ce68c5b --- /dev/null +++ b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb') + +describe ScheduleCalculateWikiSizes, :migration, :sidekiq do + let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes } + let(:migration_name) { migration_class.to_s.demodulize } + + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:project_statistics) { table(:project_statistics) } + + context 'when missing wiki sizes exist' do + before do + namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration') + projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1) + projects.create!(id: 2, name: 'wiki-project-2', path: 'wiki-project-2', namespace_id: 1) + projects.create!(id: 3, name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: 1) + project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000) + project_statistics.create!(id: 2, project_id: 2, namespace_id: 1, wiki_size: nil) + project_statistics.create!(id: 3, project_id: 3, namespace_id: 1, wiki_size: nil) + end + + it 'schedules a background migration' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(migration_name).to be_scheduled_delayed_migration(5.minutes, 2, 3) + expect(BackgroundMigrationWorker.jobs.size).to eq 1 + end + end + end + + it 'calculates missing wiki sizes' do + expect(project_statistics.find_by(id: 2).wiki_size).to be_nil + expect(project_statistics.find_by(id: 3).wiki_size).to be_nil + + migrate! + + expect(project_statistics.find_by(id: 2).wiki_size).not_to be_nil + expect(project_statistics.find_by(id: 3).wiki_size).not_to be_nil + end + end + + context 'when missing wiki sizes do not exist' do + before do + namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration') + projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1) + project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000) + end + + it 'does not schedule a background migration' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to eq 0 + end + end + end + end +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index bfde367c47f..d80183af33e 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -147,6 +147,7 @@ describe Namespace do namespace: namespace, statistics: build(:project_statistics, repository_size: 101, + wiki_size: 505, lfs_objects_size: 202, build_artifacts_size: 303, packages_size: 404)) @@ -157,6 +158,7 @@ describe Namespace do namespace: namespace, statistics: build(:project_statistics, repository_size: 10, + wiki_size: 50, lfs_objects_size: 20, build_artifacts_size: 30, packages_size: 40)) @@ -167,8 +169,9 @@ describe Namespace do project2 statistics = described_class.with_statistics.find(namespace.id) - expect(statistics.storage_size).to eq 1110 + expect(statistics.storage_size).to eq 1665 expect(statistics.repository_size).to eq 111 + expect(statistics.wiki_size).to eq 555 expect(statistics.lfs_objects_size).to eq 222 expect(statistics.build_artifacts_size).to eq 333 expect(statistics.packages_size).to eq 444 @@ -179,6 +182,7 @@ describe Namespace do expect(statistics.storage_size).to eq 0 expect(statistics.repository_size).to eq 0 + expect(statistics.wiki_size).to eq 0 expect(statistics.lfs_objects_size).to eq 0 expect(statistics.build_artifacts_size).to eq 0 expect(statistics.packages_size).to eq 0 diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index 738398a06f9..af429544d84 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -16,16 +16,18 @@ describe ProjectStatistics do statistics.update!( commit_count: 8.exabytes - 1, repository_size: 2.exabytes, + wiki_size: 1.exabytes, lfs_objects_size: 2.exabytes, - build_artifacts_size: 4.exabytes - 1 + build_artifacts_size: 3.exabytes - 1 ) statistics.reload expect(statistics.commit_count).to eq(8.exabytes - 1) expect(statistics.repository_size).to eq(2.exabytes) + expect(statistics.wiki_size).to eq(1.exabytes) expect(statistics.lfs_objects_size).to eq(2.exabytes) - expect(statistics.build_artifacts_size).to eq(4.exabytes - 1) + expect(statistics.build_artifacts_size).to eq(3.exabytes - 1) expect(statistics.storage_size).to eq(8.exabytes - 1) end end @@ -33,6 +35,7 @@ describe ProjectStatistics do describe '#total_repository_size' do it "sums repository and LFS object size" do statistics.repository_size = 2 + statistics.wiki_size = 6 statistics.lfs_objects_size = 3 statistics.build_artifacts_size = 4 @@ -40,10 +43,17 @@ describe ProjectStatistics do end end + describe '#wiki_size' do + it "is initialized with not null value" do + expect(statistics.wiki_size).to eq 0 + end + end + describe '#refresh!' do before do allow(statistics).to receive(:update_commit_count) allow(statistics).to receive(:update_repository_size) + allow(statistics).to receive(:update_wiki_size) allow(statistics).to receive(:update_lfs_objects_size) allow(statistics).to receive(:update_storage_size) end @@ -56,6 +66,7 @@ describe ProjectStatistics do it "sums all counters" do expect(statistics).to have_received(:update_commit_count) expect(statistics).to have_received(:update_repository_size) + expect(statistics).to have_received(:update_wiki_size) expect(statistics).to have_received(:update_lfs_objects_size) end end @@ -69,6 +80,7 @@ describe ProjectStatistics do expect(statistics).to have_received(:update_lfs_objects_size) expect(statistics).not_to have_received(:update_commit_count) expect(statistics).not_to have_received(:update_repository_size) + expect(statistics).not_to have_received(:update_wiki_size) end end end @@ -95,6 +107,17 @@ describe ProjectStatistics do end end + describe '#update_wiki_size' do + before do + allow(project.wiki.repository).to receive(:size).and_return(34) + statistics.update_wiki_size + end + + it "stores the size of the wiki" do + expect(statistics.wiki_size).to eq 34.megabytes + end + end + describe '#update_lfs_objects_size' do let!(:lfs_object1) { create(:lfs_object, size: 23.megabytes) } let!(:lfs_object2) { create(:lfs_object, size: 34.megabytes) } @@ -114,12 +137,13 @@ describe ProjectStatistics do it "sums all storage counters" do statistics.update!( repository_size: 2, + wiki_size: 4, lfs_objects_size: 3 ) statistics.reload - expect(statistics.storage_size).to eq 5 + expect(statistics.storage_size).to eq 9 end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 7176bc23e34..c41408fba65 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -90,8 +90,9 @@ describe API::Groups do it "includes statistics if requested" do attributes = { - storage_size: 702, + storage_size: 1158, repository_size: 123, + wiki_size: 456, lfs_objects_size: 234, build_artifacts_size: 345 }.stringify_keys diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb index 5000ea58e5f..8534853fbc7 100644 --- a/spec/services/projects/update_statistics_service_spec.rb +++ b/spec/services/projects/update_statistics_service_spec.rb @@ -17,19 +17,9 @@ describe Projects::UpdateStatisticsService do end end - context 'with an existing project without a repository' do + context 'with an existing project' do let(:project) { create(:project) } - it 'does nothing' do - expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!) - - service.execute - end - end - - context 'with an existing project with a repository' do - let(:project) { create(:project, :repository) } - it 'refreshes the project statistics' do expect_any_instance_of(ProjectStatistics).to receive(:refresh!) .with(only: statistics.map(&:to_sym)) diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index 3c40269adc7..51afb076da1 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -25,10 +25,11 @@ describe ProjectCacheWorker do end context 'with an existing project without a repository' do - it 'does nothing' do + it 'updates statistics but does not refresh the method cashes' do allow_any_instance_of(Repository).to receive(:exists?).and_return(false) - expect(worker).not_to receive(:update_statistics) + expect(worker).to receive(:update_statistics) + expect_any_instance_of(Repository).not_to receive(:refresh_method_caches) worker.perform(project.id) end -- cgit v1.2.1 From 8a0226d4be3b11d2362712b08e0c57d0a7e27c4b Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Wed, 29 May 2019 16:09:41 +0200 Subject: Add spec to verify statistics without repositories --- spec/models/project_statistics_spec.rb | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'spec') diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index af429544d84..f985c114d4b 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -83,6 +83,44 @@ describe ProjectStatistics do expect(statistics).not_to have_received(:update_wiki_size) end end + + context 'without repositories' do + it 'does not crash' do + expect(project.repository.exists?).to be_falsey + expect(project.wiki.repository.exists?).to be_falsey + + statistics.refresh! + + expect(statistics).to have_received(:update_commit_count) + expect(statistics).to have_received(:update_repository_size) + expect(statistics).to have_received(:update_wiki_size) + expect(statistics.repository_size).to eq(0) + expect(statistics.commit_count).to eq(0) + expect(statistics.wiki_size).to eq(0) + end + end + + context 'with deleted repositories' do + let(:project) { create(:project, :repository, :wiki_repo) } + + before do + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + FileUtils.rm_rf(project.repository.path) + FileUtils.rm_rf(project.wiki.repository.path) + end + end + + it 'does not crash' do + statistics.refresh! + + expect(statistics).to have_received(:update_commit_count) + expect(statistics).to have_received(:update_repository_size) + expect(statistics).to have_received(:update_wiki_size) + expect(statistics.repository_size).to eq(0) + expect(statistics.commit_count).to eq(0) + expect(statistics.wiki_size).to eq(0) + end + end end describe '#update_commit_count' do -- cgit v1.2.1 From 2cf3fbcfb4a19169713382e3e27e43b9dfce6baa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 May 2019 17:01:35 +0100 Subject: Fixes bug with parent row component triggering multiple Vue router pushes --- .../repository/components/table/parent_row_spec.js | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 spec/frontend/repository/components/table/parent_row_spec.js (limited to 'spec') diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js new file mode 100644 index 00000000000..7020055271f --- /dev/null +++ b/spec/frontend/repository/components/table/parent_row_spec.js @@ -0,0 +1,64 @@ +import { shallowMount, RouterLinkStub } from '@vue/test-utils'; +import ParentRow from '~/repository/components/table/parent_row.vue'; + +let vm; +let $router; + +function factory(path) { + $router = { + push: jest.fn(), + }; + + vm = shallowMount(ParentRow, { + propsData: { + commitRef: 'master', + path, + }, + stubs: { + RouterLink: RouterLinkStub, + }, + mocks: { + $router, + }, + }); +} + +describe('Repository parent row component', () => { + afterEach(() => { + vm.destroy(); + }); + + it.each` + path | to + ${'app'} | ${'/tree/master/'} + ${'app/assets'} | ${'/tree/master/app'} + `('renders link in $path to $to', ({ path, to }) => { + factory(path); + + expect(vm.find(RouterLinkStub).props().to).toEqual({ + path: to, + }); + }); + + it('pushes new router when clicking row', () => { + factory('app/assets'); + + vm.find('td').trigger('click'); + + expect($router.push).toHaveBeenCalledWith({ + path: '/tree/master/app', + }); + }); + + // We test that it does not get called when clicking any internal + // links as this was causing multipe routes to get pushed + it('does not trigger router.push when clicking link', () => { + factory('app/assets'); + + vm.find('a').trigger('click'); + + expect($router.push).not.toHaveBeenCalledWith({ + path: '/tree/master/app', + }); + }); +}); -- cgit v1.2.1 From 6d6bae66dffefeb3f34f2646b0e801b3c1002170 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Wed, 29 May 2019 18:53:44 +0000 Subject: Added rack-timeout for Puma It assures that requests are aborted after 60 seconds, otherwise an exception is raised. This exception is logged by Sentry, also there is a Prometheus counter for measuring number of requests in each state. --- spec/lib/gitlab/rack_timeout_observer_spec.rb | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 spec/lib/gitlab/rack_timeout_observer_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/rack_timeout_observer_spec.rb b/spec/lib/gitlab/rack_timeout_observer_spec.rb new file mode 100644 index 00000000000..3dc1a8b68fb --- /dev/null +++ b/spec/lib/gitlab/rack_timeout_observer_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::RackTimeoutObserver do + let(:counter) { Gitlab::Metrics::NullMetric.instance } + + before do + allow(Gitlab::Metrics).to receive(:counter) + .with(any_args) + .and_return(counter) + end + + describe '#callback' do + context 'when request times out' do + let(:env) do + { + ::Rack::Timeout::ENV_INFO_KEY => double(state: :timed_out), + 'action_dispatch.request.parameters' => { + 'controller' => 'foo', + 'action' => 'bar' + } + } + end + + subject { described_class.new } + + it 'increments timeout counter' do + expect(counter) + .to receive(:increment) + .with({ controller: 'foo', action: 'bar', route: nil, state: :timed_out }) + + subject.callback.call(env) + end + end + + context 'when request expires' do + let(:endpoint) { double } + let(:env) do + { + ::Rack::Timeout::ENV_INFO_KEY => double(state: :expired), + Grape::Env::API_ENDPOINT => endpoint + } + end + + subject { described_class.new } + + it 'increments timeout counter' do + allow(endpoint).to receive_message_chain('route.pattern.origin') { 'foobar' } + expect(counter) + .to receive(:increment) + .with({ controller: nil, action: nil, route: 'foobar', state: :expired }) + + subject.callback.call(env) + end + end + end +end -- cgit v1.2.1 From a1365530a66abaf47c1d2b32e3619374b0a4cb76 Mon Sep 17 00:00:00 2001 From: Andrew Fontaine Date: Wed, 29 May 2019 13:02:29 -0600 Subject: Update Snapshots for MR Popovers A new attribute was added to `GlPopover`, so the snapshots had to be updated to match. --- spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap index 5f9f13d591d..a2a7d0ee91e 100644 --- a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap +++ b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap @@ -3,6 +3,7 @@ exports[`MR Popover loaded state matches the snapshot 1`] = ` Date: Tue, 30 Apr 2019 20:30:15 +1200 Subject: New logfile for graphql queries Specify dedicated logfile and logger class for GraphQL queries. Move complexity analyzer to a dedicated class. --- spec/lib/gitlab/graphql_logger_spec.rb | 21 +++++++++++++++++++++ spec/requests/api/graphql/gitlab_schema_spec.rb | 10 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 spec/lib/gitlab/graphql_logger_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb new file mode 100644 index 00000000000..51c77181927 --- /dev/null +++ b/spec/lib/gitlab/graphql_logger_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::GraphqlLogger, :request_store do + subject { described_class.new('/dev/null') } + let(:now) { Time.now } + + it 'builds a logger once' do + expect(::Logger).to receive(:new).and_call_original + + subject.info('hello world') + subject.error('hello again') + end + + describe '#format_message' do + it 'formats properly' do + output = subject.format_message('INFO', now, 'test', 'Hello world') + + expect(output).to match(/Hello world/) + end + end +end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index a724c5c3f1c..0eb026f1ed9 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -83,6 +83,16 @@ describe 'GitlabSchema configurations' do end end + context 'logging' do + it 'writes to the GraphQL log' do + expect(Gitlab::GraphqlLogger).to receive(:info).with(/Query Complexity/) + + query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) + + post_graphql(query, current_user: nil) + end + end + context 'when IntrospectionQuery' do it 'is not too complex' do query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) -- cgit v1.2.1 From 2c011cb5b452409db7fe1c810f1ad7440a6cedce Mon Sep 17 00:00:00 2001 From: charlieablett Date: Thu, 2 May 2019 12:16:49 +1200 Subject: Implement logger analyzer - Modify GraphqlLogger to subclass JsonLogger - Replace the single-line analyser with one that can log all the GraphQL query related information in one place. - Implement analyzer behavior with spec --- .../query_analyzers/logger_analyzer_spec.rb | 27 ++++++++++++++++++++++ spec/lib/gitlab/graphql_logger_spec.rb | 12 +++------- spec/requests/api/graphql_spec.rb | 11 +++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb new file mode 100644 index 00000000000..53a1d7f8e42 --- /dev/null +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do + + subject { described_class.new } + let(:query_string) { "abc" } + let(:provided_variables) { { a: 1, b: 2, c: 3 } } + let(:complexity) { 4 } + let(:depth) { 2 } + let(:expected_hash) do + { query_string: query_string, + variables: provided_variables, + complexity: complexity, + depth: depth } + end + + it 'assembles a hash' do + query = OpenStruct.new(query_string: query_string, provided_variables: provided_variables) + + subject.initial_value(query) + + expect(subject.instance_variable_get("@info_hash")).to eq expected_hash + end + +end diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb index 51c77181927..aeeed0b2ca1 100644 --- a/spec/lib/gitlab/graphql_logger_spec.rb +++ b/spec/lib/gitlab/graphql_logger_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' -describe Gitlab::GraphqlLogger, :request_store do +describe Gitlab::GraphqlLogger do subject { described_class.new('/dev/null') } let(:now) { Time.now } @@ -10,12 +12,4 @@ describe Gitlab::GraphqlLogger, :request_store do subject.info('hello world') subject.error('hello again') end - - describe '#format_message' do - it 'formats properly' do - output = subject.format_message('INFO', now, 'test', 'Hello world') - - expect(output).to match(/Hello world/) - end - end end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index cca87c16f27..103b02ba7a7 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -16,6 +16,17 @@ describe 'GraphQL' do end end + context 'logging' do + it 'logs the query' do + expected = { query_string: query, variables: {}, duration: anything } + + expect(Gitlab::GraphqlLogger).to receive(:info).with(expected) + + post_graphql(query) + end + + end + context 'invalid variables' do it 'returns an error' do post_graphql(query, variables: "This is not JSON") -- cgit v1.2.1 From 2a1006416748950805294793f1bc8d6fa7435eea Mon Sep 17 00:00:00 2001 From: charlieablett Date: Thu, 2 May 2019 19:09:10 +1200 Subject: Restructure complexity analyzer Remove instance variables for class re-use, test individual methods, use `monotonic_time` --- .../query_analyzers/logger_analyzer_spec.rb | 40 +++++++++++++++++----- spec/requests/api/graphql/gitlab_schema_spec.rb | 2 +- spec/requests/api/graphql_spec.rb | 26 +++++++++++--- 3 files changed, 54 insertions(+), 14 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb index 53a1d7f8e42..f85a3a206b1 100644 --- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -7,21 +7,43 @@ describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do subject { described_class.new } let(:query_string) { "abc" } let(:provided_variables) { { a: 1, b: 2, c: 3 } } + let!(:now) { Gitlab::Metrics::System.monotonic_time } let(:complexity) { 4 } let(:depth) { 2 } - let(:expected_hash) do - { query_string: query_string, + let(:initial_values) do + { time_started: now, + query_string: query_string, variables: provided_variables, - complexity: complexity, - depth: depth } + complexity: nil, + depth: nil, + duration: nil } + end + before do + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(now) end - it 'assembles a hash' do - query = OpenStruct.new(query_string: query_string, provided_variables: provided_variables) - - subject.initial_value(query) + describe '#initial_value' do + it 'assembles a hash with initial values' do + query = OpenStruct.new(query_string: query_string, provided_variables: provided_variables) - expect(subject.instance_variable_get("@info_hash")).to eq expected_hash + expect(subject.initial_value(query)).to eq initial_values + end end + describe '#call' do + before do + # some statements to fudge the complexity and depth + end + + it 'sets the complexity and depth' do + expected_hash = { time_started: now, + query_string: query_string, + variables: provided_variables, + complexity: nil, + depth: depth, + duration: complexity } + + expect(subject.call(initial_values, nil, nil)).to eq expected_hash + end + end end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index 0eb026f1ed9..dcce8c1dbad 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -85,7 +85,7 @@ describe 'GitlabSchema configurations' do context 'logging' do it 'writes to the GraphQL log' do - expect(Gitlab::GraphqlLogger).to receive(:info).with(/Query Complexity/) + expect(Gitlab::GraphqlLogger).to receive(:info) query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index 103b02ba7a7..036dfa41952 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -17,14 +17,32 @@ describe 'GraphQL' do end context 'logging' do - it 'logs the query' do - expected = { query_string: query, variables: {}, duration: anything } - + before do expect(Gitlab::GraphqlLogger).to receive(:info).with(expected) + end - post_graphql(query) + context 'with no variables' do + let(:expected) do + { query_string: query, variables: {}, duration: anything, depth: 0, complexity: 0 } + end + + it 'logs the query' do + post_graphql(query) + end end + context 'with variables' do + let!(:variables) do + { foo: "bar" } + end + let(:expected) do + { query_string: query, variables: variables, duration: anything, depth: 0, complexity: 0 } + end + + it 'logs the query' do + post_graphql(query, variables: variables) + end + end end context 'invalid variables' do -- cgit v1.2.1 From b94a17e00efc89187aefd24d388e36584cd11784 Mon Sep 17 00:00:00 2001 From: charlieablett Date: Tue, 21 May 2019 10:30:11 +1200 Subject: Add GraphQL logging feature flag --- .../graphql/query_analyzers/logger_analyzer_spec.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb index f85a3a206b1..a975af1cda2 100644 --- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do - subject { described_class.new } let(:query_string) { "abc" } let(:provided_variables) { { a: 1, b: 2, c: 3 } } @@ -22,6 +21,24 @@ describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(now) end + describe '#analyze?' do + context 'feature flag disabled' do + before do + stub_feature_flags(graphql_logging: false) + end + + specify do + expect(subject.analyze?(anything)).to be_falsey + end + end + + context 'feature flag enabled by default' do + specify do + expect(subject.analyze?(anything)).to be_truthy + end + end + end + describe '#initial_value' do it 'assembles a hash with initial values' do query = OpenStruct.new(query_string: query_string, provided_variables: provided_variables) -- cgit v1.2.1 From 184a5120dc764d33cece108fbc85b0ec96f7c050 Mon Sep 17 00:00:00 2001 From: charlieablett Date: Wed, 22 May 2019 17:13:06 +1200 Subject: Call analyzers from LoggerAnalyzer - Add changelog file - Fix failing tests --- .../query_analyzers/logger_analyzer_spec.rb | 25 ---------------------- spec/lib/gitlab/graphql_logger_spec.rb | 22 +++++++++++++++++++ spec/requests/api/graphql/gitlab_schema_spec.rb | 16 ++++++++++---- spec/requests/api/graphql_spec.rb | 6 +++--- 4 files changed, 37 insertions(+), 32 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb index a975af1cda2..4491c1cfa65 100644 --- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -38,29 +38,4 @@ describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do end end end - - describe '#initial_value' do - it 'assembles a hash with initial values' do - query = OpenStruct.new(query_string: query_string, provided_variables: provided_variables) - - expect(subject.initial_value(query)).to eq initial_values - end - end - - describe '#call' do - before do - # some statements to fudge the complexity and depth - end - - it 'sets the complexity and depth' do - expected_hash = { time_started: now, - query_string: query_string, - variables: provided_variables, - complexity: nil, - depth: depth, - duration: complexity } - - expect(subject.call(initial_values, nil, nil)).to eq expected_hash - end - end end diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb index aeeed0b2ca1..4157bb86f1c 100644 --- a/spec/lib/gitlab/graphql_logger_spec.rb +++ b/spec/lib/gitlab/graphql_logger_spec.rb @@ -12,4 +12,26 @@ describe Gitlab::GraphqlLogger do subject.info('hello world') subject.error('hello again') end + + context 'logging a GraphQL query' do + let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) } + it 'logs a query from JSON' do + analyzer_memo = { + query_string: query, + variables: {}, + complexity: 181, + depth: 0, + duration: "7ms" + } + output = subject.format_message('INFO', now, 'test', analyzer_memo) + data = JSON.parse(output) + + expect(data['severity']).to eq('INFO') + expect(data['time']).to eq(now.utc.iso8601(3)) + expect(data['complexity']).to eq(181) + expect(data['variables']).to eq({}) + expect(data['depth']).to eq(0) + expect(data['duration']).to eq("7ms") + end + end end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index dcce8c1dbad..510dec5edb2 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -84,10 +84,18 @@ describe 'GitlabSchema configurations' do end context 'logging' do - it 'writes to the GraphQL log' do - expect(Gitlab::GraphqlLogger).to receive(:info) - - query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) + let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) } + + it 'logs the query complexity' do + analyzer_memo = { + query_string: query, + variables: {}, + complexity: 181, + depth: 0, + duration: "7ms" + } + expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7) + expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo) post_graphql(query, current_user: nil) end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index 036dfa41952..ebf223127b5 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -23,7 +23,7 @@ describe 'GraphQL' do context 'with no variables' do let(:expected) do - { query_string: query, variables: {}, duration: anything, depth: 0, complexity: 0 } + { query_string: query, variables: {}, duration: anything, depth: 1, complexity: 1 } end it 'logs the query' do @@ -33,10 +33,10 @@ describe 'GraphQL' do context 'with variables' do let!(:variables) do - { foo: "bar" } + { "foo" => "bar" } end let(:expected) do - { query_string: query, variables: variables, duration: anything, depth: 0, complexity: 0 } + { query_string: query, variables: variables, duration: anything, depth: 1, complexity: 1 } end it 'logs the query' do -- cgit v1.2.1 From 5f0c230a18b677bd4ec6a4a54085775b0c69a498 Mon Sep 17 00:00:00 2001 From: charlieablett Date: Fri, 24 May 2019 09:29:19 +1200 Subject: Move complexity/depth to `final_value` Tidy tests according to reviewer comments. Move complexity and depth calls from `initial_value` to `final_value` Log variables as json --- .../query_analyzers/logger_analyzer_spec.rb | 16 +++--------- spec/lib/gitlab/graphql_logger_spec.rb | 6 +++-- spec/requests/api/graphql_spec.rb | 29 +++++++++++----------- 3 files changed, 22 insertions(+), 29 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb index 4491c1cfa65..cbf35c1095a 100644 --- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -4,19 +4,9 @@ require 'spec_helper' describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do subject { described_class.new } - let(:query_string) { "abc" } - let(:provided_variables) { { a: 1, b: 2, c: 3 } } + let!(:now) { Gitlab::Metrics::System.monotonic_time } - let(:complexity) { 4 } - let(:depth) { 2 } - let(:initial_values) do - { time_started: now, - query_string: query_string, - variables: provided_variables, - complexity: nil, - depth: nil, - duration: nil } - end + before do allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(now) end @@ -27,7 +17,7 @@ describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do stub_feature_flags(graphql_logging: false) end - specify do + it 'enables the analyzer' do expect(subject.analyze?(anything)).to be_falsey end end diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb index 4157bb86f1c..c8d4dac2153 100644 --- a/spec/lib/gitlab/graphql_logger_spec.rb +++ b/spec/lib/gitlab/graphql_logger_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::GraphqlLogger do subject { described_class.new('/dev/null') } + let(:now) { Time.now } it 'builds a logger once' do @@ -15,13 +16,14 @@ describe Gitlab::GraphqlLogger do context 'logging a GraphQL query' do let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) } + it 'logs a query from JSON' do analyzer_memo = { query_string: query, variables: {}, complexity: 181, depth: 0, - duration: "7ms" + duration: 7 } output = subject.format_message('INFO', now, 'test', analyzer_memo) data = JSON.parse(output) @@ -31,7 +33,7 @@ describe Gitlab::GraphqlLogger do expect(data['complexity']).to eq(181) expect(data['variables']).to eq({}) expect(data['depth']).to eq(0) - expect(data['duration']).to eq("7ms") + expect(data['duration']).to eq(7) end end end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index ebf223127b5..abc24fc0fe8 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -17,31 +17,32 @@ describe 'GraphQL' do end context 'logging' do + shared_examples 'logging a graphql query' do + let(:expected_params) do + { query_string: query, variables: variables.to_json, duration: anything, depth: 1, complexity: 1 } + end + + it 'logs a query with the expected params' do + post_graphql(query, variables: variables) + end + end + before do - expect(Gitlab::GraphqlLogger).to receive(:info).with(expected) + expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once end context 'with no variables' do - let(:expected) do - { query_string: query, variables: {}, duration: anything, depth: 1, complexity: 1 } - end + let(:variables) { {} } - it 'logs the query' do - post_graphql(query) - end + it_behaves_like 'logging a graphql query' end context 'with variables' do - let!(:variables) do + let(:variables) do { "foo" => "bar" } end - let(:expected) do - { query_string: query, variables: variables, duration: anything, depth: 1, complexity: 1 } - end - it 'logs the query' do - post_graphql(query, variables: variables) - end + it_behaves_like 'logging a graphql query' end end -- cgit v1.2.1 From ed5ecec1a46a674d72703c9ac1b79f65fd57cef5 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 30 May 2019 07:31:42 +0000 Subject: Extract clearDetailIssue() to issue boards store --- spec/javascripts/boards/boards_store_spec.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 22f192bc7f3..26e851b5641 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -268,4 +268,14 @@ describe('Store', () => { }); }); }); + + describe('clearDetailIssue', () => { + it('resets issue details', () => { + boardsStore.detail.issue = 'something'; + + boardsStore.clearDetailIssue(); + + expect(boardsStore.detail.issue).toEqual({}); + }); + }); }); -- cgit v1.2.1 From 51c35a4ab54e94239a623082d85378549739bea8 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 28 May 2019 11:02:06 +0100 Subject: Fixes ref being displayed as raw HTML The ref to the branch was being displayed as raw HTML in the Pipelines page --- spec/features/projects/pipelines/pipeline_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index a1115b514d3..506aa867490 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -328,6 +328,12 @@ describe 'Pipeline', :js do expect(page).not_to have_link(pipeline.ref) expect(page).to have_content(pipeline.ref) end + + it 'does not render render raw HTML to the pipeline ref' do + page.within '.pipeline-info' do + expect(page).not_to have_content(' Date: Thu, 30 May 2019 09:32:20 +0000 Subject: Add Pipeline IID to views * Add Pipeline IID to MR's * Add Pipeline IID to commit box * Add Pipeline IID to builds page * Add Pipeline IID to job sidebar * Add Pipeline IID to header on view pipeline pages * Add changelog entry * Update tests --- .../user_merges_when_pipeline_succeeds_spec.rb | 2 +- .../merge_request/user_sees_merge_widget_spec.rb | 10 ++++---- .../projects/commits/user_browses_commits_spec.rb | 2 +- .../projects/jobs/user_browses_jobs_spec.rb | 6 +++++ spec/features/projects/jobs_spec.rb | 2 +- .../jobs/components/stages_dropdown_spec.js | 21 ++++++++-------- spec/javascripts/jobs/mock_data.js | 1 + spec/javascripts/pipelines/mock_data.js | 1 + spec/javascripts/pipelines/pipeline_url_spec.js | 5 ++++ .../components/mr_widget_pipeline_spec.js | 28 ++++++++++++---------- spec/javascripts/vue_mr_widget/mock_data.js | 5 ++++ .../vue_mr_widget/mr_widget_options_spec.js | 2 ++ .../projects/commit/_commit_box.html.haml_spec.rb | 4 ++-- spec/views/projects/jobs/_build.html.haml_spec.rb | 10 ++++---- 14 files changed, 61 insertions(+), 38 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 6e54aa6006b..d4ad11b3585 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -52,7 +52,7 @@ describe 'Merge request > User merges when pipeline succeeds', :js do # so we have to wait for asynchronous call to reload it # and have_content expectation handles that. # - expect(page).to have_content "Pipeline ##{pipeline.id} running" + expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) running" end it_behaves_like 'Merge when pipeline succeeds activator' 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 a32c6bdcf8f..93ddde623fe 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -160,7 +160,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ "for #{pipeline.short_sha} " \ "on #{pipeline.ref}") end @@ -189,7 +189,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch}") @@ -201,7 +201,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch}") @@ -234,7 +234,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch} " \ @@ -248,7 +248,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch} " \ diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 574a8aefd63..953517cdff9 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -61,7 +61,7 @@ describe 'User browses commits' do it 'renders commit ci info' do visit project_commit_path(project, sample_commit.id) - expect(page).to have_content "Pipeline ##{pipeline.id} pending" + expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) pending" end end diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb index ebc20d15d67..bd6c73f4b85 100644 --- a/spec/features/projects/jobs/user_browses_jobs_spec.rb +++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb @@ -16,6 +16,12 @@ describe 'User browses jobs' do visit(project_jobs_path(project)) end + it 'shows pipeline id and IID' do + page.within('td.pipeline-link') do + expect(page).to have_content("##{pipeline.id} (##{pipeline.iid})") + end + end + it 'shows the coverage' do page.within('td.coverage') do expect(page).to have_content('99.9%') diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 9cf04fe13b4..77ea613b282 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -129,7 +129,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) within '.js-pipeline-info' do - expect(page).to have_content("Pipeline ##{pipeline.id} for #{pipeline.ref}") + expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) for #{pipeline.ref}") end end diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index 52bb5161123..e98639bf21e 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -9,6 +9,7 @@ describe('Stages Dropdown', () => { const mockPipelineData = { id: 28029444, + iid: 123, details: { status: { details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', @@ -77,8 +78,8 @@ describe('Stages Dropdown', () => { expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy'); }); - it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`; + it(`renders the pipeline info text like "Pipeline #123 (#12) for source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for ${pipeline.ref.name}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -100,10 +101,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => { - const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - } into ${pipeline.merge_request.target_branch}`; + it(`renders the pipeline info text like "Pipeline #123 (#12) for !456 with source_branch into target_branch"`, () => { + const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ + pipeline.merge_request.iid + } with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -143,10 +144,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - }`; + it(`renders the pipeline info like "Pipeline #123 (#12) for !456 with source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ + pipeline.merge_request.iid + } with ${pipeline.merge_request.source_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 3d40e94d219..88b0bb206ee 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -960,6 +960,7 @@ export default { }, pipeline: { id: 140, + iid: 13, user: { name: 'Root', username: 'root', diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js index 03ead6cd8ba..8eef9166b8d 100644 --- a/spec/javascripts/pipelines/mock_data.js +++ b/spec/javascripts/pipelines/mock_data.js @@ -1,5 +1,6 @@ export const pipelineWithStages = { id: 20333396, + iid: 304399, user: { id: 128633, name: 'Rémy Coutable', diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index aa196af2f33..88c0137dc58 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -13,6 +13,7 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, + iid: 1, path: 'foo', flags: {}, }, @@ -28,6 +29,7 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, + iid: 1, path: 'foo', flags: {}, }, @@ -47,6 +49,7 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, + iid: 1, path: 'foo', flags: { latest: true, @@ -78,6 +81,7 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, + iid: 1, path: 'foo', flags: { latest: true, @@ -100,6 +104,7 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, + iid: 1, path: 'foo', flags: { failure_reason: true, 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 75017d20473..a2308b0dfdb 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 @@ -103,7 +103,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id}`, + `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, ); }); @@ -150,7 +150,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id}`, + `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, ); }); @@ -222,9 +222,9 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ - pipeline.commit.short_id - } on ${mockCopy.source_branch_link}`; + const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ + pipeline.details.status.label + } for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -247,11 +247,11 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ - pipeline.commit.short_id - } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${ - pipeline.merge_request.target_branch - }`; + const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ + pipeline.details.status.label + } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + } into ${pipeline.merge_request.target_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -274,9 +274,11 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ - pipeline.commit.short_id - } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`; + const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ + pipeline.details.status.label + } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + }`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index bec16b0aab0..edbd0d54151 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -62,6 +62,7 @@ export default { "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22", pipeline: { id: 172, + iid: 32, user: { name: 'Administrator', username: 'root', @@ -241,6 +242,8 @@ export default { export const mockStore = { pipeline: { id: 0, + iid: 0, + path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', @@ -258,6 +261,8 @@ export const mockStore = { }, mergePipeline: { id: 1, + iid: 1, + path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index a0628fdcebe..7653c10b94b 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -600,6 +600,7 @@ describe('mrWidgetOptions', () => { ]; const deploymentMockData = { id: 15, + iid: 7, name: 'review/diplo', url: '/root/acets-review-apps/environments/15', stop_url: '/root/acets-review-apps/environments/15/stop', @@ -646,6 +647,7 @@ describe('mrWidgetOptions', () => { vm.mr.state = 'merged'; vm.mr.mergePipeline = { id: 127, + iid: 35, user: { id: 1, name: 'Administrator', diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb index 1086546c10d..457dd2e940f 100644 --- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb +++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb @@ -27,7 +27,7 @@ describe 'projects/commit/_commit_box.html.haml' do render - expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed") + expect(rendered).to have_text("Pipeline ##{third_pipeline.id} (##{third_pipeline.iid}) failed") end end @@ -40,7 +40,7 @@ describe 'projects/commit/_commit_box.html.haml' do it 'shows correct pipeline description' do render - expect(rendered).to have_text "Pipeline ##{pipeline.id} " \ + expect(rendered).to have_text "Pipeline ##{pipeline.id} (##{pipeline.iid}) " \ 'waiting for manual action' end end diff --git a/spec/views/projects/jobs/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb index 1d58891036e..97b25a6976f 100644 --- a/spec/views/projects/jobs/_build.html.haml_spec.rb +++ b/spec/views/projects/jobs/_build.html.haml_spec.rb @@ -4,7 +4,7 @@ describe 'projects/ci/jobs/_build' do include Devise::Test::ControllerHelpers let(:project) { create(:project, :repository) } - let(:pipeline) { create(:ci_empty_pipeline, id: 1337, project: project, sha: project.commit.id) } + let(:pipeline) { create(:ci_empty_pipeline, id: 1337, iid: 57, project: project, sha: project.commit.id) } let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', stage_idx: 1, name: 'rspec 0:2', status: :pending) } before do @@ -15,14 +15,14 @@ describe 'projects/ci/jobs/_build' do it 'won\'t include a column with a link to its pipeline by default' do render partial: 'projects/ci/builds/build', locals: { build: build } - expect(rendered).not_to have_link('#1337') - expect(rendered).not_to have_text('#1337 by API') + expect(rendered).not_to have_link('#1337 (#57)') + expect(rendered).not_to have_text('#1337 (#57) by API') end it 'can include a column with a link to its pipeline' do render partial: 'projects/ci/builds/build', locals: { build: build, pipeline_link: true } - expect(rendered).to have_link('#1337') - expect(rendered).to have_text('#1337 by API') + expect(rendered).to have_link('#1337 (#57)') + expect(rendered).to have_text('#1337 (#57) by API') end end -- cgit v1.2.1 From bd4a3b6ebfbca9663f47940ea01b8304d68e4e38 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 30 May 2019 12:59:03 +0300 Subject: Fix js api spec for new labels feature Signed-off-by: Dmitriy Zaporozhets --- spec/javascripts/api_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 805bb10bda6..d9dcb08b177 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -264,7 +264,7 @@ describe('Api', () => { const namespace = 'some namespace'; const project = 'some project'; const labelData = { some: 'data' }; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/-/labels`; const expectedData = { label: labelData, }; -- cgit v1.2.1 From c90ba127bf8cdd4ccac9692b6c96fa746314cd55 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 29 May 2019 22:38:26 +0800 Subject: Extract roulette to its own module So it's more modular and extensible --- spec/lib/gitlab/danger/helper_spec.rb | 97 ------------------------------ spec/lib/gitlab/danger/roulette_spec.rb | 101 ++++++++++++++++++++++++++++++++ spec/lib/gitlab/danger/teammate_spec.rb | 16 +++-- 3 files changed, 111 insertions(+), 103 deletions(-) create mode 100644 spec/lib/gitlab/danger/roulette_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 32b90041c64..f7642182a17 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -2,7 +2,6 @@ require 'fast_spec_helper' require 'rspec-parameterized' -require 'webmock/rspec' require 'gitlab/danger/helper' @@ -19,39 +18,6 @@ describe Gitlab::Danger::Helper do end end - let(:teammate_json) do - <<~JSON - [ - { - "username": "in-gitlab-ce", - "name": "CE maintainer", - "projects":{ "gitlab-ce": "maintainer backend" } - }, - { - "username": "in-gitlab-ee", - "name": "EE reviewer", - "projects":{ "gitlab-ee": "reviewer frontend" } - } - ] - JSON - end - - let(:ce_teammate_matcher) do - satisfy do |teammate| - teammate.username == 'in-gitlab-ce' && - teammate.name == 'CE maintainer' && - teammate.projects == { 'gitlab-ce' => 'maintainer backend' } - end - end - - let(:ee_teammate_matcher) do - satisfy do |teammate| - teammate.username == 'in-gitlab-ee' && - teammate.name == 'EE reviewer' && - teammate.projects == { 'gitlab-ee' => 'reviewer frontend' } - end - end - let(:fake_git) { double('fake-git') } subject(:helper) { FakeDanger.new(git: fake_git) } @@ -119,69 +85,6 @@ describe Gitlab::Danger::Helper do end end - describe '#team' do - subject(:team) { helper.team } - - context 'HTTP failure' do - before do - WebMock - .stub_request(:get, 'https://about.gitlab.com/roulette.json') - .to_return(status: 404) - end - - it 'raises a pretty error' do - expect { team }.to raise_error(/Failed to read/) - end - end - - context 'JSON failure' do - before do - WebMock - .stub_request(:get, 'https://about.gitlab.com/roulette.json') - .to_return(body: 'INVALID JSON') - end - - it 'raises a pretty error' do - expect { team }.to raise_error(/Failed to parse/) - end - end - - context 'success' do - before do - WebMock - .stub_request(:get, 'https://about.gitlab.com/roulette.json') - .to_return(body: teammate_json) - end - - it 'returns an array of teammates' do - is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher) - end - - it 'memoizes the result' do - expect(team.object_id).to eq(helper.team.object_id) - end - end - end - - describe '#project_team' do - subject { helper.project_team } - - before do - WebMock - .stub_request(:get, 'https://about.gitlab.com/roulette.json') - .to_return(body: teammate_json) - end - - it 'filters team by project_name' do - expect(helper) - .to receive(:project_name) - .at_least(:once) - .and_return('gitlab-ce') - - is_expected.to contain_exactly(ce_teammate_matcher) - end - end - describe '#changes_by_category' do it 'categorizes changed files' do expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo qa/foo ee/changelogs/foo.yml] } diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb new file mode 100644 index 00000000000..40dce0c5378 --- /dev/null +++ b/spec/lib/gitlab/danger/roulette_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'webmock/rspec' + +require 'gitlab/danger/roulette' + +describe Gitlab::Danger::Roulette do + let(:teammate_json) do + <<~JSON + [ + { + "username": "in-gitlab-ce", + "name": "CE maintainer", + "projects":{ "gitlab-ce": "maintainer backend" } + }, + { + "username": "in-gitlab-ee", + "name": "EE reviewer", + "projects":{ "gitlab-ee": "reviewer frontend" } + } + ] + JSON + end + + let(:ce_teammate_matcher) do + satisfy do |teammate| + teammate.username == 'in-gitlab-ce' && + teammate.name == 'CE maintainer' && + teammate.projects == { 'gitlab-ce' => 'maintainer backend' } + end + end + + let(:ee_teammate_matcher) do + satisfy do |teammate| + teammate.username == 'in-gitlab-ee' && + teammate.name == 'EE reviewer' && + teammate.projects == { 'gitlab-ee' => 'reviewer frontend' } + end + end + + subject(:roulette) { Object.new.extend(described_class) } + + describe '#team' do + subject(:team) { roulette.team } + + context 'HTTP failure' do + before do + WebMock + .stub_request(:get, described_class::ROULETTE_DATA_URL) + .to_return(status: 404) + end + + it 'raises a pretty error' do + expect { team }.to raise_error(/Failed to read/) + end + end + + context 'JSON failure' do + before do + WebMock + .stub_request(:get, described_class::ROULETTE_DATA_URL) + .to_return(body: 'INVALID JSON') + end + + it 'raises a pretty error' do + expect { team }.to raise_error(/Failed to parse/) + end + end + + context 'success' do + before do + WebMock + .stub_request(:get, described_class::ROULETTE_DATA_URL) + .to_return(body: teammate_json) + end + + it 'returns an array of teammates' do + is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher) + end + + it 'memoizes the result' do + expect(team.object_id).to eq(roulette.team.object_id) + end + end + end + + describe '#project_team' do + subject { roulette.project_team('gitlab-ce') } + + before do + WebMock + .stub_request(:get, described_class::ROULETTE_DATA_URL) + .to_return(body: teammate_json) + end + + it 'filters team by project_name' do + is_expected.to contain_exactly(ce_teammate_matcher) + end + end +end diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index 4bc0a4c1398..753c74ff814 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +require 'fast_spec_helper' + +require 'gitlab/danger/teammate' + describe Gitlab::Danger::Teammate do subject { described_class.new({ 'projects' => projects }) } let(:projects) { { project => capabilities } } @@ -9,15 +13,15 @@ describe Gitlab::Danger::Teammate do let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer database'] } it '#reviewer? supports multiple roles per project' do - expect(subject.reviewer?(project, 'backend')).to be_truthy + expect(subject.reviewer?(project, :backend)).to be_truthy end it '#traintainer? supports multiple roles per project' do - expect(subject.traintainer?(project, 'database')).to be_truthy + expect(subject.traintainer?(project, :database)).to be_truthy end it '#maintainer? supports multiple roles per project' do - expect(subject.maintainer?(project, 'frontend')).to be_truthy + expect(subject.maintainer?(project, :frontend)).to be_truthy end end @@ -25,15 +29,15 @@ describe Gitlab::Danger::Teammate do let(:capabilities) { 'reviewer backend' } it '#reviewer? supports one role per project' do - expect(subject.reviewer?(project, 'backend')).to be_truthy + expect(subject.reviewer?(project, :backend)).to be_truthy end it '#traintainer? supports one role per project' do - expect(subject.traintainer?(project, 'database')).to be_falsey + expect(subject.traintainer?(project, :database)).to be_falsey end it '#maintainer? supports one role per project' do - expect(subject.maintainer?(project, 'frontend')).to be_falsey + expect(subject.maintainer?(project, :frontend)).to be_falsey end end end -- cgit v1.2.1 From e55e5d6d5a060c97e65c6cb1a3e8d3740b444077 Mon Sep 17 00:00:00 2001 From: Heinrich Lee Yu Date: Thu, 30 May 2019 20:54:36 +0800 Subject: Fix test with hard coded id --- spec/requests/api/issues/get_project_issues_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index a07d7673345..0b0f754ab57 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -172,7 +172,9 @@ describe API::Issues do end it 'returns 404 when project does not exist' do - get api('/projects/1000/issues', non_member) + max_project_id = Project.maximum(:id).to_i + + get api("/projects/#{max_project_id + 1}/issues", non_member) expect(response).to have_gitlab_http_status(404) end -- cgit v1.2.1 From a9bcddee4c2653cbf2254d893299393e3778e7df Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 21 Apr 2019 12:03:26 +0200 Subject: Protect Gitlab::HTTP against DNS rebinding attack Gitlab::HTTP now resolves the hostname only once, verifies the IP is not blocked, and then uses the same IP to perform the actual request, while passing the original hostname in the `Host` header and SSL SNI field. --- .../projects/ci/lints_controller_spec.rb | 4 +- .../gitlab/ci/config/external/file/remote_spec.rb | 14 ++-- spec/lib/gitlab/ci/config/external/mapper_spec.rb | 4 +- .../gitlab/ci/config/external/processor_spec.rb | 20 +++-- spec/lib/gitlab/ci/config_spec.rb | 5 +- spec/lib/gitlab/ci/yaml_processor_spec.rb | 4 +- spec/lib/gitlab/http_connection_adapter_spec.rb | 88 ++++++++++++++++++++++ spec/lib/gitlab/http_spec.rb | 28 ++++++- .../web_upload_strategy_spec.rb | 4 +- spec/lib/gitlab/url_blocker_spec.rb | 48 +++++++++++- spec/lib/mattermost/session_spec.rb | 7 +- .../project_services/assembla_service_spec.rb | 6 +- .../models/project_services/bamboo_service_spec.rb | 3 +- .../project_services/buildkite_service_spec.rb | 10 +-- .../project_services/campfire_service_spec.rb | 24 +++--- .../pivotaltracker_service_spec.rb | 10 ++- .../project_services/pushover_service_spec.rb | 6 +- .../project_services/teamcity_service_spec.rb | 3 +- spec/requests/api/system_hooks_spec.rb | 8 +- .../lfs_pointers/lfs_download_service_spec.rb | 19 +++-- spec/services/submit_usage_ping_service_spec.rb | 4 +- spec/services/web_hook_service_spec.rb | 10 ++- spec/support/helpers/stub_requests.rb | 40 ++++++++++ 23 files changed, 304 insertions(+), 65 deletions(-) create mode 100644 spec/lib/gitlab/http_connection_adapter_spec.rb create mode 100644 spec/support/helpers/stub_requests.rb (limited to 'spec') diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb index 323a32575af..cc6ac83ca38 100644 --- a/spec/controllers/projects/ci/lints_controller_spec.rb +++ b/spec/controllers/projects/ci/lints_controller_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Projects::Ci::LintsController do + include StubRequests + let(:project) { create(:project, :repository) } let(:user) { create(:user) } @@ -70,7 +72,7 @@ describe Projects::Ci::LintsController do context 'with a valid gitlab-ci.yml' do before do - WebMock.stub_request(:get, remote_file_path).to_return(body: remote_file_content) + stub_full_request(remote_file_path).to_return(body: remote_file_content) project.add_developer(user) post :create, params: { namespace_id: project.namespace, project_id: project, content: content } diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index d8a61618e77..46d68097fff 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::File::Remote do + include StubRequests + let(:context) { described_class::Context.new(nil, '12345', nil, Set.new) } let(:params) { { remote: location } } let(:remote_file) { described_class.new(params, context) } @@ -46,7 +48,7 @@ describe Gitlab::Ci::Config::External::File::Remote do describe "#valid?" do context 'when is a valid remote url' do before do - WebMock.stub_request(:get, location).to_return(body: remote_file_content) + stub_full_request(location).to_return(body: remote_file_content) end it 'returns true' do @@ -92,7 +94,7 @@ describe Gitlab::Ci::Config::External::File::Remote do describe "#content" do context 'with a valid remote file' do before do - WebMock.stub_request(:get, location).to_return(body: remote_file_content) + stub_full_request(location).to_return(body: remote_file_content) end it 'returns the content of the file' do @@ -114,7 +116,7 @@ describe Gitlab::Ci::Config::External::File::Remote do let(:location) { 'https://asdasdasdaj48ggerexample.com' } before do - WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error')) + stub_full_request(location).to_raise(SocketError.new('Some HTTP error')) end it 'is nil' do @@ -144,7 +146,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when timeout error has been raised' do before do - WebMock.stub_request(:get, location).to_timeout + stub_full_request(location).to_timeout end it 'returns error message about a timeout' do @@ -154,7 +156,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when HTTP error has been raised' do before do - WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error) + stub_full_request(location).to_raise(Gitlab::HTTP::Error) end it 'returns error message about a HTTP error' do @@ -164,7 +166,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when response has 404 status' do before do - WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404) + stub_full_request(location).to_return(body: remote_file_content, status: 404) end it 'returns error message about a timeout' do diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index 136974569de..e068b786b02 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::Mapper do + include StubRequests + set(:project) { create(:project, :repository) } set(:user) { create(:user) } @@ -18,7 +20,7 @@ describe Gitlab::Ci::Config::External::Mapper do end before do - WebMock.stub_request(:get, remote_url).to_return(body: file_content) + stub_full_request(remote_url).to_return(body: file_content) end describe '#process' do diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 0f58a4f1d44..856187371e1 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::Processor do + include StubRequests + set(:project) { create(:project, :repository) } set(:another_project) { create(:project, :repository) } set(:user) { create(:user) } @@ -42,7 +44,7 @@ describe Gitlab::Ci::Config::External::Processor do let(:values) { { include: remote_file, image: 'ruby:2.2' } } before do - WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error')) + stub_full_request(remote_file).and_raise(SocketError.new('Some HTTP error')) end it 'raises an error' do @@ -75,7 +77,7 @@ describe Gitlab::Ci::Config::External::Processor do end before do - WebMock.stub_request(:get, remote_file).to_return(body: external_file_content) + stub_full_request(remote_file).to_return(body: external_file_content) end it 'appends the file to the values' do @@ -145,7 +147,7 @@ describe Gitlab::Ci::Config::External::Processor do allow_any_instance_of(Gitlab::Ci::Config::External::File::Local) .to receive(:fetch_local_content).and_return(local_file_content) - WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) + stub_full_request(remote_file).to_return(body: remote_file_content) end it 'appends the files to the values' do @@ -191,7 +193,8 @@ describe Gitlab::Ci::Config::External::Processor do end it 'takes precedence' do - WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) + stub_full_request(remote_file).to_return(body: remote_file_content) + expect(processor.perform[:image]).to eq('ruby:2.2') end end @@ -231,7 +234,8 @@ describe Gitlab::Ci::Config::External::Processor do HEREDOC end - WebMock.stub_request(:get, 'http://my.domain.com/config.yml').to_return(body: 'remote_build: { script: echo Hello World }') + stub_full_request('http://my.domain.com/config.yml') + .to_return(body: 'remote_build: { script: echo Hello World }') end context 'when project is public' do @@ -273,8 +277,10 @@ describe Gitlab::Ci::Config::External::Processor do context 'when config includes an external configuration file via SSL web request' do before do - stub_request(:get, 'https://sha256.badssl.com/fake.yml').to_return(body: 'image: ruby:2.6', status: 200) - stub_request(:get, 'https://self-signed.badssl.com/fake.yml') + stub_full_request('https://sha256.badssl.com/fake.yml', ip_address: '8.8.8.8') + .to_return(body: 'image: ruby:2.6', status: 200) + + stub_full_request('https://self-signed.badssl.com/fake.yml', ip_address: '8.8.8.9') .to_raise(OpenSSL::SSL::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed (self signed certificate)')) end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 092e9f242b7..7f336ee853e 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Config do + include StubRequests + set(:user) { create(:user) } let(:config) do @@ -216,8 +218,7 @@ describe Gitlab::Ci::Config do end before do - WebMock.stub_request(:get, remote_location) - .to_return(body: remote_file_content) + stub_full_request(remote_location).to_return(body: remote_file_content) allow(project.repository) .to receive(:blob_data_at).and_return(local_file_content) diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 0d998d89d73..29276d5b686 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' module Gitlab module Ci describe YamlProcessor do + include StubRequests + subject { described_class.new(config, user: nil) } describe '#build_attributes' do @@ -648,7 +650,7 @@ module Gitlab end before do - WebMock.stub_request(:get, 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml') + stub_full_request('https://gitlab.com/awesome-project/raw/master/.before-script-template.yml') .to_return( status: 200, headers: { 'Content-Type' => 'application/json' }, diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb new file mode 100644 index 00000000000..af033be8e5b --- /dev/null +++ b/spec/lib/gitlab/http_connection_adapter_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::HTTPConnectionAdapter do + describe '#connection' do + context 'when local requests are not allowed' do + it 'sets up the connection' do + uri = URI('https://example.org') + + connection = described_class.new(uri).connection + + expect(connection).to be_a(Net::HTTP) + expect(connection.address).to eq('93.184.216.34') + expect(connection.hostname_override).to eq('example.org') + expect(connection.addr_port).to eq('example.org') + expect(connection.port).to eq(443) + end + + it 'raises error when it is a request to local address' do + uri = URI('http://172.16.0.0/12') + + expect { described_class.new(uri).connection } + .to raise_error(Gitlab::HTTP::BlockedUrlError, + "URL 'http://172.16.0.0/12' is blocked: Requests to the local network are not allowed") + end + + it 'raises error when it is a request to localhost address' do + uri = URI('http://127.0.0.1') + + expect { described_class.new(uri).connection } + .to raise_error(Gitlab::HTTP::BlockedUrlError, + "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed") + end + + context 'when port different from URL scheme is used' do + it 'sets up the addr_port accordingly' do + uri = URI('https://example.org:8080') + + connection = described_class.new(uri).connection + + expect(connection.address).to eq('93.184.216.34') + expect(connection.hostname_override).to eq('example.org') + expect(connection.addr_port).to eq('example.org:8080') + expect(connection.port).to eq(8080) + end + end + end + + context 'when local requests are allowed' do + it 'sets up the connection' do + uri = URI('https://example.org') + + connection = described_class.new(uri, allow_local_requests: true).connection + + expect(connection).to be_a(Net::HTTP) + expect(connection.address).to eq('93.184.216.34') + expect(connection.hostname_override).to eq('example.org') + expect(connection.addr_port).to eq('example.org') + expect(connection.port).to eq(443) + end + + it 'sets up the connection when it is a local network' do + uri = URI('http://172.16.0.0/12') + + connection = described_class.new(uri, allow_local_requests: true).connection + + expect(connection).to be_a(Net::HTTP) + expect(connection.address).to eq('172.16.0.0') + expect(connection.hostname_override).to be(nil) + expect(connection.addr_port).to eq('172.16.0.0') + expect(connection.port).to eq(80) + end + + it 'sets up the connection when it is localhost' do + uri = URI('http://127.0.0.1') + + connection = described_class.new(uri, allow_local_requests: true).connection + + expect(connection).to be_a(Net::HTTP) + expect(connection.address).to eq('127.0.0.1') + expect(connection.hostname_override).to be(nil) + expect(connection.addr_port).to eq('127.0.0.1') + expect(connection.port).to eq(80) + end + end + end +end diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb index 6c37c157f5d..158f77cab2c 100644 --- a/spec/lib/gitlab/http_spec.rb +++ b/spec/lib/gitlab/http_spec.rb @@ -1,6 +1,28 @@ require 'spec_helper' describe Gitlab::HTTP do + include StubRequests + + context 'when allow_local_requests' do + it 'sends the request to the correct URI' do + stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200) + + described_class.get('https://example.org:8080', allow_local_requests: false) + + expect(WebMock).to have_requested(:get, 'https://8.8.8.8:8080').once + end + end + + context 'when not allow_local_requests' do + it 'sends the request to the correct URI' do + stub_full_request('https://example.org:8080') + + described_class.get('https://example.org:8080', allow_local_requests: true) + + expect(WebMock).to have_requested(:get, 'https://8.8.8.9:8080').once + end + end + describe 'allow_local_requests_from_hooks_and_services is' do before do WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success') @@ -21,6 +43,8 @@ describe Gitlab::HTTP do context 'if allow_local_requests set to true' do it 'override the global value and allow requests to localhost or private network' do + stub_full_request('http://localhost:3003') + expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error end end @@ -32,6 +56,8 @@ describe Gitlab::HTTP do end it 'allow requests to localhost' do + stub_full_request('http://localhost:3003') + expect { described_class.get('http://localhost:3003') }.not_to raise_error end @@ -49,7 +75,7 @@ describe Gitlab::HTTP do describe 'handle redirect loops' do before do - WebMock.stub_request(:any, "http://example.org").to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep")) + stub_full_request("http://example.org", method: :any).to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep")) end it 'handles GET requests' do diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb index 7c4ac62790e..21a227335cd 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do + include StubRequests + let(:example_url) { 'http://www.example.com' } let(:strategy) { subject.new(url: example_url, http_method: 'post') } let!(:project) { create(:project, :with_export) } @@ -35,7 +37,7 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do context 'when upload fails' do it 'stores the export error' do - stub_request(:post, example_url).to_return(status: [404, 'Page not found']) + stub_full_request(example_url, method: :post).to_return(status: [404, 'Page not found']) strategy.execute(user, project) diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 445a56ab0d8..416f8cfece9 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -2,6 +2,52 @@ require 'spec_helper' describe Gitlab::UrlBlocker do + describe '#validate!' do + context 'when URI is nil' do + let(:import_url) { nil } + + it 'returns no URI and hostname' do + uri, hostname = described_class.validate!(import_url) + + expect(uri).to be(nil) + expect(hostname).to be(nil) + end + end + + context 'when URI is internal' do + let(:import_url) { 'http://localhost' } + + it 'returns URI and no hostname' do + uri, hostname = described_class.validate!(import_url) + + expect(uri).to eq(Addressable::URI.parse('http://[::1]')) + expect(hostname).to eq('localhost') + end + end + + context 'when the URL hostname is a domain' do + let(:import_url) { 'https://example.org' } + + it 'returns URI and hostname' do + uri, hostname = described_class.validate!(import_url) + + expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) + expect(hostname).to eq('example.org') + end + end + + context 'when the URL hostname is an IP address' do + let(:import_url) { 'https://93.184.216.34' } + + it 'returns URI and no hostname' do + uri, hostname = described_class.validate!(import_url) + + expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) + expect(hostname).to be(nil) + end + end + end + describe '#blocked_url?' do let(:ports) { Project::VALID_IMPORT_PORTS } @@ -208,7 +254,7 @@ describe Gitlab::UrlBlocker do end def stub_domain_resolv(domain, ip) - address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false) + address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false) allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address]) allow(address).to receive(:ipv6_v4mapped?).and_return(false) end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index 77fea5b2d24..346455067a7 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Mattermost::Session, type: :request do include ExclusiveLeaseHelpers + include StubRequests let(:user) { create(:user) } @@ -24,7 +25,7 @@ describe Mattermost::Session, type: :request do let(:location) { 'http://location.tld' } let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'} let!(:stub) do - WebMock.stub_request(:get, "#{mattermost_url}/oauth/gitlab/login") + stub_full_request("#{mattermost_url}/oauth/gitlab/login") .to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 302) end @@ -63,7 +64,7 @@ describe Mattermost::Session, type: :request do end before do - WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete") + stub_full_request("#{mattermost_url}/signup/gitlab/complete") .with(query: hash_including({ 'state' => state })) .to_return do |request| post "/oauth/token", @@ -80,7 +81,7 @@ describe Mattermost::Session, type: :request do end end - WebMock.stub_request(:post, "#{mattermost_url}/api/v4/users/logout") + stub_full_request("#{mattermost_url}/api/v4/users/logout", method: :post) .to_return(headers: { Authorization: 'token thisworksnow' }, status: 200) end diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index 7742e33e901..2c86c0ec7be 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe AssemblaService do + include StubRequests + describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } @@ -23,12 +25,12 @@ describe AssemblaService do ) @sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) @api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret' - WebMock.stub_request(:post, @api_url) + stub_full_request(@api_url, method: :post) end it "calls Assembla API" do @assembla_service.execute(@sample_data) - expect(WebMock).to have_requested(:post, @api_url).with( + expect(WebMock).to have_requested(:post, stubbed_hostname(@api_url)).with( body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ ).once end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 08c510f09df..65d227a17f9 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe BambooService, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers + include StubRequests let(:bamboo_url) { 'http://gitlab.com/bamboo' } @@ -257,7 +258,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do end def stub_bamboo_request(url, status, body) - WebMock.stub_request(:get, url).to_return( + stub_full_request(url).to_return( status: status, headers: { 'Content-Type' => 'application/json' }, body: body diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 091d4d8f695..ca196069055 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe BuildkiteService, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers + include StubRequests let(:project) { create(:project) } @@ -110,10 +111,9 @@ describe BuildkiteService, :use_clean_rails_memory_store_caching do body ||= %q({"status":"success"}) buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123' - WebMock.stub_request(:get, buildkite_full_url).to_return( - status: status, - headers: { 'Content-Type' => 'application/json' }, - body: body - ) + stub_full_request(buildkite_full_url) + .to_return(status: status, + headers: { 'Content-Type' => 'application/json' }, + body: body) end end diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index bf4c52fc7ab..0d3dd89e93b 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe CampfireService do + include StubRequests + describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } @@ -49,39 +51,37 @@ describe CampfireService do it "calls Campfire API to get a list of rooms and speak in a room" do # make sure a valid list of rooms is returned body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json') - WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return( + + stub_full_request(@rooms_url).with(basic_auth: @auth).to_return( body: body, status: 200, headers: @headers ) + # stub the speak request with the room id found in the previous request's response speak_url = 'https://project-name.campfirenow.com/room/123/speak.json' - WebMock.stub_request(:post, speak_url).with(basic_auth: @auth) + stub_full_request(speak_url, method: :post).with(basic_auth: @auth) @campfire_service.execute(@sample_data) - expect(WebMock).to have_requested(:get, @rooms_url).once - expect(WebMock).to have_requested(:post, speak_url).with( - body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/ - ).once + expect(WebMock).to have_requested(:get, stubbed_hostname(@rooms_url)).once + expect(WebMock).to have_requested(:post, stubbed_hostname(speak_url)) + .with(body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/).once end it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do # return a list of rooms that do not contain a room named 'test-room' body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json') - WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return( + stub_full_request(@rooms_url).with(basic_auth: @auth).to_return( body: body, status: 200, headers: @headers ) - # we want to make sure no request is sent to the /speak endpoint, here is a basic - # regexp that matches this endpoint - speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/.*/speak.json' @campfire_service.execute(@sample_data) - expect(WebMock).to have_requested(:get, @rooms_url).once - expect(WebMock).not_to have_requested(:post, /#{speak_url}/) + expect(WebMock).to have_requested(:get, 'https://8.8.8.9/rooms.json').once + expect(WebMock).not_to have_requested(:post, '*/room/.*/speak.json') end end end diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index 773b8b7890f..dde46c82df6 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe PivotaltrackerService do + include StubRequests + describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } @@ -53,12 +55,12 @@ describe PivotaltrackerService do end before do - WebMock.stub_request(:post, url) + stub_full_request(url, method: :post) end it 'posts correct message' do service.execute(push_data) - expect(WebMock).to have_requested(:post, url).with( + expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with( body: { 'source_commit' => { 'commit_id' => '21c12ea', @@ -85,14 +87,14 @@ describe PivotaltrackerService do service.execute(push_data(branch: 'master')) service.execute(push_data(branch: 'v10')) - expect(WebMock).to have_requested(:post, url).twice + expect(WebMock).to have_requested(:post, stubbed_hostname(url)).twice end it 'does not post message if branch is not in the list' do service.execute(push_data(branch: 'mas')) service.execute(push_data(branch: 'v11')) - expect(WebMock).not_to have_requested(:post, url) + expect(WebMock).not_to have_requested(:post, stubbed_hostname(url)) end end end diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index d2a45f48705..380f02739bc 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe PushoverService do + include StubRequests + describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } @@ -57,13 +59,13 @@ describe PushoverService do sound: sound ) - WebMock.stub_request(:post, api_url) + stub_full_request(api_url, method: :post, ip_address: '8.8.8.8') end it 'calls Pushover API' do pushover.execute(sample_data) - expect(WebMock).to have_requested(:post, api_url).once + expect(WebMock).to have_requested(:post, 'https://8.8.8.8/1/messages.json').once end end end diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 96dccae733b..1c434b25205 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe TeamcityService, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers + include StubRequests let(:teamcity_url) { 'http://gitlab.com/teamcity' } @@ -212,7 +213,7 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) - WebMock.stub_request(:get, teamcity_full_url).with(basic_auth: auth).to_return( + stub_full_request(teamcity_full_url).with(basic_auth: auth).to_return( status: status, headers: { 'Content-Type' => 'application/json' }, body: body diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index b6e8d74c2e9..0e2f3face71 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -1,12 +1,14 @@ require 'spec_helper' describe API::SystemHooks do + include StubRequests + let(:user) { create(:user) } let(:admin) { create(:admin) } let!(:hook) { create(:system_hook, url: "http://example.com") } before do - stub_request(:post, hook.url) + stub_full_request(hook.url, method: :post) end describe "GET /hooks" do @@ -68,6 +70,8 @@ describe API::SystemHooks do end it 'sets default values for events' do + stub_full_request('http://mep.mep', method: :post) + post api('/hooks', admin), params: { url: 'http://mep.mep' } expect(response).to have_gitlab_http_status(201) @@ -78,6 +82,8 @@ describe API::SystemHooks do end it 'sets explicit values for events' do + stub_full_request('http://mep.mep', method: :post) + post api('/hooks', admin), params: { url: 'http://mep.mep', diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index f4470b50753..75d534c59bf 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe Projects::LfsPointers::LfsDownloadService do + include StubRequests + let(:project) { create(:project) } let(:lfs_content) { SecureRandom.random_bytes(10) } let(:oid) { Digest::SHA256.hexdigest(lfs_content) } @@ -62,7 +64,7 @@ describe Projects::LfsPointers::LfsDownloadService do describe '#execute' do context 'when file download succeeds' do before do - WebMock.stub_request(:get, download_link).to_return(body: lfs_content) + stub_full_request(download_link).to_return(body: lfs_content) end it_behaves_like 'lfs object is created' @@ -104,7 +106,7 @@ describe Projects::LfsPointers::LfsDownloadService do let(:size) { 1 } before do - WebMock.stub_request(:get, download_link).to_return(body: lfs_content) + stub_full_request(download_link).to_return(body: lfs_content) end it_behaves_like 'no lfs object is created' @@ -118,7 +120,7 @@ describe Projects::LfsPointers::LfsDownloadService do context 'when downloaded lfs file has a different oid' do before do - WebMock.stub_request(:get, download_link).to_return(body: lfs_content) + stub_full_request(download_link).to_return(body: lfs_content) allow_any_instance_of(Digest::SHA256).to receive(:hexdigest).and_return('foobar') end @@ -136,7 +138,7 @@ describe Projects::LfsPointers::LfsDownloadService do let(:lfs_object) { LfsDownloadObject.new(oid: oid, size: size, link: download_link_with_credentials) } before do - WebMock.stub_request(:get, download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content) + stub_full_request(download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content) end it 'the request adds authorization headers' do @@ -149,7 +151,7 @@ describe Projects::LfsPointers::LfsDownloadService do let(:local_request_setting) { true } before do - WebMock.stub_request(:get, download_link).to_return(body: lfs_content) + stub_full_request(download_link, ip_address: '192.168.2.120').to_return(body: lfs_content) end it_behaves_like 'lfs object is created' @@ -173,7 +175,8 @@ describe Projects::LfsPointers::LfsDownloadService do with_them do before do - WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link }) + stub_full_request(download_link, ip_address: '192.168.2.120') + .to_return(status: 301, headers: { 'Location' => redirect_link }) end it_behaves_like 'no lfs object is created' @@ -184,8 +187,8 @@ describe Projects::LfsPointers::LfsDownloadService do let(:redirect_link) { "http://example.com/"} before do - WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link }) - WebMock.stub_request(:get, redirect_link).to_return(body: lfs_content) + stub_full_request(download_link).to_return(status: 301, headers: { 'Location' => redirect_link }) + stub_full_request(redirect_link).to_return(body: lfs_content) end it_behaves_like 'lfs object is created' diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb index 78df9bf96bf..653f17a4324 100644 --- a/spec/services/submit_usage_ping_service_spec.rb +++ b/spec/services/submit_usage_ping_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe SubmitUsagePingService do + include StubRequests + context 'when usage ping is disabled' do before do stub_application_setting(usage_ping_enabled: false) @@ -99,7 +101,7 @@ describe SubmitUsagePingService do end def stub_response(body) - stub_request(:post, 'https://version.gitlab.com/usage_data') + stub_full_request('https://version.gitlab.com/usage_data', method: :post) .to_return( headers: { 'Content-Type' => 'application/json' }, body: body.to_json diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 75ba2479b63..37bafc0c002 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe WebHookService do + include StubRequests + let(:project) { create(:project) } let(:project_hook) { create(:project_hook) } let(:headers) do @@ -67,11 +69,11 @@ describe WebHookService do let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') } it 'uses the credentials' do - WebMock.stub_request(:post, url) + stub_full_request(url, method: :post) service_instance.execute - expect(WebMock).to have_requested(:post, url).with( + expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with( headers: headers.merge('Authorization' => 'Basic ZGVtbzpkZW1v') ).once end @@ -82,11 +84,11 @@ describe WebHookService do let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') } it 'uses the credentials anyways' do - WebMock.stub_request(:post, url) + stub_full_request(url, method: :post) service_instance.execute - expect(WebMock).to have_requested(:post, url).with( + expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with( headers: headers.merge('Authorization' => 'Basic ZGVtbzo=') ).once end diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb new file mode 100644 index 00000000000..5cad35282c0 --- /dev/null +++ b/spec/support/helpers/stub_requests.rb @@ -0,0 +1,40 @@ +module StubRequests + IP_ADDRESS_STUB = '8.8.8.9'.freeze + + # Fully stubs a request using WebMock class. This class also + # stubs the IP address the URL is translated to (DNS lookup). + # + # It expects the final request to go to the `ip_address` instead the given url. + # That's primarily a DNS rebind attack prevention of Gitlab::HTTP + # (see: Gitlab::UrlBlocker). + # + def stub_full_request(url, ip_address: IP_ADDRESS_STUB, port: 80, method: :get) + stub_dns(url, ip_address: ip_address, port: port) + + url = stubbed_hostname(url, hostname: ip_address) + WebMock.stub_request(method, url) + end + + def stub_dns(url, ip_address:, port: 80) + url = parse_url(url) + socket = Socket.sockaddr_in(port, ip_address) + addr = Addrinfo.new(socket) + + # See Gitlab::UrlBlocker + allow(Addrinfo).to receive(:getaddrinfo) + .with(url.hostname, url.port, nil, :STREAM) + .and_return([addr]) + end + + def stubbed_hostname(url, hostname: IP_ADDRESS_STUB) + url = parse_url(url) + url.hostname = hostname + url.to_s + end + + private + + def parse_url(url) + url.is_a?(URI) ? url : URI(url) + end +end -- cgit v1.2.1 From a1a0f8e6b017f57060bc94d14fd4d37d8756e47d Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 29 May 2019 13:43:07 -0300 Subject: Add DNS rebinding protection settings --- spec/features/admin/admin_settings_spec.rb | 5 +++- spec/lib/gitlab/http_connection_adapter_spec.rb | 32 ++++++++++++++++++++++ spec/lib/gitlab/url_blocker_spec.rb | 35 +++++++++++++++++++++++++ spec/lib/gitlab_spec.rb | 30 +++++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index f9950b5b03f..c4dbe23f6b4 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -332,16 +332,19 @@ describe 'Admin updates settings' do end context 'Network page' do - it 'Enable outbound requests' do + it 'Changes Outbound requests settings' do visit network_admin_application_settings_path page.within('.as-outbound') do check 'Allow requests to the local network from hooks and services' + # Enabled by default + uncheck 'Enforce DNS rebinding attack protection' click_button 'Save changes' end expect(page).to have_content "Application settings saved successfully" expect(Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services).to be true + expect(Gitlab::CurrentSettings.dns_rebinding_protection_enabled).to be false end end diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb index af033be8e5b..930d1f62272 100644 --- a/spec/lib/gitlab/http_connection_adapter_spec.rb +++ b/spec/lib/gitlab/http_connection_adapter_spec.rb @@ -47,6 +47,38 @@ describe Gitlab::HTTPConnectionAdapter do end end + context 'when DNS rebinding protection is disabled' do + it 'sets up the connection' do + stub_application_setting(dns_rebinding_protection_enabled: false) + + uri = URI('https://example.org') + + connection = described_class.new(uri).connection + + expect(connection).to be_a(Net::HTTP) + expect(connection.address).to eq('example.org') + expect(connection.hostname_override).to eq(nil) + expect(connection.addr_port).to eq('example.org') + expect(connection.port).to eq(443) + end + end + + context 'when http(s) environment variable is set' do + it 'sets up the connection' do + stub_env('https_proxy' => 'https://my.proxy') + + uri = URI('https://example.org') + + connection = described_class.new(uri).connection + + expect(connection).to be_a(Net::HTTP) + expect(connection.address).to eq('example.org') + expect(connection.hostname_override).to eq(nil) + expect(connection.addr_port).to eq('example.org') + expect(connection.port).to eq(443) + end + end + context 'when local requests are allowed' do it 'sets up the connection' do uri = URI('https://example.org') diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 416f8cfece9..253366e0789 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -46,6 +46,41 @@ describe Gitlab::UrlBlocker do expect(hostname).to be(nil) end end + + context 'disabled DNS rebinding protection' do + context 'when URI is internal' do + let(:import_url) { 'http://localhost' } + + it 'returns URI and no hostname' do + uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false) + + expect(uri).to eq(Addressable::URI.parse('http://localhost')) + expect(hostname).to be(nil) + end + end + + context 'when the URL hostname is a domain' do + let(:import_url) { 'https://example.org' } + + it 'returns URI and no hostname' do + uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false) + + expect(uri).to eq(Addressable::URI.parse('https://example.org')) + expect(hostname).to eq(nil) + end + end + + context 'when the URL hostname is an IP address' do + let(:import_url) { 'https://93.184.216.34' } + + it 'returns URI and no hostname' do + uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false) + + expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) + expect(hostname).to be(nil) + end + end + end end describe '#blocked_url?' do diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 767b5779a79..e075904b0cc 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -109,4 +109,34 @@ describe Gitlab do expect(described_class.ee?).to eq(false) end end + + describe '.http_proxy_env?' do + it 'returns true when lower case https' do + stub_env('https_proxy', 'https://my.proxy') + + expect(described_class.http_proxy_env?).to eq(true) + end + + it 'returns true when upper case https' do + stub_env('HTTPS_PROXY', 'https://my.proxy') + + expect(described_class.http_proxy_env?).to eq(true) + end + + it 'returns true when lower case http' do + stub_env('http_proxy', 'http://my.proxy') + + expect(described_class.http_proxy_env?).to eq(true) + end + + it 'returns true when upper case http' do + stub_env('HTTP_PROXY', 'http://my.proxy') + + expect(described_class.http_proxy_env?).to eq(true) + end + + it 'returns false when not set' do + expect(described_class.http_proxy_env?).to eq(false) + end + end end -- cgit v1.2.1 From 951afba624ed042e818f0febc702b5abbe3fec91 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 29 May 2019 14:07:48 +0200 Subject: Remove hook directory requirement from Shell It used to be the case that GitLab created symlinks for each repository to one copy of the Git hooks, so these ran when required. This changed to set the hooks dynamically on Gitaly when invoking Git. The side effect is that we didn't need all these symlinks anymore, which Gitaly doesn't create anymore either. Now that means that the tests in GitLab-Rails should test for it either. Related: https://gitlab.com/gitlab-org/gitaly/issues/1392#note_175619926 --- .../projects/import_export/import_file_spec.rb | 1 - spec/lib/gitlab/git/repository_spec.rb | 52 ---------------------- .../lib/gitlab/import_export/repo_restorer_spec.rb | 6 --- spec/lib/gitlab/shell_spec.rb | 10 ----- spec/support/helpers/git_helpers.rb | 8 ---- spec/tasks/gitlab/shell_rake_spec.rb | 18 +------- spec/tasks/tokens_spec.rb | 4 +- 7 files changed, 3 insertions(+), 96 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 28ae90bc0de..8d2b1fc7e30 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -47,7 +47,6 @@ describe 'Import/Export - project import integration test', :js do expect(project.description).to eq("Foo Bar") expect(project.issues).not_to be_empty expect(project.merge_requests).not_to be_empty - expect(project_hook_exists?(project)).to be true expect(wiki_exists?(project)).to be true expect(project.import_state.status).to eq('finished') end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 7644d83992f..cb4701e8edc 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -29,51 +29,6 @@ describe Gitlab::Git::Repository, :seed_helper do let(:storage_path) { TestEnv.repos_path } let(:user) { build(:user) } - describe '.create_hooks' do - let(:repo_path) { File.join(storage_path, 'hook-test.git') } - let(:hooks_dir) { File.join(repo_path, 'hooks') } - let(:target_hooks_dir) { Gitlab::Shell.new.hooks_path } - let(:existing_target) { File.join(repo_path, 'foobar') } - - before do - FileUtils.rm_rf(repo_path) - FileUtils.mkdir_p(repo_path) - end - - context 'hooks is a directory' do - let(:existing_file) { File.join(hooks_dir, 'my-file') } - - before do - FileUtils.mkdir_p(hooks_dir) - FileUtils.touch(existing_file) - described_class.create_hooks(repo_path, target_hooks_dir) - end - - it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } - it { expect(Dir[File.join(repo_path, "hooks.old.*/my-file")].count).to eq(1) } - end - - context 'hooks is a valid symlink' do - before do - FileUtils.mkdir_p existing_target - File.symlink(existing_target, hooks_dir) - described_class.create_hooks(repo_path, target_hooks_dir) - end - - it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } - end - - context 'hooks is a broken symlink' do - before do - FileUtils.rm_f(existing_target) - File.symlink(existing_target, hooks_dir) - described_class.create_hooks(repo_path, target_hooks_dir) - end - - it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } - end - end - describe "Respond to" do subject { repository } @@ -1959,13 +1914,6 @@ describe Gitlab::Git::Repository, :seed_helper do expect { imported_repo.fsck }.not_to raise_exception end - it 'creates a symlink to the global hooks dir' do - imported_repo.create_from_bundle(valid_bundle_path) - hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') } - - expect(File.readlink(hooks_path)).to eq(Gitlab::Shell.new.hooks_path) - end - it 'raises an error if the bundle is an attempted malicious payload' do expect do imported_repo.create_from_bundle(malicious_bundle_path) diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index 8a699eb1461..e2ffb2adb9b 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -34,11 +34,5 @@ describe Gitlab::ImportExport::RepoRestorer do it 'restores the repo successfully' do expect(restorer.restore).to be_truthy end - - it 'has the webhooks' do - restorer.restore - - expect(project_hook_exists?(project)).to be true - end end end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index e2f09de2808..bce2e754176 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -612,16 +612,6 @@ describe Gitlab::Shell do FileUtils.rm_rf(created_path) end - it 'creates a repository' do - expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_truthy - - expect(File.stat(created_path).mode & 0o777).to eq(0o770) - - hooks_path = File.join(created_path, 'hooks') - expect(File.lstat(hooks_path)).to be_symlink - expect(File.realpath(hooks_path)).to eq(gitlab_shell_hooks_path) - end - it 'returns false when the command fails' do FileUtils.mkdir_p(File.dirname(created_path)) # This file will block the creation of the repo's .git directory. That diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb index 99a7c39852e..99c5871ba54 100644 --- a/spec/support/helpers/git_helpers.rb +++ b/spec/support/helpers/git_helpers.rb @@ -6,12 +6,4 @@ module GitHelpers Rugged::Repository.new(path) end - - def project_hook_exists?(project) - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - project_path = project.repository.raw_repository.path - - File.exist?(File.join(project_path, 'hooks', 'post-receive')) - end - end end diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb index a9d14070177..c3e912b02c5 100644 --- a/spec/tasks/gitlab/shell_rake_spec.rb +++ b/spec/tasks/gitlab/shell_rake_spec.rb @@ -7,14 +7,8 @@ describe 'gitlab:shell rake tasks' do stub_warn_user_is_not_gitlab end - after do - TestEnv.sabotage_gitlab_shell_hooks - end - describe 'install task' do - it 'invokes create_hooks task' do - expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke) - + it 'installs and compiles gitlab-shell' do storages = Gitlab::GitalyClient::StorageSettings.allow_disk_access do Gitlab.config.repositories.storages.values.map(&:legacy_disk_path) end @@ -24,14 +18,4 @@ describe 'gitlab:shell rake tasks' do run_rake_task('gitlab:shell:install') end end - - describe 'create_hooks task' do - it 'calls gitlab-shell bin/create_hooks' do - expect_any_instance_of(Object).to receive(:system) - .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks", - *Gitlab::TaskHelpers.repository_storage_paths_args) - - run_rake_task('gitlab:shell:create_hooks') - end - end end diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb index 555a58e9aa1..4188e7caccb 100644 --- a/spec/tasks/tokens_spec.rb +++ b/spec/tasks/tokens_spec.rb @@ -8,13 +8,13 @@ describe 'tokens rake tasks' do end describe 'reset_all_email task' do - it 'invokes create_hooks task' do + it 'changes the incoming email token' do expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token } end end describe 'reset_all_feed task' do - it 'invokes create_hooks task' do + it 'changes the feed token for the user' do expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token } end end -- cgit v1.2.1 From 72e1123691ee97b8a038f0cfc567431a64222a7d Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Thu, 30 May 2019 21:53:25 +0000 Subject: CE port of Move EE specific lines in API::Settings --- spec/requests/api/settings_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 527ab1cfb66..8a60980fe80 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -13,6 +13,7 @@ describe API::Settings, 'Settings' do expect(json_response['default_projects_limit']).to eq(42) expect(json_response['password_authentication_enabled_for_web']).to be_truthy expect(json_response['repository_storages']).to eq(['default']) + expect(json_response['password_authentication_enabled']).to be_truthy expect(json_response['plantuml_enabled']).to be_falsey expect(json_response['plantuml_url']).to be_nil expect(json_response['default_project_visibility']).to be_a String -- cgit v1.2.1 From 9cd70ec1cda8d36248df27b18fe6f1badbd09e84 Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Thu, 30 May 2019 21:55:17 +0000 Subject: CE port of Move EE specific lines in API::Projects --- spec/requests/api/projects_spec.rb | 49 +++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 16d306f39cd..799e84e83c1 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1025,7 +1025,54 @@ describe API::Projects do end end - context 'when authenticated' do + context 'when authenticated as an admin' do + it 'returns a project by id' do + project + project_member + group = create(:group) + link = create(:project_group_link, project: project, group: group) + + get api("/projects/#{project.id}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(project.id) + expect(json_response['description']).to eq(project.description) + expect(json_response['default_branch']).to eq(project.default_branch) + expect(json_response['tag_list']).to be_an Array + expect(json_response['archived']).to be_falsey + expect(json_response['visibility']).to be_present + expect(json_response['ssh_url_to_repo']).to be_present + expect(json_response['http_url_to_repo']).to be_present + expect(json_response['web_url']).to be_present + expect(json_response['owner']).to be_a Hash + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to be_present + expect(json_response['issues_enabled']).to be_present + expect(json_response['merge_requests_enabled']).to be_present + expect(json_response['wiki_enabled']).to be_present + expect(json_response['jobs_enabled']).to be_present + expect(json_response['snippets_enabled']).to be_present + expect(json_response['container_registry_enabled']).to be_present + expect(json_response['created_at']).to be_present + expect(json_response['last_activity_at']).to be_present + expect(json_response['shared_runners_enabled']).to be_present + expect(json_response['creator_id']).to be_present + expect(json_response['namespace']).to be_present + expect(json_response['avatar_url']).to be_nil + expect(json_response['star_count']).to be_present + expect(json_response['forks_count']).to be_present + expect(json_response['public_jobs']).to be_present + expect(json_response['shared_with_groups']).to be_an Array + expect(json_response['shared_with_groups'].length).to eq(1) + expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) + expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) + expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) + end + end + + context 'when authenticated as a regular user' do before do project project_member -- cgit v1.2.1 From 02cc659ee8cacac14a022eedd45bc181fc17298c Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 30 May 2019 22:15:03 +0000 Subject: Sort by position in issue boards store addList() --- spec/javascripts/boards/boards_store_spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 26e851b5641..d3e6eb78e5a 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -44,6 +44,15 @@ describe('Store', () => { expect(boardsStore.state.lists.length).toBe(0); }); + describe('addList', () => { + it('sorts by position', () => { + boardsStore.addList({ position: 2 }); + boardsStore.addList({ position: 1 }); + + expect(boardsStore.state.lists[0].position).toBe(1); + }); + }); + describe('lists', () => { it('creates new list without persisting to DB', () => { boardsStore.addList(listObj); -- cgit v1.2.1 From 60c58c757039bea7b141605bdb3b947eb10d0068 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Wed, 29 May 2019 14:56:35 -0400 Subject: Extract knative domain editor into a component The new component also implements several improvements in the knative domain editor workflow: - Display a loading spinner when saving changes in the domain name - Display success toast message indicating changes were saved successfully. - Display error message in the contraty occurs --- .../clusters/components/applications_spec.js | 97 ++++++-------- .../components/knative_domain_editor_spec.js | 141 +++++++++++++++++++++ 2 files changed, 178 insertions(+), 60 deletions(-) create mode 100644 spec/frontend/clusters/components/knative_domain_editor_spec.js (limited to 'spec') diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js index 8bcf02f0a34..221ebb143be 100644 --- a/spec/frontend/clusters/components/applications_spec.js +++ b/spec/frontend/clusters/components/applications_spec.js @@ -1,9 +1,11 @@ import Vue from 'vue'; import applications from '~/clusters/components/applications.vue'; import { CLUSTER_TYPE } from '~/clusters/constants'; -import eventHub from '~/clusters/event_hub'; import mountComponent from 'helpers/vue_mount_component_helper'; import { APPLICATIONS_MOCK_STATE } from '../services/mock_data'; +import eventHub from '~/clusters/event_hub'; +import { shallowMount } from '@vue/test-utils'; +import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue'; describe('Applications', () => { let vm; @@ -277,73 +279,48 @@ describe('Applications', () => { }); describe('Knative application', () => { - describe('when installed', () => { - describe('with ip address', () => { - const props = { - applications: { - ...APPLICATIONS_MOCK_STATE, - knative: { - title: 'Knative', - hostname: 'example.com', - status: 'installed', - externalIp: '1.1.1.1', - }, - }, - }; - it('renders ip address with a clipboard button', () => { - vm = mountComponent(Applications, props); + const propsData = { + applications: { + ...APPLICATIONS_MOCK_STATE, + knative: { + title: 'Knative', + hostname: 'example.com', + status: 'installed', + externalIp: '1.1.1.1', + installed: true, + }, + }, + }; + const newHostname = 'newhostname.com'; + let wrapper; + let knativeDomainEditor; - expect(vm.$el.querySelector('.js-knative-endpoint').value).toEqual('1.1.1.1'); - - expect( - vm.$el - .querySelector('.js-knative-endpoint-clipboard-btn') - .getAttribute('data-clipboard-text'), - ).toEqual('1.1.1.1'); - }); - - it('renders domain & allows editing', () => { - expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com'); - expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe( - null, - ); - }); - - it('renders an update/save Knative domain button', () => { - expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null); - }); + beforeEach(() => { + wrapper = shallowMount(Applications, { propsData }); + jest.spyOn(eventHub, '$emit'); - it('emits event when clicking Save changes button', () => { - jest.spyOn(eventHub, '$emit'); - vm = mountComponent(Applications, props); + knativeDomainEditor = wrapper.find(KnativeDomainEditor); + }); - const saveButton = vm.$el.querySelector('.js-knative-save-domain-button'); + afterEach(() => { + wrapper.destroy(); + }); - saveButton.click(); + it('emits saveKnativeDomain event when knative domain editor emits save event', () => { + knativeDomainEditor.vm.$emit('save', newHostname); - expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { - id: 'knative', - params: { hostname: 'example.com' }, - }); - }); + expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { + id: 'knative', + params: { hostname: newHostname }, }); + }); - describe('without ip address', () => { - it('renders an input text with a loading icon and an alert text', () => { - vm = mountComponent(Applications, { - applications: { - ...APPLICATIONS_MOCK_STATE, - knative: { - title: 'Knative', - hostname: 'example.com', - status: 'installed', - }, - }, - }); + it('emits setKnativeHostname event when knative domain editor emits change event', () => { + wrapper.find(KnativeDomainEditor).vm.$emit('set', newHostname); - expect(vm.$el.querySelector('.js-knative-ip-loading-icon')).not.toBe(null); - expect(vm.$el.querySelector('.js-no-knative-endpoint-message')).not.toBe(null); - }); + expect(eventHub.$emit).toHaveBeenCalledWith('setKnativeHostname', { + id: 'knative', + hostname: newHostname, }); }); }); diff --git a/spec/frontend/clusters/components/knative_domain_editor_spec.js b/spec/frontend/clusters/components/knative_domain_editor_spec.js new file mode 100644 index 00000000000..242b5701f8b --- /dev/null +++ b/spec/frontend/clusters/components/knative_domain_editor_spec.js @@ -0,0 +1,141 @@ +import { shallowMount } from '@vue/test-utils'; +import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue'; +import LoadingButton from '~/vue_shared/components/loading_button.vue'; +import { APPLICATION_STATUS } from '~/clusters/constants'; + +const { UPDATING } = APPLICATION_STATUS; + +describe('KnativeDomainEditor', () => { + let wrapper; + let knative; + + const createComponent = (props = {}) => { + wrapper = shallowMount(KnativeDomainEditor, { + propsData: { ...props }, + }); + }; + + beforeEach(() => { + knative = { + title: 'Knative', + hostname: 'example.com', + installed: true, + }; + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('knative has an assigned IP address', () => { + beforeEach(() => { + knative.externalIp = '1.1.1.1'; + createComponent({ knative }); + }); + + it('renders ip address with a clipboard button', () => { + expect(wrapper.find('.js-knative-endpoint').exists()).toBe(true); + expect(wrapper.find('.js-knative-endpoint').element.value).toEqual(knative.externalIp); + }); + + it('displays ip address clipboard button', () => { + expect(wrapper.find('.js-knative-endpoint-clipboard-btn').attributes('text')).toEqual( + knative.externalIp, + ); + }); + + it('renders domain & allows editing', () => { + const domainNameInput = wrapper.find('.js-knative-domainname'); + + expect(domainNameInput.element.value).toEqual(knative.hostname); + expect(domainNameInput.attributes('readonly')).toBeFalsy(); + }); + + it('renders an update/save Knative domain button', () => { + expect(wrapper.find('.js-knative-save-domain-button').exists()).toBe(true); + }); + }); + + describe('knative without ip address', () => { + beforeEach(() => { + knative.externalIp = null; + createComponent({ knative }); + }); + + it('renders an input text with a loading icon', () => { + expect(wrapper.find('.js-knative-ip-loading-icon').exists()).toBe(true); + }); + + it('renders message indicating there is not IP address assigned', () => { + expect(wrapper.find('.js-no-knative-endpoint-message').exists()).toBe(true); + }); + }); + + describe('clicking save changes button', () => { + beforeEach(() => { + createComponent({ knative }); + }); + + it('triggers save event and pass current knative hostname', () => { + wrapper.find(LoadingButton).vm.$emit('click'); + expect(wrapper.emitted('save')[0]).toEqual([knative.hostname]); + }); + }); + + describe('when knative domain name was saved successfully', () => { + beforeEach(() => { + createComponent({ knative }); + }); + + it('displays toast indicating a successful update', () => { + wrapper.vm.$toast = { show: jest.fn() }; + wrapper.setProps({ knative: Object.assign({ updateSuccessful: true }, knative) }); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$toast.show).toHaveBeenCalledWith( + 'Knative domain name was updated successfully.', + ); + }); + }); + }); + + describe('when knative domain name input changes', () => { + it('emits "set" event with updated domain name', () => { + const newHostname = 'newhostname.com'; + + wrapper.setData({ knativeHostname: newHostname }); + + expect(wrapper.emitted('set')[0]).toEqual([newHostname]); + }); + }); + + describe('when updating knative domain name failed', () => { + beforeEach(() => { + createComponent({ knative }); + }); + + it('displays an error banner indicating the operation failure', () => { + wrapper.setProps({ knative: { updateFailed: true, ...knative } }); + + expect(wrapper.find('.js-cluster-knative-domain-name-failure-message').exists()).toBe(true); + }); + }); + + describe(`when knative status is ${UPDATING}`, () => { + beforeEach(() => { + createComponent({ knative: { status: UPDATING, ...knative } }); + }); + + it('renders loading spinner in save button', () => { + expect(wrapper.find(LoadingButton).props('loading')).toBe(true); + }); + + it('renders disabled save button', () => { + expect(wrapper.find(LoadingButton).props('disabled')).toBe(true); + }); + + it('renders save button with "Saving" label', () => { + expect(wrapper.find(LoadingButton).props('label')).toBe('Saving'); + }); + }); +}); -- cgit v1.2.1 From 2f0ec5c01761033edbadc1f450e4f414f86cc2d9 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Wed, 29 May 2019 18:42:07 -0400 Subject: Add updateSuccessful/Failed props to knative app They are used to indicate when knative domain name has changed --- spec/frontend/clusters/stores/clusters_store_spec.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index aa926bb36d7..0d129349799 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -133,6 +133,8 @@ describe('Clusters Store', () => { uninstallable: false, uninstallSuccessful: false, uninstallFailed: false, + updateSuccessful: false, + updateFailed: false, }, cert_manager: { title: 'Cert-Manager', -- cgit v1.2.1 From 851321f725062e34248f3d3053cdb92d89d2a1f3 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 22 May 2019 14:32:15 +1200 Subject: Ignore ProjectAutoDevops domain column This column is no longer used. The form was removed in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580 and other usages removed in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28460. Remove final usages of :domain --- spec/controllers/projects/settings/ci_cd_controller_spec.rb | 2 +- spec/factories/project_auto_devops.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index db53e5bc8a4..b91a4df40a5 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -109,7 +109,7 @@ describe Projects::Settings::CiCdController do end context 'when updating the auto_devops settings' do - let(:params) { { auto_devops_attributes: { enabled: '', domain: 'mepmep.md' } } } + let(:params) { { auto_devops_attributes: { enabled: '' } } } context 'following the instance default' do let(:params) { { auto_devops_attributes: { enabled: '' } } } diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb index 75ac7cc7687..1de42512402 100644 --- a/spec/factories/project_auto_devops.rb +++ b/spec/factories/project_auto_devops.rb @@ -2,7 +2,6 @@ FactoryBot.define do factory :project_auto_devops do project enabled true - domain "example.com" deploy_strategy :continuous trait :continuous_deployment do -- cgit v1.2.1 From 00c8264faf6e02f741240b5c430cebcebd730280 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 24 May 2019 13:11:29 -0500 Subject: Jestify activities spec Also add `jQuery` to global in `test_setup`. This was needed because: - `vendor/jquery.endless-scroll` depends on it. - Adding it to `global` in the spec itself didn't work. --- spec/frontend/activities_spec.js | 70 ++++++++++++++++++++++++++++++++++++ spec/javascripts/activities_spec.js | 71 ------------------------------------- 2 files changed, 70 insertions(+), 71 deletions(-) create mode 100644 spec/frontend/activities_spec.js delete mode 100644 spec/javascripts/activities_spec.js (limited to 'spec') diff --git a/spec/frontend/activities_spec.js b/spec/frontend/activities_spec.js new file mode 100644 index 00000000000..d14be3a1f26 --- /dev/null +++ b/spec/frontend/activities_spec.js @@ -0,0 +1,70 @@ +/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */ + +import $ from 'jquery'; +import Activities from '~/activities'; +import Pager from '~/pager'; + +describe('Activities', () => { + window.gon || (window.gon = {}); + const fixtureTemplate = 'static/event_filter.html'; + const filters = [ + { + id: 'all', + }, + { + id: 'push', + name: 'push events', + }, + { + id: 'merged', + name: 'merge events', + }, + { + id: 'comments', + }, + { + id: 'team', + }, + ]; + + function getEventName(index) { + const filter = filters[index]; + return filter.hasOwnProperty('name') ? filter.name : filter.id; + } + + function getSelector(index) { + const filter = filters[index]; + return `#${filter.id}_event_filter`; + } + + beforeEach(() => { + loadFixtures(fixtureTemplate); + jest.spyOn(Pager, 'init').mockImplementation(() => {}); + new Activities(); + }); + + for (let i = 0; i < filters.length; i += 1) { + (i => { + describe(`when selecting ${getEventName(i)}`, () => { + beforeEach(() => { + $(getSelector(i)).click(); + }); + + for (let x = 0; x < filters.length; x += 1) { + (x => { + const shouldHighlight = i === x; + const testName = shouldHighlight ? 'should highlight' : 'should not highlight'; + + it(`${testName} ${getEventName(x)}`, () => { + expect( + $(getSelector(x)) + .parent() + .hasClass('active'), + ).toEqual(shouldHighlight); + }); + })(x); + } + }); + })(i); + } +}); diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js deleted file mode 100644 index 23b6de7e4e0..00000000000 --- a/spec/javascripts/activities_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */ - -import $ from 'jquery'; -import 'vendor/jquery.endless-scroll'; -import Activities from '~/activities'; -import Pager from '~/pager'; - -describe('Activities', () => { - window.gon || (window.gon = {}); - const fixtureTemplate = 'static/event_filter.html'; - const filters = [ - { - id: 'all', - }, - { - id: 'push', - name: 'push events', - }, - { - id: 'merged', - name: 'merge events', - }, - { - id: 'comments', - }, - { - id: 'team', - }, - ]; - - function getEventName(index) { - const filter = filters[index]; - return filter.hasOwnProperty('name') ? filter.name : filter.id; - } - - function getSelector(index) { - const filter = filters[index]; - return `#${filter.id}_event_filter`; - } - - beforeEach(() => { - loadFixtures(fixtureTemplate); - spyOn(Pager, 'init').and.stub(); - new Activities(); - }); - - for (let i = 0; i < filters.length; i += 1) { - (i => { - describe(`when selecting ${getEventName(i)}`, () => { - beforeEach(() => { - $(getSelector(i)).click(); - }); - - for (let x = 0; x < filters.length; x += 1) { - (x => { - const shouldHighlight = i === x; - const testName = shouldHighlight ? 'should highlight' : 'should not highlight'; - - it(`${testName} ${getEventName(x)}`, () => { - expect( - $(getSelector(x)) - .parent() - .hasClass('active'), - ).toEqual(shouldHighlight); - }); - })(x); - } - }); - })(i); - } -}); -- cgit v1.2.1 From 2bf5135af31a2ff133f3682f59323954458cc2c3 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 24 May 2019 13:12:03 -0500 Subject: Jestify api spec --- spec/frontend/api_spec.js | 477 +++++++++++++++++++++++++++++++++++++++++++ spec/javascripts/api_spec.js | 477 ------------------------------------------- 2 files changed, 477 insertions(+), 477 deletions(-) create mode 100644 spec/frontend/api_spec.js delete mode 100644 spec/javascripts/api_spec.js (limited to 'spec') diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js new file mode 100644 index 00000000000..13615be1f27 --- /dev/null +++ b/spec/frontend/api_spec.js @@ -0,0 +1,477 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import Api from '~/api'; + +describe('Api', () => { + const dummyApiVersion = 'v3000'; + const dummyUrlRoot = '/gitlab'; + const dummyGon = { + api_version: dummyApiVersion, + relative_url_root: dummyUrlRoot, + }; + let originalGon; + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + originalGon = window.gon; + window.gon = Object.assign({}, dummyGon); + }); + + afterEach(() => { + mock.restore(); + window.gon = originalGon; + }); + + describe('buildUrl', () => { + it('adds URL root and fills in API version', () => { + const input = '/api/:version/foo/bar'; + const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`; + + const builtUrl = Api.buildUrl(input); + + expect(builtUrl).toEqual(expectedOutput); + }); + + [null, '', '/'].forEach(root => { + it(`works when relative_url_root is ${root}`, () => { + window.gon.relative_url_root = root; + const input = '/api/:version/foo/bar'; + const expectedOutput = `/api/${dummyApiVersion}/foo/bar`; + + const builtUrl = Api.buildUrl(input); + + expect(builtUrl).toEqual(expectedOutput); + }); + }); + }); + + describe('group', () => { + it('fetches a group', done => { + const groupId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`; + mock.onGet(expectedUrl).reply(200, { + name: 'test', + }); + + Api.group(groupId, response => { + expect(response.name).toBe('test'); + done(); + }); + }); + }); + + describe('groupMembers', () => { + it('fetches group members', done => { + const groupId = '54321'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`; + const expectedData = [{ id: 7 }]; + mock.onGet(expectedUrl).reply(200, expectedData); + + Api.groupMembers(groupId) + .then(({ data }) => { + expect(data).toEqual(expectedData); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('groups', () => { + it('fetches groups', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.groups(query, options, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('namespaces', () => { + it('fetches namespaces', done => { + const query = 'dummy query'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.namespaces(query, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('projects', () => { + it('fetches projects with membership when logged in', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; + window.gon.current_user_id = 1; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.projects(query, options, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + + it('fetches projects without membership when not logged in', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.projects(query, options, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('projectMergeRequests', () => { + const projectPath = 'abc'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`; + + it('fetches all merge requests for a project', done => { + const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }]; + mock.onGet(expectedUrl).reply(200, mockData); + Api.projectMergeRequests(projectPath) + .then(({ data }) => { + expect(data.length).toEqual(2); + expect(data[0].source_branch).toBe('foo'); + expect(data[1].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + + it('fetches merge requests filtered with passed params', done => { + const params = { + source_branch: 'bar', + }; + const mockData = [{ source_branch: 'bar' }]; + mock.onGet(expectedUrl, { params }).reply(200, mockData); + + Api.projectMergeRequests(projectPath, params) + .then(({ data }) => { + expect(data.length).toEqual(1); + expect(data[0].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectMergeRequest', () => { + it('fetches a merge request', done => { + const projectPath = 'abc'; + const mergeRequestId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`; + mock.onGet(expectedUrl).reply(200, { + title: 'test', + }); + + Api.projectMergeRequest(projectPath, mergeRequestId) + .then(({ data }) => { + expect(data.title).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectMergeRequestChanges', () => { + it('fetches the changes of a merge request', done => { + const projectPath = 'abc'; + const mergeRequestId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`; + mock.onGet(expectedUrl).reply(200, { + title: 'test', + }); + + Api.projectMergeRequestChanges(projectPath, mergeRequestId) + .then(({ data }) => { + expect(data.title).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectMergeRequestVersions', () => { + it('fetches the versions of a merge request', done => { + const projectPath = 'abc'; + const mergeRequestId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`; + mock.onGet(expectedUrl).reply(200, [ + { + id: 123, + }, + ]); + + Api.projectMergeRequestVersions(projectPath, mergeRequestId) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].id).toBe(123); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('projectRunners', () => { + it('fetches the runners of a project', done => { + const projectPath = 7; + const params = { scope: 'active' }; + const mockData = [{ id: 4 }]; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`; + mock.onGet(expectedUrl, { params }).reply(200, mockData); + + Api.projectRunners(projectPath, { params }) + .then(({ data }) => { + expect(data).toEqual(mockData); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('newLabel', () => { + it('creates a new label', done => { + const namespace = 'some namespace'; + const project = 'some project'; + const labelData = { some: 'data' }; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`; + const expectedData = { + label: labelData, + }; + mock.onPost(expectedUrl).reply(config => { + expect(config.data).toBe(JSON.stringify(expectedData)); + + return [ + 200, + { + name: 'test', + }, + ]; + }); + + Api.newLabel(namespace, project, labelData, response => { + expect(response.name).toBe('test'); + done(); + }); + }); + + it('creates a group label', done => { + const namespace = 'group/subgroup'; + const labelData = { some: 'data' }; + const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace); + const expectedData = { + label: labelData, + }; + mock.onPost(expectedUrl).reply(config => { + expect(config.data).toBe(JSON.stringify(expectedData)); + + return [ + 200, + { + name: 'test', + }, + ]; + }); + + Api.newLabel(namespace, undefined, labelData, response => { + expect(response.name).toBe('test'); + done(); + }); + }); + }); + + describe('groupProjects', () => { + it('fetches group projects', done => { + const groupId = '123456'; + const query = 'dummy query'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.groupProjects(groupId, query, {}, response => { + expect(response.length).toBe(1); + expect(response[0].name).toBe('test'); + done(); + }); + }); + }); + + describe('issueTemplate', () => { + it('fetches an issue template', done => { + const namespace = 'some namespace'; + const project = 'some project'; + const templateKey = ' template #%?.key '; + const templateType = 'template type'; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( + templateKey, + )}`; + mock.onGet(expectedUrl).reply(200, 'test'); + + Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => { + expect(response).toBe('test'); + done(); + }); + }); + }); + + describe('projectTemplates', () => { + it('fetches a list of templates', done => { + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`; + + mock.onGet(expectedUrl).reply(200, 'test'); + + Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => { + expect(response).toBe('test'); + done(); + }); + }); + }); + + describe('projectTemplate', () => { + it('fetches a single template', done => { + const data = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`; + + mock.onGet(expectedUrl).reply(200, 'test'); + + Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => { + expect(response).toBe('test'); + done(); + }); + }); + }); + + describe('users', () => { + it('fetches users', done => { + const query = 'dummy query'; + const options = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.users(query, options) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].name).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('user', () => { + it('fetches single user', done => { + const userId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`; + mock.onGet(expectedUrl).reply(200, { + name: 'testuser', + }); + + Api.user(userId) + .then(({ data }) => { + expect(data.name).toBe('testuser'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('user status', () => { + it('fetches single user status', done => { + const userId = '123456'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`; + mock.onGet(expectedUrl).reply(200, { + message: 'testmessage', + }); + + Api.userStatus(userId) + .then(({ data }) => { + expect(data.message).toBe('testmessage'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('commitPipelines', () => { + it('fetches pipelines for a given commit', done => { + const projectId = 'example/foobar'; + const commitSha = 'abc123def'; + const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.commitPipelines(projectId, commitSha) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].name).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('createBranch', () => { + it('creates new branch', done => { + const ref = 'master'; + const branch = 'new-branch-name'; + const dummyProjectPath = 'gitlab-org/gitlab-ce'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent( + dummyProjectPath, + )}/repository/branches`; + + jest.spyOn(axios, 'post'); + + mock.onPost(expectedUrl).replyOnce(200, { + name: branch, + }); + + Api.createBranch(dummyProjectPath, { ref, branch }) + .then(({ data }) => { + expect(data.name).toBe(branch); + expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch }); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js deleted file mode 100644 index 805bb10bda6..00000000000 --- a/spec/javascripts/api_spec.js +++ /dev/null @@ -1,477 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Api from '~/api'; - -describe('Api', () => { - const dummyApiVersion = 'v3000'; - const dummyUrlRoot = '/gitlab'; - const dummyGon = { - api_version: dummyApiVersion, - relative_url_root: dummyUrlRoot, - }; - let originalGon; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - originalGon = window.gon; - window.gon = Object.assign({}, dummyGon); - }); - - afterEach(() => { - mock.restore(); - window.gon = originalGon; - }); - - describe('buildUrl', () => { - it('adds URL root and fills in API version', () => { - const input = '/api/:version/foo/bar'; - const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`; - - const builtUrl = Api.buildUrl(input); - - expect(builtUrl).toEqual(expectedOutput); - }); - - [null, '', '/'].forEach(root => { - it(`works when relative_url_root is ${root}`, () => { - window.gon.relative_url_root = root; - const input = '/api/:version/foo/bar'; - const expectedOutput = `/api/${dummyApiVersion}/foo/bar`; - - const builtUrl = Api.buildUrl(input); - - expect(builtUrl).toEqual(expectedOutput); - }); - }); - }); - - describe('group', () => { - it('fetches a group', done => { - const groupId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`; - mock.onGet(expectedUrl).reply(200, { - name: 'test', - }); - - Api.group(groupId, response => { - expect(response.name).toBe('test'); - done(); - }); - }); - }); - - describe('groupMembers', () => { - it('fetches group members', done => { - const groupId = '54321'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`; - const expectedData = [{ id: 7 }]; - mock.onGet(expectedUrl).reply(200, expectedData); - - Api.groupMembers(groupId) - .then(({ data }) => { - expect(data).toEqual(expectedData); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('groups', () => { - it('fetches groups', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.groups(query, options, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('namespaces', () => { - it('fetches namespaces', done => { - const query = 'dummy query'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.namespaces(query, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('projects', () => { - it('fetches projects with membership when logged in', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; - window.gon.current_user_id = 1; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.projects(query, options, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - - it('fetches projects without membership when not logged in', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.projects(query, options, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('projectMergeRequests', () => { - const projectPath = 'abc'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`; - - it('fetches all merge requests for a project', done => { - const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }]; - mock.onGet(expectedUrl).reply(200, mockData); - Api.projectMergeRequests(projectPath) - .then(({ data }) => { - expect(data.length).toEqual(2); - expect(data[0].source_branch).toBe('foo'); - expect(data[1].source_branch).toBe('bar'); - }) - .then(done) - .catch(done.fail); - }); - - it('fetches merge requests filtered with passed params', done => { - const params = { - source_branch: 'bar', - }; - const mockData = [{ source_branch: 'bar' }]; - mock.onGet(expectedUrl, { params }).reply(200, mockData); - - Api.projectMergeRequests(projectPath, params) - .then(({ data }) => { - expect(data.length).toEqual(1); - expect(data[0].source_branch).toBe('bar'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectMergeRequest', () => { - it('fetches a merge request', done => { - const projectPath = 'abc'; - const mergeRequestId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`; - mock.onGet(expectedUrl).reply(200, { - title: 'test', - }); - - Api.projectMergeRequest(projectPath, mergeRequestId) - .then(({ data }) => { - expect(data.title).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectMergeRequestChanges', () => { - it('fetches the changes of a merge request', done => { - const projectPath = 'abc'; - const mergeRequestId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`; - mock.onGet(expectedUrl).reply(200, { - title: 'test', - }); - - Api.projectMergeRequestChanges(projectPath, mergeRequestId) - .then(({ data }) => { - expect(data.title).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectMergeRequestVersions', () => { - it('fetches the versions of a merge request', done => { - const projectPath = 'abc'; - const mergeRequestId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`; - mock.onGet(expectedUrl).reply(200, [ - { - id: 123, - }, - ]); - - Api.projectMergeRequestVersions(projectPath, mergeRequestId) - .then(({ data }) => { - expect(data.length).toBe(1); - expect(data[0].id).toBe(123); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('projectRunners', () => { - it('fetches the runners of a project', done => { - const projectPath = 7; - const params = { scope: 'active' }; - const mockData = [{ id: 4 }]; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`; - mock.onGet(expectedUrl, { params }).reply(200, mockData); - - Api.projectRunners(projectPath, { params }) - .then(({ data }) => { - expect(data).toEqual(mockData); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('newLabel', () => { - it('creates a new label', done => { - const namespace = 'some namespace'; - const project = 'some project'; - const labelData = { some: 'data' }; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`; - const expectedData = { - label: labelData, - }; - mock.onPost(expectedUrl).reply(config => { - expect(config.data).toBe(JSON.stringify(expectedData)); - - return [ - 200, - { - name: 'test', - }, - ]; - }); - - Api.newLabel(namespace, project, labelData, response => { - expect(response.name).toBe('test'); - done(); - }); - }); - - it('creates a group label', done => { - const namespace = 'group/subgroup'; - const labelData = { some: 'data' }; - const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace); - const expectedData = { - label: labelData, - }; - mock.onPost(expectedUrl).reply(config => { - expect(config.data).toBe(JSON.stringify(expectedData)); - - return [ - 200, - { - name: 'test', - }, - ]; - }); - - Api.newLabel(namespace, undefined, labelData, response => { - expect(response.name).toBe('test'); - done(); - }); - }); - }); - - describe('groupProjects', () => { - it('fetches group projects', done => { - const groupId = '123456'; - const query = 'dummy query'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.groupProjects(groupId, query, {}, response => { - expect(response.length).toBe(1); - expect(response[0].name).toBe('test'); - done(); - }); - }); - }); - - describe('issueTemplate', () => { - it('fetches an issue template', done => { - const namespace = 'some namespace'; - const project = 'some project'; - const templateKey = ' template #%?.key '; - const templateType = 'template type'; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( - templateKey, - )}`; - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => { - expect(response).toBe('test'); - done(); - }); - }); - }); - - describe('projectTemplates', () => { - it('fetches a list of templates', done => { - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`; - - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => { - expect(response).toBe('test'); - done(); - }); - }); - }); - - describe('projectTemplate', () => { - it('fetches a single template', done => { - const data = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`; - - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => { - expect(response).toBe('test'); - done(); - }); - }); - }); - - describe('users', () => { - it('fetches users', done => { - const query = 'dummy query'; - const options = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.users(query, options) - .then(({ data }) => { - expect(data.length).toBe(1); - expect(data[0].name).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('user', () => { - it('fetches single user', done => { - const userId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`; - mock.onGet(expectedUrl).reply(200, { - name: 'testuser', - }); - - Api.user(userId) - .then(({ data }) => { - expect(data.name).toBe('testuser'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('user status', () => { - it('fetches single user status', done => { - const userId = '123456'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`; - mock.onGet(expectedUrl).reply(200, { - message: 'testmessage', - }); - - Api.userStatus(userId) - .then(({ data }) => { - expect(data.message).toBe('testmessage'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('commitPipelines', () => { - it('fetches pipelines for a given commit', done => { - const projectId = 'example/foobar'; - const commitSha = 'abc123def'; - const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`; - mock.onGet(expectedUrl).reply(200, [ - { - name: 'test', - }, - ]); - - Api.commitPipelines(projectId, commitSha) - .then(({ data }) => { - expect(data.length).toBe(1); - expect(data[0].name).toBe('test'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('createBranch', () => { - it('creates new branch', done => { - const ref = 'master'; - const branch = 'new-branch-name'; - const dummyProjectPath = 'gitlab-org/gitlab-ce'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent( - dummyProjectPath, - )}/repository/branches`; - - spyOn(axios, 'post').and.callThrough(); - - mock.onPost(expectedUrl).replyOnce(200, { - name: branch, - }); - - Api.createBranch(dummyProjectPath, { ref, branch }) - .then(({ data }) => { - expect(data.name).toBe(branch); - expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch }); - }) - .then(done) - .catch(done.fail); - }); - }); -}); -- cgit v1.2.1 From fa89a6089e6a339d5cfdf6898d38e337a7a56515 Mon Sep 17 00:00:00 2001 From: jboyson1 Date: Thu, 30 May 2019 21:48:40 -0500 Subject: Fix broken floating point tests Update tests to use toBeCloseTo instead of toBe for floating point checks. More info here: https://jestjs.io/docs/en/expect#tobeclosetonumber-numdigits --- spec/javascripts/lib/utils/common_utils_spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 0cd077a6099..296ee85089f 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -852,20 +852,20 @@ describe('common_utils', () => { describe('roundOffFloat', () => { it('Rounds off decimal places of a float number with provided precision', () => { - expect(commonUtils.roundOffFloat(3.141592, 3)).toBe(3.142); + expect(commonUtils.roundOffFloat(3.141592, 3)).toBeCloseTo(3.142); }); it('Rounds off a float number to a whole number when provided precision is zero', () => { - expect(commonUtils.roundOffFloat(3.141592, 0)).toBe(3); - expect(commonUtils.roundOffFloat(3.5, 0)).toBe(4); + expect(commonUtils.roundOffFloat(3.141592, 0)).toBeCloseTo(3); + expect(commonUtils.roundOffFloat(3.5, 0)).toBeCloseTo(4); }); it('Rounds off float number to nearest 0, 10, 100, 1000 and so on when provided precision is below 0', () => { - expect(commonUtils.roundOffFloat(34567.14159, -1)).toBe(34570); - expect(commonUtils.roundOffFloat(34567.14159, -2)).toBe(34600); - expect(commonUtils.roundOffFloat(34567.14159, -3)).toBe(35000); - expect(commonUtils.roundOffFloat(34567.14159, -4)).toBe(30000); - expect(commonUtils.roundOffFloat(34567.14159, -5)).toBe(0); + expect(commonUtils.roundOffFloat(34567.14159, -1)).toBeCloseTo(34570); + expect(commonUtils.roundOffFloat(34567.14159, -2)).toBeCloseTo(34600); + expect(commonUtils.roundOffFloat(34567.14159, -3)).toBeCloseTo(35000); + expect(commonUtils.roundOffFloat(34567.14159, -4)).toBeCloseTo(30000); + expect(commonUtils.roundOffFloat(34567.14159, -5)).toBeCloseTo(0); }); }); -- cgit v1.2.1 From 387a4f4b2cc9cffe2a21ef1060fe35c60d1ac769 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 22 Mar 2019 19:38:45 +0700 Subject: Remove legacy artifact related code We've already migrated all the legacy artifacts to the new realm, which is ci_job_artifacts table. It's time to remove the old code base that is no longer used. --- spec/controllers/projects/jobs_controller_spec.rb | 4 +- spec/factories/ci/builds.rb | 11 -- spec/factories/ci/job_artifacts.rb | 18 ++- spec/features/commits_spec.rb | 6 +- .../user_sees_mini_pipeline_graph_spec.rb | 6 +- spec/features/projects/jobs/permissions_spec.rb | 2 +- .../projects/jobs/user_browses_job_spec.rb | 4 +- spec/features/projects/jobs_spec.rb | 10 +- spec/features/projects/pages_spec.rb | 15 ++- spec/migrations/migrate_old_artifacts_spec.rb | 4 - spec/models/ci/build_spec.rb | 146 +-------------------- spec/requests/api/jobs_spec.rb | 4 +- spec/requests/api/runner_spec.rb | 10 +- spec/services/ci/retry_build_service_spec.rb | 5 +- .../services/projects/update_pages_service_spec.rb | 59 +-------- spec/tasks/gitlab/artifacts/migrate_rake_spec.rb | 55 -------- spec/uploaders/legacy_artifact_uploader_spec.rb | 61 --------- .../object_storage/background_move_worker_spec.rb | 34 ----- .../expire_build_instance_artifacts_worker_spec.rb | 6 +- 19 files changed, 59 insertions(+), 401 deletions(-) delete mode 100644 spec/uploaders/legacy_artifact_uploader_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 9ef00fff3b2..490e9841492 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -841,8 +841,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end it 'erases artifacts' do - expect(job.artifacts_file.exists?).to be_falsey - expect(job.artifacts_metadata.exists?).to be_falsey + expect(job.artifacts_file.present?).to be_falsey + expect(job.artifacts_metadata.present?).to be_falsey end it 'erases trace' do diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index f8c494c159e..a473136b57b 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -248,17 +248,6 @@ FactoryBot.define do runner factory: :ci_runner end - trait :legacy_artifacts do - after(:create) do |build, _| - build.update!( - legacy_artifacts_file: fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip'), - legacy_artifacts_metadata: fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') - ) - end - end - trait :artifacts do after(:create) do |build| create(:ci_job_artifact, :archive, job: build, expire_at: build.artifacts_expire_at) diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb index 2c76c22ba69..542fa9775cd 100644 --- a/spec/factories/ci/job_artifacts.rb +++ b/spec/factories/ci/job_artifacts.rb @@ -45,9 +45,12 @@ FactoryBot.define do file_type :archive file_format :zip - after(:build) do |artifact, _| - artifact.file = fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') + transient do + file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') } + end + + after(:build) do |artifact, evaluator| + artifact.file = evaluator.file end end @@ -61,9 +64,12 @@ FactoryBot.define do file_type :metadata file_format :gzip - after(:build) do |artifact, _| - artifact.file = fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') + transient do + file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') } + end + + after(:build) do |artifact, evaluator| + artifact.file = evaluator.file end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 5c6c1c4fd15..2adeb37c98a 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -89,7 +89,7 @@ describe 'Commits' do context 'Download artifacts' do before do - build.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) end it do @@ -119,7 +119,7 @@ describe 'Commits' do context "when logged as reporter" do before do project.add_reporter(user) - build.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) visit pipeline_path(pipeline) end @@ -141,7 +141,7 @@ describe 'Commits' do project.update( visibility_level: Gitlab::VisibilityLevel::INTERNAL, public_builds: false) - build.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) visit pipeline_path(pipeline) end diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb index 5188dc3625f..dd8900a3698 100644 --- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb +++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb @@ -27,7 +27,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') } before do - create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1) + job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline) + create(:ci_job_artifact, :archive, file: artifacts_file1, job: job) create(:ci_build, :manual, pipeline: pipeline, when: 'manual') end @@ -35,7 +36,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do xit 'avoids repeated database queries' do before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } - create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2) + job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline) + create(:ci_job_artifact, :archive, file: artifacts_file2, job: job) create(:ci_build, :manual, pipeline: pipeline, when: 'manual') after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb index 6ce37297a7e..b5e711997a0 100644 --- a/spec/features/projects/jobs/permissions_spec.rb +++ b/spec/features/projects/jobs/permissions_spec.rb @@ -90,7 +90,7 @@ describe 'Project Jobs Permissions' do before do archive = fixture_file_upload('spec/fixtures/ci_build_artifacts.zip') - job.update(legacy_artifacts_file: archive) + create(:ci_job_artifact, :archive, file: archive, job: job) end context 'when public access for jobs is disabled' do diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 908c616f2fc..54b462da87a 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -28,8 +28,8 @@ describe 'User browses a job', :js do expect(page).to have_no_css('.artifacts') expect(build).not_to have_trace - expect(build.artifacts_file.exists?).to be_falsy - expect(build.artifacts_metadata.exists?).to be_falsy + expect(build.artifacts_file.present?).to be_falsy + expect(build.artifacts_metadata.present?).to be_falsy expect(page).to have_content('Job has been erased') end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 77ea613b282..d0878c4088a 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -314,7 +314,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context "Download artifacts", :js do before do - job.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: job) visit project_job_path(project, job) end @@ -338,8 +338,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context 'Artifacts expire date', :js do before do - job.update(legacy_artifacts_file: artifacts_file, - artifacts_expire_at: expire_at) + create(:ci_job_artifact, :archive, file: artifacts_file, expire_at: expire_at, job: job) + job.update!(artifacts_expire_at: expire_at) visit project_job_path(project, job) end @@ -981,7 +981,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do describe "GET /:project/jobs/:id/download", :js do before do - job.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: job) visit project_job_path(project, job) click_link 'Download' @@ -989,7 +989,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context "Build from other project" do before do - job2.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: job2) end it do diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index f564ae34f11..be05c74efdb 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -287,10 +287,17 @@ describe 'Pages' do :ci_build, project: project, pipeline: pipeline, - ref: 'HEAD', - legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), - legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')) - ) + ref: 'HEAD') + end + + let!(:artifact) do + create(:ci_job_artifact, :archive, + file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), job: ci_build) + end + + let!(:metadata) do + create(:ci_job_artifact, :metadata, + file: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')), job: ci_build) end before do diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb index 79e21514506..bc826d91471 100644 --- a/spec/migrations/migrate_old_artifacts_spec.rb +++ b/spec/migrations/migrate_old_artifacts_spec.rb @@ -45,10 +45,6 @@ describe MigrateOldArtifacts, :migration, schema: 20170918072948 do expect(build_with_legacy_artifacts.artifacts?).to be_falsey end - it "legacy artifacts are set" do - expect(build_with_legacy_artifacts.legacy_artifacts_file_identifier).not_to be_nil - end - describe '#min_id' do subject { migration.send(:min_id) } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 32eef9e0e01..89d18abee27 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -30,12 +30,6 @@ describe Ci::Build do it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } it { is_expected.to include_module(Ci::PipelineDelegator) } - it { is_expected.to be_a(ArtifactMigratable) } - - it_behaves_like 'UpdateProjectStatistics' do - subject { FactoryBot.build(:ci_build, pipeline: pipeline, artifacts_size: 23) } - end - describe 'associations' do it 'has a bidirectional relationship with projects' do expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds) @@ -116,24 +110,6 @@ describe Ci::Build do end end - context 'when job has a legacy archive' do - let!(:job) { create(:ci_build, :legacy_artifacts) } - - it 'returns the job' do - is_expected.to include(job) - end - - context 'when ci_enable_legacy_artifacts feature flag is disabled' do - before do - stub_feature_flags(ci_enable_legacy_artifacts: false) - end - - it 'does not return the job' do - is_expected.not_to include(job) - end - end - end - context 'when job has a job artifact archive' do let!(:job) { create(:ci_build, :artifacts) } @@ -464,51 +440,11 @@ describe Ci::Build do end end end - - context 'when legacy artifacts are used' do - let(:build) { create(:ci_build, :legacy_artifacts) } - - subject { build.artifacts? } - - context 'is expired' do - let(:build) { create(:ci_build, :legacy_artifacts, :expired) } - - it { is_expected.to be_falsy } - end - - context 'artifacts archive does not exist' do - let(:build) { create(:ci_build) } - - it { is_expected.to be_falsy } - end - - context 'artifacts archive exists' do - let(:build) { create(:ci_build, :legacy_artifacts) } - - it { is_expected.to be_truthy } - - context 'when ci_enable_legacy_artifacts feature flag is disabled' do - before do - stub_feature_flags(ci_enable_legacy_artifacts: false) - end - - it { is_expected.to be_falsy } - end - end - end end describe '#browsable_artifacts?' do subject { build.browsable_artifacts? } - context 'artifacts metadata does not exist' do - before do - build.update(legacy_artifacts_metadata: nil) - end - - it { is_expected.to be_falsy } - end - context 'artifacts metadata does exists' do let(:build) { create(:ci_build, :artifacts) } @@ -764,12 +700,6 @@ describe Ci::Build do it { is_expected.to be_truthy } end - - context 'when build does not have job artifacts' do - let(:build) { create(:ci_build, :legacy_artifacts) } - - it { is_expected.to be_falsy } - end end describe '#has_old_trace?' do @@ -1096,11 +1026,11 @@ describe Ci::Build do describe 'erasable build' do shared_examples 'erasable' do it 'removes artifact file' do - expect(build.artifacts_file.exists?).to be_falsy + expect(build.artifacts_file.present?).to be_falsy end it 'removes artifact metadata file' do - expect(build.artifacts_metadata.exists?).to be_falsy + expect(build.artifacts_metadata.present?).to be_falsy end it 'removes all job_artifacts' do @@ -1192,7 +1122,7 @@ describe Ci::Build do let!(:build) { create(:ci_build, :success, :artifacts) } before do - build.remove_artifacts_metadata! + build.erase_erasable_artifacts! end describe '#erase' do @@ -1203,76 +1133,6 @@ describe Ci::Build do end end end - - context 'old artifacts' do - context 'build is erasable' do - context 'new artifacts' do - let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) } - - describe '#erase' do - before do - build.erase(erased_by: erased_by) - end - - context 'erased by user' do - let!(:erased_by) { create(:user, username: 'eraser') } - - include_examples 'erasable' - - it 'records user who erased a build' do - expect(build.erased_by).to eq erased_by - end - end - - context 'erased by system' do - let(:erased_by) { nil } - - include_examples 'erasable' - - it 'does not set user who erased a build' do - expect(build.erased_by).to be_nil - end - end - end - - describe '#erasable?' do - subject { build.erasable? } - it { is_expected.to be_truthy } - end - - describe '#erased?' do - let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) } - subject { build.erased? } - - context 'job has not been erased' do - it { is_expected.to be_falsey } - end - - context 'job has been erased' do - before do - build.erase - end - - it { is_expected.to be_truthy } - end - end - - context 'metadata and build trace are not available' do - let!(:build) { create(:ci_build, :success, :legacy_artifacts) } - - before do - build.remove_artifacts_metadata! - end - - describe '#erase' do - it 'does not raise error' do - expect { build.erase }.not_to raise_error - end - end - end - end - end - end end describe '#erase_erasable_artifacts!' do diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 43462913497..7208cec357a 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -913,8 +913,8 @@ describe API::Jobs do expect(response).to have_gitlab_http_status(201) expect(job.job_artifacts.count).to eq(0) expect(job.trace.exist?).to be_falsy - expect(job.artifacts_file.exists?).to be_falsy - expect(job.artifacts_metadata.exists?).to be_falsy + expect(job.artifacts_file.present?).to be_falsy + expect(job.artifacts_metadata.present?).to be_falsy expect(job.has_job_artifacts?).to be_falsy end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 4006e697a41..3202050ac20 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1632,8 +1632,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let!(:metadata) { file_upload2 } let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest } - let(:stored_artifacts_file) { job.reload.artifacts_file.file } - let(:stored_metadata_file) { job.reload.artifacts_metadata.file } + let(:stored_artifacts_file) { job.reload.artifacts_file } + let(:stored_metadata_file) { job.reload.artifacts_metadata } let(:stored_artifacts_size) { job.reload.artifacts_size } let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 } let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 } @@ -1654,9 +1654,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do it 'stores artifacts and artifacts metadata' do expect(response).to have_gitlab_http_status(201) - expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) - expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) - expect(stored_artifacts_size).to eq(72821) + expect(stored_artifacts_file.filename).to eq(artifacts.original_filename) + expect(stored_metadata_file.filename).to eq(metadata.original_filename) + expect(stored_artifacts_size).to eq(artifacts.size) expect(stored_artifacts_sha256).to eq(artifacts_sha256) expect(stored_metadata_sha256).to eq(metadata_sha256) end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index df2376384ca..e9a26400723 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -23,7 +23,7 @@ describe Ci::RetryBuildService do REJECT_ACCESSORS = %i[id status user token token_encrypted coverage trace runner - artifacts_expire_at artifacts_file artifacts_metadata artifacts_size + artifacts_expire_at created_at updated_at started_at finished_at queued_at erased_by erased_at auto_canceled_by job_artifacts job_artifacts_archive job_artifacts_metadata job_artifacts_trace job_artifacts_junit @@ -38,7 +38,8 @@ describe Ci::RetryBuildService do runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason sourced_pipelines artifacts_file_store artifacts_metadata_store - metadata runner_session trace_chunks].freeze + metadata runner_session trace_chunks + artifacts_file artifacts_metadata artifacts_size].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 7c91f0bbe6e..b597717c347 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -27,59 +27,6 @@ describe Projects::UpdatePagesService do it { is_expected.not_to match(Gitlab::PathRegex.namespace_format_regex) } end - context 'legacy artifacts' do - before do - build.update(legacy_artifacts_file: file) - build.update(legacy_artifacts_metadata: metadata) - end - - describe 'pages artifacts' do - it "doesn't delete artifacts after deploying" do - expect(execute).to eq(:success) - - expect(build.reload.artifacts?).to eq(true) - end - end - - it 'succeeds' do - expect(project.pages_deployed?).to be_falsey - expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - - # Check that all expected files are extracted - %w[index.html zero .hidden/file].each do |filename| - expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy - end - end - - it 'limits pages size' do - stub_application_setting(max_pages_size: 1) - expect(execute).not_to eq(:success) - end - - it 'removes pages after destroy' do - expect(PagesWorker).to receive(:perform_in) - expect(project.pages_deployed?).to be_falsey - expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - project.destroy - expect(project.pages_deployed?).to be_falsey - end - - it 'fails if sha on branch is not latest' do - build.update(ref: 'feature') - - expect(execute).not_to eq(:success) - end - - it 'fails for empty file fails' do - build.update(legacy_artifacts_file: empty_file) - - expect { execute } - .to raise_error(Projects::UpdatePagesService::FailedToExtractError) - end - end - context 'for new artifacts' do context "for a valid job" do before do @@ -207,7 +154,7 @@ describe Projects::UpdatePagesService do end it 'fails for invalid archive' do - build.update(legacy_artifacts_file: invalid_file) + create(:ci_job_artifact, :archive, file: invalid_file, job: build) expect(execute).not_to eq(:success) end @@ -218,8 +165,8 @@ describe Projects::UpdatePagesService do file = fixture_file_upload('spec/fixtures/pages.zip') metafile = fixture_file_upload('spec/fixtures/pages.zip.meta') - build.update(legacy_artifacts_file: file) - build.update(legacy_artifacts_metadata: metafile) + create(:ci_job_artifact, :archive, file: file, job: build) + create(:ci_job_artifact, :metadata, file: metafile, job: build) allow(build).to receive(:artifacts_metadata_entry) .and_return(metadata) diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb index 8544fb62b5a..be69c10d7c8 100644 --- a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb +++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb @@ -13,61 +13,6 @@ describe 'gitlab:artifacts namespace rake task' do subject { run_rake_task('gitlab:artifacts:migrate') } - context 'legacy artifacts' do - describe 'migrate' do - let!(:build) { create(:ci_build, :legacy_artifacts, artifacts_file_store: store, artifacts_metadata_store: store) } - - context 'when local storage is used' do - let(:store) { ObjectStorage::Store::LOCAL } - - context 'and job does not have file store defined' do - let(:object_storage_enabled) { true } - let(:store) { nil } - - it "migrates file to remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE) - end - end - - context 'and remote storage is defined' do - let(:object_storage_enabled) { true } - - it "migrates file to remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE) - end - end - - context 'and remote storage is not defined' do - it "fails to migrate to remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::LOCAL) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::LOCAL) - end - end - end - - context 'when remote storage is used' do - let(:object_storage_enabled) { true } - - let(:store) { ObjectStorage::Store::REMOTE } - - it "file stays on remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE) - end - end - end - end - context 'job artifacts' do let!(:artifact) { create(:ci_job_artifact, :archive, file_store: store) } diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb deleted file mode 100644 index 0589563b502..00000000000 --- a/spec/uploaders/legacy_artifact_uploader_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'rails_helper' - -describe LegacyArtifactUploader do - let(:store) { described_class::Store::LOCAL } - let(:job) { create(:ci_build, artifacts_file_store: store) } - let(:uploader) { described_class.new(job, :legacy_artifacts_file) } - let(:local_path) { described_class.root } - - subject { uploader } - - # TODO: move to Workhorse::UploadPath - describe '.workhorse_upload_path' do - subject { described_class.workhorse_upload_path } - - it { is_expected.to start_with(local_path) } - it { is_expected.to end_with('tmp/uploads') } - end - - it_behaves_like "builds correct paths", - store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z], - cache_dir: %r[artifacts/tmp/cache], - work_dir: %r[artifacts/tmp/work] - - context 'object store is remote' do - before do - stub_artifacts_object_storage - end - - include_context 'with storage', described_class::Store::REMOTE - - it_behaves_like "builds correct paths", - store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z] - end - - describe '#filename' do - # we need to use uploader, as this makes to use mounter - # which initialises uploader.file object - let(:uploader) { job.artifacts_file } - - subject { uploader.filename } - - it { is_expected.to be_nil } - end - - context 'file is stored in valid path' do - let(:file) do - fixture_file_upload('spec/fixtures/ci_build_artifacts.zip', 'application/zip') - end - - before do - uploader.store!(file) - end - - subject { uploader.file.path } - - it { is_expected.to start_with("#{uploader.root}") } - it { is_expected.to include("/#{job.created_at.utc.strftime('%Y_%m')}/") } - it { is_expected.to include("/#{job.project_id}/") } - it { is_expected.to end_with("ci_build_artifacts.zip") } - end -end diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb index 95813d15e52..cc8970d2ba0 100644 --- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb +++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb @@ -48,40 +48,6 @@ describe ObjectStorage::BackgroundMoveWorker do end end - context 'for legacy artifacts' do - let(:build) { create(:ci_build, :legacy_artifacts) } - let(:uploader_class) { LegacyArtifactUploader } - let(:subject_class) { Ci::Build } - let(:file_field) { :artifacts_file } - let(:subject_id) { build.id } - - context 'when local storage is used' do - let(:store) { local } - - context 'and remote storage is defined' do - before do - stub_artifacts_object_storage(background_upload: true) - end - - it "migrates file to remote storage" do - perform - - expect(build.reload.artifacts_file_store).to eq(remote) - end - - context 'for artifacts_metadata' do - let(:file_field) { :artifacts_metadata } - - it 'migrates metadata to remote storage' do - perform - - expect(build.reload.artifacts_metadata_store).to eq(remote) - end - end - end - end - end - context 'for job artifacts' do let(:artifact) { create(:ci_job_artifact, :archive) } let(:uploader_class) { JobArtifactUploader } diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index bdb5a3801d9..39f676f1057 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -21,7 +21,7 @@ describe ExpireBuildInstanceArtifactsWorker do end it 'does remove files' do - expect(build.reload.artifacts_file.exists?).to be_falsey + expect(build.reload.artifacts_file.present?).to be_falsey end it 'does remove the job artifact record' do @@ -40,7 +40,7 @@ describe ExpireBuildInstanceArtifactsWorker do end it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy + expect(build.reload.artifacts_file.present?).to be_truthy end it 'does not remove the job artifact record' do @@ -56,7 +56,7 @@ describe ExpireBuildInstanceArtifactsWorker do end it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy + expect(build.reload.artifacts_file.present?).to be_truthy end it 'does not remove the job artifact record' do -- cgit v1.2.1 From 39e21fb2661693fed914012a39fb3a53b2b687c2 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Fri, 31 May 2019 05:22:55 +0000 Subject: Generate lets_encrypt_private_key on the fly Remove migration generating lets encrypt key Don't generate private_key if database is readonly For reference: This reverts commit 988a7f70489b99383b95e9f271a2caf6bb5b3a44. This reverts commit 21acbe531592d55caf0e5b8716a3b551dafd6233. --- spec/lib/gitlab/lets_encrypt/client_spec.rb | 46 ++++++++++++++++++++-- .../generate_lets_encrypt_private_key_spec.rb | 12 +----- spec/support/matchers/eq_pem.rb | 11 ++++++ 3 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 spec/support/matchers/eq_pem.rb (limited to 'spec') diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb index d63a2fbee04..5454d9c1af4 100644 --- a/spec/lib/gitlab/lets_encrypt/client_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -5,14 +5,12 @@ require 'spec_helper' describe ::Gitlab::LetsEncrypt::Client do include LetsEncryptHelpers - set(:private_key) { OpenSSL::PKey::RSA.new(4096).to_pem } let(:client) { described_class.new } before do stub_application_setting( lets_encrypt_notification_email: 'myemail@test.example.com', - lets_encrypt_terms_of_service_accepted: true, - lets_encrypt_private_key: private_key + lets_encrypt_terms_of_service_accepted: true ) end @@ -28,6 +26,36 @@ describe ::Gitlab::LetsEncrypt::Client do ) end + it 'generates and stores private key and initialize acme client with it' do + expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil) + + subject + + saved_private_key = Gitlab::CurrentSettings.lets_encrypt_private_key + + expect(saved_private_key).to be + expect(Acme::Client).to have_received(:new).with( + hash_including(private_key: eq_pem(saved_private_key)) + ) + end + + context 'when private key is saved in settings' do + let!(:saved_private_key) do + key = OpenSSL::PKey::RSA.new(4096).to_pem + Gitlab::CurrentSettings.current_application_settings.update(lets_encrypt_private_key: key) + key + end + + it 'uses current value of private key' do + subject + + expect(Acme::Client).to have_received(:new).with( + hash_including(private_key: eq_pem(saved_private_key)) + ) + expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(saved_private_key) + end + end + context 'when acme integration is disabled' do before do stub_application_setting(lets_encrypt_terms_of_service_accepted: false) @@ -94,6 +122,18 @@ describe ::Gitlab::LetsEncrypt::Client do context 'when terms of service are accepted' do it { is_expected.to eq(true) } + context "when private_key isn't present and database is read only" do + before do + allow(::Gitlab::Database).to receive(:read_only?).and_return(true) + end + + it 'returns false' do + expect(::Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil) + + is_expected.to eq(false) + end + end + context 'when feature flag is disabled' do before do stub_feature_flags(pages_auto_ssl: false) diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb index f47cc0c36ef..773bf5222f0 100644 --- a/spec/migrations/generate_lets_encrypt_private_key_spec.rb +++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb @@ -3,17 +3,9 @@ require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_p describe GenerateLetsEncryptPrivateKey, :migration do describe '#up' do - let(:applications_settings) { table(:applications_settings) } - - it 'generates RSA private key and saves it in application settings' do - application_setting = described_class::ApplicationSetting.create! - - described_class.new.up - application_setting.reload - - expect(application_setting.lets_encrypt_private_key).to be_present + it 'does not fail' do expect do - OpenSSL::PKey::RSA.new(application_setting.lets_encrypt_private_key) + described_class.new.up end.not_to raise_error end end diff --git a/spec/support/matchers/eq_pem.rb b/spec/support/matchers/eq_pem.rb new file mode 100644 index 00000000000..158281e4a19 --- /dev/null +++ b/spec/support/matchers/eq_pem.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :eq_pem do |expected_pem_string| + match do |actual| + actual.to_pem == expected_pem_string + end + + description do + "contain pem #{expected_pem_string}" + end +end -- cgit v1.2.1 From 589b2db06ca2ca2bc3e5d9e56968e3609f9e4626 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 23 Apr 2019 16:27:01 +0200 Subject: Setup Phabricator import This sets up all the basics for importing Phabricator tasks into GitLab issues. To import all tasks from a Phabricator instance into GitLab, we'll import all of them into a new project that will have its repository disabled. The import is hooked into a regular ProjectImport setup, but similar to the GitHub parallel importer takes care of all the imports itself. In this iteration, we're importing each page of tasks in a separate sidekiq job. The first thing we do when requesting a new page of tasks is schedule the next page to be imported. But to avoid deadlocks, we only allow a single job per worker type to run at the same time. For now we're only importing basic Issue information, this should be extended to richer information. --- .../import/phabricator_controller_spec.rb | 92 ++++++++++++++++++++ .../phabricator_responses/auth_failed.json | 1 + .../phabricator_responses/maniphest.search.json | 98 ++++++++++++++++++++++ .../gitlab/github_import/parallel_importer_spec.rb | 11 +-- spec/lib/gitlab/import/set_async_jid_spec.rb | 23 +++++ spec/lib/gitlab/import_sources_spec.rb | 13 ++- .../gitlab/phabricator_import/base_worker_spec.rb | 74 ++++++++++++++++ .../gitlab/phabricator_import/cache/map_spec.rb | 66 +++++++++++++++ .../phabricator_import/conduit/client_spec.rb | 59 +++++++++++++ .../phabricator_import/conduit/maniphest_spec.rb | 39 +++++++++ .../phabricator_import/conduit/response_spec.rb | 79 +++++++++++++++++ .../conduit/tasks_response_spec.rb | 27 ++++++ .../phabricator_import/import_tasks_worker_spec.rb | 16 ++++ .../lib/gitlab/phabricator_import/importer_spec.rb | 32 +++++++ .../phabricator_import/issues/importer_spec.rb | 53 ++++++++++++ .../issues/task_importer_spec.rb | 54 ++++++++++++ .../phabricator_import/project_creator_spec.rb | 58 +++++++++++++ .../phabricator_import/representation/task_spec.rb | 33 ++++++++ .../gitlab/phabricator_import/worker_state_spec.rb | 46 ++++++++++ spec/routing/import_routing_spec.rb | 12 +++ 20 files changed, 872 insertions(+), 14 deletions(-) create mode 100644 spec/controllers/import/phabricator_controller_spec.rb create mode 100644 spec/fixtures/phabricator_responses/auth_failed.json create mode 100644 spec/fixtures/phabricator_responses/maniphest.search.json create mode 100644 spec/lib/gitlab/import/set_async_jid_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/base_worker_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/cache/map_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/conduit/client_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/conduit/response_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/importer_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/issues/importer_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/project_creator_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/representation/task_spec.rb create mode 100644 spec/lib/gitlab/phabricator_import/worker_state_spec.rb (limited to 'spec') diff --git a/spec/controllers/import/phabricator_controller_spec.rb b/spec/controllers/import/phabricator_controller_spec.rb new file mode 100644 index 00000000000..85085a8e996 --- /dev/null +++ b/spec/controllers/import/phabricator_controller_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Import::PhabricatorController do + let(:current_user) { create(:user) } + + before do + sign_in current_user + end + + describe 'GET #new' do + subject { get :new } + + context 'when the import source is not available' do + before do + stub_feature_flags(phabricator_import: true) + stub_application_setting(import_sources: []) + end + + it { is_expected.to have_gitlab_http_status(404) } + end + + context 'when the feature is disabled' do + before do + stub_feature_flags(phabricator_import: false) + stub_application_setting(import_sources: ['phabricator']) + end + + it { is_expected.to have_gitlab_http_status(404) } + end + + context 'when the import is available' do + before do + stub_feature_flags(phabricator_import: true) + stub_application_setting(import_sources: ['phabricator']) + end + + it { is_expected.to have_gitlab_http_status(200) } + end + end + + describe 'POST #create' do + subject(:post_create) { post :create, params: params } + + context 'with valid params' do + let(:params) do + { path: 'phab-import', + name: 'Phab import', + phabricator_server_url: 'https://phabricator.example.com', + api_token: 'hazaah', + namespace_id: current_user.namespace_id } + end + + it 'creates a project to import' do + expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer| + expect(importer).to receive(:execute) + end + + expect { post_create }.to change { current_user.namespace.projects.reload.size }.from(0).to(1) + + expect(current_user.namespace.projects.last).to be_import + end + end + + context 'when an import param is missing' do + let(:params) do + { path: 'phab-import', + name: 'Phab import', + phabricator_server_url: nil, + api_token: 'hazaah', + namespace_id: current_user.namespace_id } + end + + it 'does not create the project' do + expect { post_create }.not_to change { current_user.namespace.projects.reload.size } + end + end + + context 'when a project param is missing' do + let(:params) do + { phabricator_server_url: 'https://phabricator.example.com', + api_token: 'hazaah', + namespace_id: current_user.namespace_id } + end + + it 'does not create the project' do + expect { post_create }.not_to change { current_user.namespace.projects.reload.size } + end + end + end +end diff --git a/spec/fixtures/phabricator_responses/auth_failed.json b/spec/fixtures/phabricator_responses/auth_failed.json new file mode 100644 index 00000000000..50e57c0ba49 --- /dev/null +++ b/spec/fixtures/phabricator_responses/auth_failed.json @@ -0,0 +1 @@ +{"result":null,"error_code":"ERR-INVALID-AUTH","error_info":"API token \"api-token\" has the wrong length. API tokens should be 32 characters long."} diff --git a/spec/fixtures/phabricator_responses/maniphest.search.json b/spec/fixtures/phabricator_responses/maniphest.search.json new file mode 100644 index 00000000000..6a965007d0c --- /dev/null +++ b/spec/fixtures/phabricator_responses/maniphest.search.json @@ -0,0 +1,98 @@ +{ + "result": { + "data": [ + { + "id": 283, + "type": "TASK", + "phid": "PHID-TASK-fswfs3wkowjb6cyyxtyx", + "fields": { + "name": "Things are slow", + "description": { + "raw": "Things are slow but should be fast!" + }, + "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "ownerPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "status": { + "value": "resolved", + "name": "Resolved", + "color": null + }, + "priority": { + "value": 100, + "subpriority": 8589934592, + "name": "Super urgent", + "color": "pink" + }, + "points": null, + "subtype": "default", + "closerPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "dateClosed": 1374657042, + "spacePHID": null, + "dateCreated": 1374616241, + "dateModified": 1374657044, + "policy": { + "view": "users", + "interact": "users", + "edit": "users" + }, + "custom.field-1": null, + "custom.field-2": null, + "custom.field-3": null + }, + "attachments": {} + }, + { + "id": 284, + "type": "TASK", + "phid": "PHID-TASK-5f73nyq5sjeh4cbmcsnb", + "fields": { + "name": "Things are broken", + "description": { + "raw": "Things are broken and should be fixed" + }, + "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "ownerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot", + "status": { + "value": "resolved", + "name": "Resolved", + "color": null + }, + "priority": { + "value": 100, + "subpriority": 8589803520, + "name": "Super urgent", + "color": "pink" + }, + "points": null, + "subtype": "default", + "closerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot", + "dateClosed": 1375049556, + "spacePHID": null, + "dateCreated": 1374616578, + "dateModified": 1375049556, + "policy": { + "view": "users", + "interact": "users", + "edit": "users" + }, + "custom.field-1": null, + "custom.field-2": null, + "custom.field-3": null + }, + "attachments": {} + } + ], + "maps": {}, + "query": { + "queryKey": null + }, + "cursor": { + "limit": "2", + "after": "284", + "before": null, + "order": null + } + }, + "error_code": null, + "error_info": null +} diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb index f5df38c9aaf..ecab64a372a 100644 --- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb +++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb @@ -25,18 +25,9 @@ describe Gitlab::GithubImport::ParallelImporter do end it 'sets the JID in Redis' do - expect(Gitlab::SidekiqStatus) - .to receive(:set) - .with("github-importer/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) - .and_call_original + expect(Gitlab::Import::SetAsyncJid).to receive(:set_jid).with(project).and_call_original importer.execute end - - it 'updates the import JID of the project' do - importer.execute - - expect(project.import_state.reload.jid).to eq("github-importer/#{project.id}") - end end end diff --git a/spec/lib/gitlab/import/set_async_jid_spec.rb b/spec/lib/gitlab/import/set_async_jid_spec.rb new file mode 100644 index 00000000000..51397280138 --- /dev/null +++ b/spec/lib/gitlab/import/set_async_jid_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::Import::SetAsyncJid do + describe '.set_jid', :clean_gitlab_redis_shared_state do + let(:project) { create(:project, :import_scheduled) } + + it 'sets the JID in Redis' do + expect(Gitlab::SidekiqStatus) + .to receive(:set) + .with("async-import/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) + .and_call_original + + described_class.set_jid(project) + end + + it 'updates the import JID of the project' do + described_class.set_jid(project) + + expect(project.import_state.reload.jid).to eq("async-import/#{project.id}") + end + end +end diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb index 94abf9679c4..8060b5d4448 100644 --- a/spec/lib/gitlab/import_sources_spec.rb +++ b/spec/lib/gitlab/import_sources_spec.rb @@ -14,7 +14,8 @@ describe Gitlab::ImportSources do 'Repo by URL' => 'git', 'GitLab export' => 'gitlab_project', 'Gitea' => 'gitea', - 'Manifest file' => 'manifest' + 'Manifest file' => 'manifest', + 'Phabricator' => 'phabricator' } expect(described_class.options).to eq(expected) @@ -35,6 +36,7 @@ describe Gitlab::ImportSources do gitlab_project gitea manifest + phabricator ) expect(described_class.values).to eq(expected) @@ -53,6 +55,7 @@ describe Gitlab::ImportSources do fogbugz gitlab_project gitea + phabricator ) expect(described_class.importer_names).to eq(expected) @@ -70,7 +73,8 @@ describe Gitlab::ImportSources do 'git' => nil, 'gitlab_project' => Gitlab::ImportExport::Importer, 'gitea' => Gitlab::LegacyGithubImport::Importer, - 'manifest' => nil + 'manifest' => nil, + 'phabricator' => Gitlab::PhabricatorImport::Importer } import_sources.each do |name, klass| @@ -91,7 +95,8 @@ describe Gitlab::ImportSources do 'git' => 'Repo by URL', 'gitlab_project' => 'GitLab export', 'gitea' => 'Gitea', - 'manifest' => 'Manifest file' + 'manifest' => 'Manifest file', + 'phabricator' => 'Phabricator' } import_sources.each do |name, title| @@ -102,7 +107,7 @@ describe Gitlab::ImportSources do end describe 'imports_repository? checker' do - let(:allowed_importers) { %w[github gitlab_project bitbucket_server] } + let(:allowed_importers) { %w[github gitlab_project bitbucket_server phabricator] } it 'fails if any importer other than the allowed ones implements this method' do current_importers = described_class.values.select { |kind| described_class.importer(kind).try(:imports_repository?) } diff --git a/spec/lib/gitlab/phabricator_import/base_worker_spec.rb b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb new file mode 100644 index 00000000000..d46d908a3e3 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::BaseWorker do + let(:subclass) do + # Creating an anonymous class for a worker is complicated, as we generate the + # queue name from the class name. + Gitlab::PhabricatorImport::ImportTasksWorker + end + + describe '.schedule' do + let(:arguments) { %w[project_id the_next_page] } + + it 'schedules the job' do + expect(subclass).to receive(:perform_async).with(*arguments) + + subclass.schedule(*arguments) + end + + it 'counts the scheduled job', :clean_gitlab_redis_shared_state do + state = Gitlab::PhabricatorImport::WorkerState.new('project_id') + + allow(subclass).to receive(:remove_job) # otherwise the job is removed before we saw it + + expect { subclass.schedule(*arguments) }.to change { state.running_count }.by(1) + end + end + + describe '#perform' do + let(:project) { create(:project, :import_started, import_url: "https://a.phab.instance") } + let(:worker) { subclass.new } + let(:state) { Gitlab::PhabricatorImport::WorkerState.new(project.id) } + + before do + allow(worker).to receive(:import) + end + + it 'does not break for a non-existing project' do + expect { worker.perform('not a thing') }.not_to raise_error + end + + it 'does not do anything when the import is not in progress' do + project = create(:project, :import_failed) + + expect(worker).not_to receive(:import) + + worker.perform(project.id) + end + + it 'calls import for the project' do + expect(worker).to receive(:import).with(project, 'other_arg') + + worker.perform(project.id, 'other_arg') + end + + it 'marks the project as imported if there was only one job running' do + worker.perform(project.id) + + expect(project.import_state.reload).to be_finished + end + + it 'does not mark the job as finished when there are more scheduled jobs' do + 2.times { state.add_job } + + worker.perform(project.id) + + expect(project.import_state.reload).to be_in_progress + end + + it 'decrements the job counter' do + expect { worker.perform(project.id) }.to change { state.running_count }.by(-1) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb new file mode 100644 index 00000000000..52c7a02219f --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do + set(:project) { create(:project) } + let(:redis) { Gitlab::Redis::Cache } + subject(:map) { described_class.new(project) } + + describe '#get_gitlab_model' do + it 'returns nil if there was nothing cached for the phabricator id' do + expect(map.get_gitlab_model('does not exist')).to be_nil + end + + it 'returns the object if it was set in redis' do + issue = create(:issue, project: project) + set_in_redis('exists', issue) + + expect(map.get_gitlab_model('exists')).to eq(issue) + end + + it 'extends the TTL for the cache key' do + set_in_redis('extend', create(:issue, project: project)) do |redis| + redis.expire(cache_key('extend'), 10.seconds.to_i) + end + + map.get_gitlab_model('extend') + + ttl = redis.with { |redis| redis.ttl(cache_key('extend')) } + + expect(ttl).to be > 10.seconds + end + end + + describe '#set_gitlab_model' do + around do |example| + Timecop.freeze { example.run } + end + + it 'sets the class and id in redis with a ttl' do + issue = create(:issue, project: project) + + map.set_gitlab_model(issue, 'it is set') + + set_data, ttl = redis.with do |redis| + redis.pipelined do |p| + p.mapped_hmget(cache_key('it is set'), :classname, :database_id) + p.ttl(cache_key('it is set')) + end + end + + expect(set_data).to eq({ classname: 'Issue', database_id: issue.id.to_s }) + expect(ttl).to be_within(1.second).of(StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) + end + end + + def set_in_redis(key, object) + redis.with do |redis| + redis.mapped_hmset(cache_key(key), + { classname: object.class, database_id: object.id }) + yield(redis) if block_given? + end + end + + def cache_key(phabricator_id) + subject.__send__(:cache_key_for_phabricator_id, phabricator_id) + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb new file mode 100644 index 00000000000..542b3cd060f --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::Client do + let(:client) do + described_class.new('https://see-ya-later.phabricator', 'api-token') + end + + describe '#get' do + it 'performs and parses a request' do + params = { some: 'extra', values: %w[are passed] } + stub_valid_request(params) + + response = client.get('test', params: params) + + expect(response).to be_a(Gitlab::PhabricatorImport::Conduit::Response) + expect(response).to be_success + end + + it 'wraps request errors in an `ApiError`' do + stub_timeout + + expect { client.get('test') }.to raise_error(Gitlab::PhabricatorImport::Conduit::ApiError) + end + + it 'raises response error' do + stub_error_response + + expect { client.get('test') } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /has the wrong length/) + end + end + + def stub_valid_request(params = {}) + WebMock.stub_request( + :get, 'https://see-ya-later.phabricator/api/test' + ).with( + body: CGI.unescape(params.reverse_merge('api.token' => 'api-token').to_query) + ).and_return( + status: 200, + body: fixture_file('phabricator_responses/maniphest.search.json') + ) + end + + def stub_timeout + WebMock.stub_request( + :get, 'https://see-ya-later.phabricator/api/test' + ).to_timeout + end + + def stub_error_response + WebMock.stub_request( + :get, 'https://see-ya-later.phabricator/api/test' + ).and_return( + status: 200, + body: fixture_file('phabricator_responses/auth_failed.json') + ) + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb new file mode 100644 index 00000000000..0d7714649b9 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::Maniphest do + let(:maniphest) do + described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token') + end + + describe '#tasks' do + let(:fake_client) { double('Phabricator client') } + + before do + allow(maniphest).to receive(:client).and_return(fake_client) + end + + it 'calls the api with the correct params' do + expected_params = { + after: '123', + attachments: { + projects: 1, subscribers: 1, columns: 1 + } + } + + expect(fake_client).to receive(:get).with('maniphest.search', + params: expected_params) + + maniphest.tasks(after: '123') + end + + it 'returns a parsed response' do + response = Gitlab::PhabricatorImport::Conduit::Response + .new(fixture_file('phabricator_responses/maniphest.search.json')) + + allow(fake_client).to receive(:get).and_return(response) + + expect(maniphest.tasks).to be_a(Gitlab::PhabricatorImport::Conduit::TasksResponse) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb new file mode 100644 index 00000000000..a8596968f14 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::Response do + let(:response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))} + let(:error_response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/auth_failed.json'))) } + + describe '.parse!' do + it 'raises a ResponseError if the http response was not successfull' do + fake_response = double(:http_response, success?: false, status: 401) + + expect { described_class.parse!(fake_response) } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /responded with 401/) + end + + it 'raises a ResponseError if the response contained a Phabricator error' do + fake_response = double(:http_response, + success?: true, + status: 200, + body: fixture_file('phabricator_responses/auth_failed.json')) + + expect { described_class.parse!(fake_response) } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /ERR-INVALID-AUTH: API token/) + end + + it 'raises a ResponseError if JSON parsing failed' do + fake_response = double(:http_response, + success?: true, + status: 200, + body: 'This is no JSON') + + expect { described_class.parse!(fake_response) } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected token at/) + end + + it 'returns a parsed response for valid input' do + fake_response = double(:http_response, + success?: true, + status: 200, + body: fixture_file('phabricator_responses/maniphest.search.json')) + + expect(described_class.parse!(fake_response)).to be_a(described_class) + end + end + + describe '#success?' do + it { expect(response).to be_success } + it { expect(error_response).not_to be_success } + end + + describe '#error_code' do + it { expect(error_response.error_code).to eq('ERR-INVALID-AUTH') } + it { expect(response.error_code).to be_nil } + end + + describe '#error_info' do + it 'returns the correct error info' do + expected_message = 'API token "api-token" has the wrong length. API tokens should be 32 characters long.' + + expect(error_response.error_info).to eq(expected_message) + end + + it { expect(response.error_info).to be_nil } + end + + describe '#data' do + it { expect(error_response.data).to be_nil } + it { expect(response.data).to be_an(Array) } + end + + describe '#pagination' do + it { expect(error_response.pagination).to be_nil } + + it 'builds the pagination correctly' do + expect(response.pagination).to be_a(Gitlab::PhabricatorImport::Conduit::Pagination) + expect(response.pagination.next_page).to eq('284') + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb new file mode 100644 index 00000000000..4b4c2a6276e --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::TasksResponse do + let(:conduit_response) do + Gitlab::PhabricatorImport::Conduit::Response + .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json'))) + end + + subject(:response) { described_class.new(conduit_response) } + + describe '#pagination' do + it 'delegates to the conduit reponse' do + expect(response.pagination).to eq(conduit_response.pagination) + end + end + + describe '#tasks' do + it 'builds the correct tasks representation' do + tasks = response.tasks + + titles = tasks.map(&:issue_attributes).map { |attrs| attrs[:title] } + + expect(titles).to contain_exactly('Things are slow', 'Things are broken') + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb new file mode 100644 index 00000000000..1e38ef8aaa5 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::ImportTasksWorker do + describe '#perform' do + it 'calls the correct importer' do + project = create(:project, :import_started, import_url: "https://the.phab.ulr") + fake_importer = instance_double(Gitlab::PhabricatorImport::Issues::Importer) + + expect(Gitlab::PhabricatorImport::Issues::Importer).to receive(:new).with(project).and_return(fake_importer) + expect(fake_importer).to receive(:execute) + + described_class.new.perform(project.id) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb new file mode 100644 index 00000000000..bf14010a187 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Importer do + it { expect(described_class).to be_async } + + it "acts like it's importing repositories" do + expect(described_class).to be_imports_repository + end + + describe '#execute' do + let(:project) { create(:project, :import_scheduled) } + subject(:importer) { described_class.new(project) } + + it 'sets a custom jid that will be kept up to date' do + expect { importer.execute }.to change { project.import_state.reload.jid } + end + + it 'starts importing tasks' do + expect(Gitlab::PhabricatorImport::ImportTasksWorker).to receive(:schedule).with(project.id) + + importer.execute + end + + it 'marks the import as failed when something goes wrong' do + allow(importer).to receive(:schedule_first_tasks_page).and_raise('Stuff is broken') + + importer.execute + + expect(project.import_state).to be_failed + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb new file mode 100644 index 00000000000..2412cf76f79 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Issues::Importer do + set(:project) { create(:project) } + + let(:response) do + Gitlab::PhabricatorImport::Conduit::TasksResponse.new( + Gitlab::PhabricatorImport::Conduit::Response + .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json'))) + ) + end + + subject(:importer) { described_class.new(project, nil) } + + before do + client = instance_double(Gitlab::PhabricatorImport::Conduit::Maniphest) + + allow(client).to receive(:tasks).and_return(response) + allow(importer).to receive(:client).and_return(client) + end + + describe '#execute' do + it 'imports each task in the response' do + response.tasks.each do |task| + task_importer = instance_double(Gitlab::PhabricatorImport::Issues::TaskImporter) + + expect(task_importer).to receive(:execute) + expect(Gitlab::PhabricatorImport::Issues::TaskImporter) + .to receive(:new).with(project, task) + .and_return(task_importer) + end + + importer.execute + end + + it 'schedules the next batch if there is one' do + expect(Gitlab::PhabricatorImport::ImportTasksWorker) + .to receive(:schedule).with(project.id, response.pagination.next_page) + + importer.execute + end + + it 'does not reschedule when there is no next page' do + allow(response.pagination).to receive(:has_next_page?).and_return(false) + + expect(Gitlab::PhabricatorImport::ImportTasksWorker) + .not_to receive(:schedule) + + importer.execute + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb new file mode 100644 index 00000000000..1625604e754 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Issues::TaskImporter do + set(:project) { create(:project) } + let(:task) do + Gitlab::PhabricatorImport::Representation::Task.new( + { + 'phid' => 'the-phid', + 'fields' => { + 'name' => 'Title', + 'description' => { + 'raw' => '# This is markdown\n it can contain more text.' + }, + 'dateCreated' => '1518688921', + 'dateClosed' => '1518789995' + } + } + ) + end + + describe '#execute' do + it 'creates the issue with the expected attributes' do + issue = described_class.new(project, task).execute + + expect(issue.project).to eq(project) + expect(issue).to be_persisted + expect(issue.author).to eq(User.ghost) + expect(issue.title).to eq('Title') + expect(issue.description).to eq('# This is markdown\n it can contain more text.') + expect(issue).to be_closed + expect(issue.created_at).to eq(Time.at(1518688921)) + expect(issue.closed_at).to eq(Time.at(1518789995)) + end + + it 'does not recreate the issue when called multiple times' do + expect { described_class.new(project, task).execute } + .to change { project.issues.reload.size }.from(0).to(1) + expect { described_class.new(project, task).execute } + .not_to change { project.issues.reload.size } + end + + it 'does not trigger a save when the object did not change' do + existing_issue = create(:issue, + task.issue_attributes.merge(author: User.ghost)) + importer = described_class.new(project, task) + allow(importer).to receive(:issue).and_return(existing_issue) + + expect(existing_issue).not_to receive(:save!) + + importer.execute + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb new file mode 100644 index 00000000000..e9455b866ac --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::ProjectCreator do + let(:user) { create(:user) } + let(:params) do + { path: 'new-phab-import', + phabricator_server_url: 'http://phab.example.com', + api_token: 'the-token' } + end + subject(:creator) { described_class.new(user, params) } + + describe '#execute' do + it 'creates a project correctly and schedule an import' do + expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer| + expect(importer).to receive(:execute) + end + + project = creator.execute + + expect(project).to be_persisted + expect(project).to be_import + expect(project.import_type).to eq('phabricator') + expect(project.import_data.credentials).to match(a_hash_including(api_token: 'the-token')) + expect(project.import_data.data).to match(a_hash_including('phabricator_url' => 'http://phab.example.com')) + expect(project.import_url).to eq(Project::UNKNOWN_IMPORT_URL) + expect(project.namespace).to eq(user.namespace) + end + + context 'when import params are missing' do + let(:params) do + { path: 'new-phab-import', + phabricator_server_url: 'http://phab.example.com', + api_token: '' } + end + + it 'returns nil' do + expect(creator.execute).to be_nil + end + end + + context 'when import params are invalid' do + let(:params) do + { path: 'new-phab-import', + namespace_id: '-1', + phabricator_server_url: 'http://phab.example.com', + api_token: 'the-token' } + end + + it 'returns an unpersisted project' do + project = creator.execute + + expect(project).not_to be_persisted + expect(project).not_to be_valid + end + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb new file mode 100644 index 00000000000..dfbd8c546eb --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Representation::Task do + subject(:task) do + described_class.new( + { + 'phid' => 'the-phid', + 'fields' => { + 'name' => 'Title'.ljust(257, '.'), # A string padded to 257 chars + 'description' => { + 'raw' => '# This is markdown\n it can contain more text.' + }, + 'dateCreated' => '1518688921', + 'dateClosed' => '1518789995' + } + } + ) + end + + describe '#issue_attributes' do + it 'contains the expected values' do + expected_attributes = { + title: 'Title'.ljust(255, '.'), + description: '# This is markdown\n it can contain more text.', + state: :closed, + created_at: Time.at(1518688921), + closed_at: Time.at(1518789995) + } + + expect(task.issue_attributes).to eq(expected_attributes) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb new file mode 100644 index 00000000000..a44947445c9 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do + subject(:state) { described_class.new('weird-project-id') } + let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' } + + describe '#add_job' do + it 'increments the counter for jobs' do + set_value(3) + + expect { state.add_job }.to change { get_value }.from('3').to('4') + end + end + + describe '#remove_job' do + it 'decrements the counter for jobs' do + set_value(3) + + expect { state.remove_job }.to change { get_value }.from('3').to('2') + end + end + + describe '#running_count' do + it 'reads the value' do + set_value(9) + + expect(state.running_count).to eq(9) + end + + it 'returns 0 when nothing was set' do + expect(state.running_count).to eq(0) + end + end + + def set_value(value) + redis.with { |r| r.set(key, value) } + end + + def get_value + redis.with { |r| r.get(key) } + end + + def redis + Gitlab::Redis::SharedState + end +end diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb index 106f92082e4..3fdede7914d 100644 --- a/spec/routing/import_routing_spec.rb +++ b/spec/routing/import_routing_spec.rb @@ -174,3 +174,15 @@ describe Import::GitlabProjectsController, 'routing' do expect(get('/import/gitlab_project/new')).to route_to('import/gitlab_projects#new') end end + +# new_import_phabricator GET /import/phabricator/new(.:format) import/phabricator#new +# import_phabricator POST /import/phabricator(.:format) import/phabricator#create +describe Import::PhabricatorController, 'routing' do + it 'to #create' do + expect(post("/import/phabricator")).to route_to("import/phabricator#create") + end + + it 'to #new' do + expect(get("/import/phabricator/new")).to route_to("import/phabricator#new") + end +end -- cgit v1.2.1 From 699532232ca27e6079c553261e0ab1d17317472a Mon Sep 17 00:00:00 2001 From: charlie ablett Date: Thu, 23 May 2019 22:43:47 +0000 Subject: Apply reviewer feedback - Comply doc with guidelines - Improve tests for readability and completeness - Separate out phases visually with newlines - Add `format_message` test - test readability - code and test structure/styling - static query analyzers - call `as_json` on `provided_variables` - add exception handling --- .../query_analyzers/logger_analyzer_spec.rb | 10 ++----- spec/lib/gitlab/graphql_logger_spec.rb | 13 ++++----- spec/requests/api/graphql/gitlab_schema_spec.rb | 31 +++++++++++++--------- spec/requests/api/graphql_spec.rb | 26 +++++++++++++++--- 4 files changed, 50 insertions(+), 30 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb index cbf35c1095a..66033736e01 100644 --- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -5,25 +5,19 @@ require 'spec_helper' describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do subject { described_class.new } - let!(:now) { Gitlab::Metrics::System.monotonic_time } - - before do - allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(now) - end - describe '#analyze?' do context 'feature flag disabled' do before do stub_feature_flags(graphql_logging: false) end - it 'enables the analyzer' do + it 'disables the analyzer' do expect(subject.analyze?(anything)).to be_falsey end end context 'feature flag enabled by default' do - specify do + it 'enables the analyzer' do expect(subject.analyze?(anything)).to be_truthy end end diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb index c8d4dac2153..4977f98b83e 100644 --- a/spec/lib/gitlab/graphql_logger_spec.rb +++ b/spec/lib/gitlab/graphql_logger_spec.rb @@ -19,15 +19,16 @@ describe Gitlab::GraphqlLogger do it 'logs a query from JSON' do analyzer_memo = { - query_string: query, - variables: {}, - complexity: 181, - depth: 0, - duration: 7 + query_string: query, + variables: {}, + complexity: 181, + depth: 0, + duration: 7 } + output = subject.format_message('INFO', now, 'test', analyzer_memo) - data = JSON.parse(output) + data = JSON.parse(output) expect(data['severity']).to eq('INFO') expect(data['time']).to eq(now.utc.iso8601(3)) expect(data['complexity']).to eq(181) diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index 510dec5edb2..1017e409f6c 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -83,31 +83,38 @@ describe 'GitlabSchema configurations' do end end + context 'when IntrospectionQuery' do + it 'is not too complex' do + query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) + + post_graphql(query, current_user: nil) + + expect(graphql_errors).to be_nil + end + end + context 'logging' do let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) } - it 'logs the query complexity' do + it 'logs the query complexity and depth' do analyzer_memo = { - query_string: query, - variables: {}, - complexity: 181, - depth: 0, - duration: "7ms" + query_string: query, + variables: {}.to_s, + complexity: 181, + depth: 0, + duration: 7 } + expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7) expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo) post_graphql(query, current_user: nil) end - end - context 'when IntrospectionQuery' do - it 'is not too complex' do - query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) + it 'logs using `format_message`' do + expect_any_instance_of(Gitlab::GraphqlLogger).to receive(:format_message) post_graphql(query, current_user: nil) - - expect(graphql_errors).to be_nil end end end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index abc24fc0fe8..656d6f8b50b 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -19,16 +19,21 @@ describe 'GraphQL' do context 'logging' do shared_examples 'logging a graphql query' do let(:expected_params) do - { query_string: query, variables: variables.to_json, duration: anything, depth: 1, complexity: 1 } + { query_string: query, variables: variables.to_s, duration: anything, depth: 1, complexity: 1 } end it 'logs a query with the expected params' do + expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once + post_graphql(query, variables: variables) end - end - before do - expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once + it 'does not instantiate any query analyzers' do # they are static and re-used + expect(GraphQL::Analysis::QueryComplexity).not_to receive(:new) + expect(GraphQL::Analysis::QueryDepth).not_to receive(:new) + + 2.times { post_graphql(query, variables: variables) } + end end context 'with no variables' do @@ -44,6 +49,19 @@ describe 'GraphQL' do it_behaves_like 'logging a graphql query' end + + context 'when there is an error in the logger' do + before do + allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:process_variables).and_raise(StandardError.new("oh noes!")) + end + + it 'logs the exception in Sentry and continues with the request' do + expect(Gitlab::Sentry).to receive(:track_exception).at_least(1).times + expect(Gitlab::GraphqlLogger).to receive(:info) + + post_graphql(query, variables: {}) + end + end end context 'invalid variables' do -- cgit v1.2.1 From 724b5afbd99ab31242eed386d9169a1f1b5a7610 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Fri, 31 May 2019 09:59:36 +0000 Subject: Remove nils from project_statistics.packages_size Now it defaults to 0 --- ...ges_size_defaults_in_project_statistics_spec.rb | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb (limited to 'spec') diff --git a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb new file mode 100644 index 00000000000..93e7e9304b1 --- /dev/null +++ b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190516155724_change_packages_size_defaults_in_project_statistics.rb') + +describe ChangePackagesSizeDefaultsInProjectStatistics, :migration do + let(:project_statistics) { table(:project_statistics) } + let(:projects) { table(:projects) } + + it 'removes null packages_size' do + stats_to_migrate = 10 + + stats_to_migrate.times do |i| + p = projects.create!(name: "project #{i}", namespace_id: 1) + project_statistics.create!(project_id: p.id, namespace_id: p.namespace_id) + end + + expect { migrate! } + .to change { ProjectStatistics.where(packages_size: nil).count } + .from(stats_to_migrate) + .to(0) + end + + it 'defaults packages_size to 0' do + project = projects.create!(name: 'a new project', namespace_id: 2) + stat = project_statistics.create!(project_id: project.id, namespace_id: project.namespace_id) + + expect(stat.packages_size).to be_nil + + migrate! + + stat.reload + expect(stat.packages_size).to eq(0) + end +end -- cgit v1.2.1 From 5e8184cc6601bfc690f533b48993904dfe3833eb Mon Sep 17 00:00:00 2001 From: Luke Picciau Date: Fri, 31 May 2019 10:31:47 +0000 Subject: Change query to work on mysql as well. Also set entire date because setting only the year can trip 'start_date_should_be_less_than_due_date' --- spec/models/milestone_spec.rb | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index b82368318f2..752a7965704 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -31,12 +31,28 @@ describe Milestone do end describe 'start_date' do - it 'adds an error when start_date is greated then due_date' do + it 'adds an error when start_date is greater then due_date' do milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday) expect(milestone).not_to be_valid expect(milestone.errors[:due_date]).to include("must be greater than start date") end + + it 'adds an error when start_date is greater than 9999-12-31' do + milestone = build(:milestone, start_date: Date.new(10000, 1, 1)) + + expect(milestone).not_to be_valid + expect(milestone.errors[:start_date]).to include("date must not be after 9999-12-31") + end + end + + describe 'due_date' do + it 'adds an error when due_date is greater than 9999-12-31' do + milestone = build(:milestone, due_date: Date.new(10000, 1, 1)) + + expect(milestone).not_to be_valid + expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31") + end end end @@ -381,21 +397,6 @@ describe Milestone do expect(milestone_ids).to be_empty end end - - context 'when there is a milestone with a date after 294276 AD', :postgresql do - before do - past_milestone_project_1.update!(due_date: Date.new(294277, 1, 1)) - end - - it 'returns the next upcoming open milestone ID for each project and group' do - expect(milestone_ids).to contain_exactly( - current_milestone_project_1.id, - current_milestone_project_2.id, - current_milestone_group_1.id, - current_milestone_group_2.id - ) - end - end end describe '#to_reference' do -- cgit v1.2.1 From 7099211ea7eb409e7b13670a3ecaa8503f4714fc Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Fri, 31 May 2019 14:45:37 +0200 Subject: Fix emoji picker visibility issue --- spec/javascripts/notes/components/note_actions_spec.js | 1 + 1 file changed, 1 insertion(+) (limited to 'spec') diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index 0cfcc994234..2159e4ddf16 100644 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -58,6 +58,7 @@ describe('noteActions', () => { it('should render emoji link', () => { expect(wrapper.find('.js-add-award').exists()).toBe(true); + expect(wrapper.find('.js-add-award').attributes('data-position')).toBe('right'); }); describe('actions dropdown', () => { -- cgit v1.2.1 From 1a402d888c05196212d1ba671368837e85246c9c Mon Sep 17 00:00:00 2001 From: Wei-Meng Lee Date: Sat, 16 Feb 2019 01:56:13 +0800 Subject: Send notifications to group-specific email address - Select notification email by walking up group/subgroup path - Add settings UI to set group email notification address - Add tests --- spec/mailers/emails/pages_domains_spec.rb | 6 ++- spec/mailers/notify_spec.rb | 8 +++- .../shared_examples/notify_shared_examples.rb | 46 +++++++++++++++++++++- 3 files changed, 55 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb index 050af587061..2f594dbf9d1 100644 --- a/spec/mailers/emails/pages_domains_spec.rb +++ b/spec/mailers/emails/pages_domains_spec.rb @@ -5,11 +5,13 @@ describe Emails::PagesDomains do include EmailSpec::Matchers include_context 'gitlab email notification' - set(:project) { create(:project) } set(:domain) { create(:pages_domain, project: project) } - set(:user) { project.owner } + set(:user) { project.creator } shared_examples 'a pages domain email' do + let(:test_recipient) { user } + + it_behaves_like 'an email sent to a user' it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'a user cannot unsubscribe through footer link' diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 8f348b1b053..67c1aa69db4 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -45,6 +45,10 @@ describe Notify do context 'for a project' do shared_examples 'an assignee email' do + let(:test_recipient) { assignee } + + it_behaves_like 'an email sent to a user' + it 'is sent to the assignee as the author' do sender = subject.header[:from].addrs.first @@ -618,8 +622,10 @@ describe Notify do end describe 'project was moved' do + let(:test_recipient) { user } subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } + it_behaves_like 'an email sent to a user' it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" @@ -1083,8 +1089,6 @@ describe Notify do end context 'for a group' do - set(:group) { create(:group) } - describe 'group access requested' do let(:group) { create(:group, :public, :access_requestable) } let(:group_member) do diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 4fff1c4e228..bcb30a86727 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -1,5 +1,7 @@ shared_context 'gitlab email notification' do - set(:project) { create(:project, :repository, name: 'a-known-name') } + set(:group) { create(:group) } + set(:subgroup) { create(:group, parent: group) } + set(:project) { create(:project, :repository, name: 'a-known-name', group: group) } set(:recipient) { create(:user, email: 'recipient@example.com') } let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } @@ -39,6 +41,48 @@ shared_examples 'an email sent from GitLab' do end end +shared_examples 'an email sent to a user' do + let(:group_notification_email) { 'user+group@example.com' } + + it 'is sent to user\'s global notification email address' do + expect(subject).to deliver_to(test_recipient.notification_email) + end + + context 'that is part of a project\'s group' do + it 'is sent to user\'s group notification email address when set' do + create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email) + expect(subject).to deliver_to(group_notification_email) + end + + it 'is sent to user\'s global notification email address when no group email set' do + create(:notification_setting, user: test_recipient, source: project.group, notification_email: '') + expect(subject).to deliver_to(test_recipient.notification_email) + end + end + + context 'when project is in a sub-group' do + before do + project.group = subgroup + project.save! + end + + it 'is sent to user\'s subgroup notification email address when set' do + # Set top-level group notification email address to make sure it doesn't get selected + create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email) + + subgroup_notification_email = 'user+subgroup@example.com' + create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email) + + expect(subject).to deliver_to(subgroup_notification_email) + end + + it 'is sent to user\'s group notification email address when set and subgroup email address not set' do + create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '') + expect(subject).to deliver_to(test_recipient.notification_email) + end + end +end + shared_examples 'an email that contains a header with author username' do it 'has X-GitLab-Author header containing author\'s username' do is_expected.to have_header 'X-GitLab-Author', user.username -- cgit v1.2.1 From 11fb4d4209cdb31cbcd64a9ec057a90cd2e63618 Mon Sep 17 00:00:00 2001 From: Wei-Meng Lee Date: Wed, 3 Apr 2019 17:04:14 +0800 Subject: Fix typo in project invitation declined test --- spec/mailers/notify_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 67c1aa69db4..cbbb22ad78c 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -779,7 +779,7 @@ describe Notify do invitee end - subject { described_class.member_invite_declined_email('project', project.id, project_member.invite_email, maintainer.id) } + subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) } it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' -- cgit v1.2.1 From ddb0344a8ff43555061a0775875ea38cbebf7c52 Mon Sep 17 00:00:00 2001 From: Wei-Meng Lee <1081658-weimeng@users.noreply.gitlab.com> Date: Fri, 31 May 2019 10:31:28 +0000 Subject: Apply reviewer suggestions --- spec/support/shared_examples/notify_shared_examples.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index bcb30a86727..897c9106d77 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -60,10 +60,9 @@ shared_examples 'an email sent to a user' do end end - context 'when project is in a sub-group' do + context 'when project is in a sub-group', :nested_groups do before do - project.group = subgroup - project.save! + project.update!(group: subgroup) end it 'is sent to user\'s subgroup notification email address when set' do -- cgit v1.2.1 From 9181e4b135053140fe61f785603eb8101f4534f1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 24 May 2019 20:20:05 +0100 Subject: Fix search dropdown not closing on blur if empty --- spec/features/global_search_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index d7692181453..f2ab5373d3d 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -25,4 +25,18 @@ describe 'Global search' do expect(page).to have_selector('.gl-pagination .next') end end + + it 'closes the dropdown on blur', :js do + visit dashboard_projects_path + + fill_in 'search', with: "a" + dropdown = find('.js-dashboard-search-options') + + expect(dropdown[:class]).to include 'show' + + find('#search').send_keys(:backspace) + find('body').click + + expect(dropdown[:class]).not_to include 'show' + end end -- cgit v1.2.1 From dbd6223211ec5046535544ea3fe0b8986f51a7e1 Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Thu, 30 May 2019 10:23:34 +0100 Subject: Revert a default GIT_DEPTH for MR pipeline --- spec/presenters/ci/build_runner_presenter_spec.rb | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index ad6cb012d0b..3430111ca9e 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -136,24 +136,6 @@ describe Ci::BuildRunnerPresenter do is_expected.to eq(1) end end - - context 'when pipeline is detached merge request pipeline' do - let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } - let(:pipeline) { merge_request.all_pipelines.first } - let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } - - it 'returns the default git depth for pipelines for merge requests' do - is_expected.to eq(described_class::DEFAULT_GIT_DEPTH_MERGE_REQUEST) - end - - context 'when pipeline is legacy detached merge request pipeline' do - let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } - - it 'behaves as branch pipeline' do - is_expected.to eq(0) - end - end - end end describe '#refspecs' do @@ -191,7 +173,9 @@ describe Ci::BuildRunnerPresenter do it 'returns the correct refspecs' do is_expected - .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') + .to contain_exactly('+refs/heads/*:refs/remotes/origin/*', + '+refs/tags/*:refs/tags/*', + '+refs/merge-requests/1/head:refs/merge-requests/1/head') end context 'when pipeline is legacy detached merge request pipeline' do -- cgit v1.2.1 From 35c37fb29343974bf2edc3959d6a21db9c384307 Mon Sep 17 00:00:00 2001 From: Kerri Miller Date: Fri, 31 May 2019 14:21:15 +0000 Subject: Add optional param :start_project to allow variable commit targets This extends POST#:id/repository/commits to allow the optional parameter `:start_project`, which will allow targeting other projects besides the one derived from `:id`. Resolves https://gitlab.com/gitlab-org/gitlab-ce/issues/50850 --- spec/requests/api/commits_spec.rb | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'spec') diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index a132b85b878..f104da6ebba 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' require 'mime/types' describe API::Commits do + include ProjectForksHelper + let(:user) { create(:user) } let(:guest) { create(:user).tap { |u| project.add_guest(u) } } let(:project) { create(:project, :repository, creator: user, path: 'my.project') } @@ -317,6 +319,96 @@ describe API::Commits do expect(response).to have_gitlab_http_status(201) end end + + context 'when the API user is a guest' do + def last_commit_id(project, branch_name) + project.repository.find_branch(branch_name)&.dereferenced_target&.id + end + + let(:public_project) { create(:project, :public, :repository) } + let!(:url) { "/projects/#{public_project.id}/repository/commits" } + let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } } + + it 'returns a 403' do + post api(url, guest), params: valid_c_params + + expect(response).to have_gitlab_http_status(403) + end + + context 'when start_project is provided' do + context 'when posting to a forked project the user owns' do + let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) } + let!(:url) { "/projects/#{forked_project.id}/repository/commits" } + + before do + valid_c_params[:start_branch] = "master" + valid_c_params[:branch] = "patch" + end + + context 'identified by Integer (id)' do + before do + valid_c_params[:start_project] = public_project.id + end + + it 'adds a new commit to forked_project and returns a 201' do + expect { post api(url, guest), params: valid_c_params } + .to change { last_commit_id(forked_project, valid_c_params[:branch]) } + .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) } + + expect(response).to have_gitlab_http_status(201) + end + end + + context 'identified by String (full_path)' do + before do + valid_c_params[:start_project] = public_project.full_path + end + + it 'adds a new commit to forked_project and returns a 201' do + expect { post api(url, guest), params: valid_c_params } + .to change { last_commit_id(forked_project, valid_c_params[:branch]) } + .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) } + + expect(response).to have_gitlab_http_status(201) + end + end + end + + context 'when the target project is not part of the fork network of start_project' do + let(:unrelated_project) { create(:project, :public, :repository, creator: guest) } + let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" } + + before do + valid_c_params[:start_branch] = "master" + valid_c_params[:branch] = "patch" + valid_c_params[:start_project] = public_project.id + end + + it 'returns a 403' do + post api(url, guest), params: valid_c_params + + expect(response).to have_gitlab_http_status(403) + end + end + end + + context 'when posting to a forked project the user does not have write access' do + let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) } + let!(:url) { "/projects/#{forked_project.id}/repository/commits" } + + before do + valid_c_params[:start_branch] = "master" + valid_c_params[:branch] = "patch" + valid_c_params[:start_project] = public_project.id + end + + it 'returns a 403' do + post api(url, guest), params: valid_c_params + + expect(response).to have_gitlab_http_status(403) + end + end + end end describe 'delete' do -- cgit v1.2.1 From 1285b0051705519488fbc7ac3bf8511864560ac9 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Fri, 31 May 2019 11:46:16 -0500 Subject: Added common fields to the IssueType and allow passing of child_complexity to the 'resolver_complexity' metho --- spec/graphql/types/base_field_spec.rb | 2 +- spec/graphql/types/issue_type_spec.rb | 6 ++++++ spec/presenters/issue_presenter_spec.rb | 29 +++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 spec/presenters/issue_presenter_spec.rb (limited to 'spec') diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb index 4fe426e2447..a7fb156d9a8 100644 --- a/spec/graphql/types/base_field_spec.rb +++ b/spec/graphql/types/base_field_spec.rb @@ -6,7 +6,7 @@ describe Types::BaseField do context 'when considering complexity' do let(:resolver) do Class.new(described_class) do - def self.resolver_complexity(args) + def self.resolver_complexity(args, child_complexity:) 2 if args[:foo] end diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index dc37b15001f..bae560829cc 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -6,4 +6,10 @@ describe GitlabSchema.types['Issue'] do it { expect(described_class.graphql_name).to eq('Issue') } it { expect(described_class).to require_graphql_authorizations(:read_issue) } + + it 'has specific fields' do + %i[relative_position web_path web_url reference].each do |field_name| + expect(described_class).to have_graphql_field(field_name) + end + end end diff --git a/spec/presenters/issue_presenter_spec.rb b/spec/presenters/issue_presenter_spec.rb new file mode 100644 index 00000000000..8e24559341b --- /dev/null +++ b/spec/presenters/issue_presenter_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe IssuePresenter do + include Gitlab::Routing.url_helpers + + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:issue) { create(:issue, project: project) } + let(:presenter) { described_class.new(issue, current_user: user) } + + before do + group.add_developer(user) + end + + describe '#web_url' do + it 'returns correct path' do + expect(presenter.web_url).to eq "http://localhost/#{group.name}/#{project.name}/issues/#{issue.iid}" + end + end + + describe '#issue_path' do + it 'returns correct path' do + expect(presenter.issue_path).to eq "/#{group.name}/#{project.name}/issues/#{issue.iid}" + end + end +end -- cgit v1.2.1 From f3efec202986f820eaa1dc76db34b095e73a99f6 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 28 May 2019 18:28:12 -0300 Subject: Reset merge status from mergeable MRs Adds migrations to reset the merge_status of opened, mergeable MRs. That's required by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28513 so we're able to sync the status update along merge-ref, without leaving MRs with a stale merge-ref. --- .../reset_merge_status_spec.rb | 48 +++++++++++++++++++++ spec/migrations/enqueue_reset_merge_status_spec.rb | 49 ++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 spec/lib/gitlab/background_migration/reset_merge_status_spec.rb create mode 100644 spec/migrations/enqueue_reset_merge_status_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb new file mode 100644 index 00000000000..740781f1aa5 --- /dev/null +++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Gitlab::BackgroundMigration::ResetMergeStatus, :migration, schema: 20190528180441 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:merge_requests) { table(:merge_requests) } + + def create_merge_request(id, extra_params = {}) + params = { + id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}" + }.merge(extra_params) + + merge_requests.create!(params) + end + + it 'correctly updates opened mergeable MRs to unchecked' do + create_merge_request(1, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(2, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(3, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(4, state: 'merged', merge_status: 'can_be_merged') + create_merge_request(5, state: 'opened', merge_status: 'cannot_be_merged') + + subject.perform(1, 5) + + expected_rows = [ + { id: 1, state: 'opened', merge_status: 'unchecked' }, + { id: 2, state: 'opened', merge_status: 'unchecked' }, + { id: 3, state: 'opened', merge_status: 'unchecked' }, + { id: 4, state: 'merged', merge_status: 'can_be_merged' }, + { id: 5, state: 'opened', merge_status: 'cannot_be_merged' } + ] + + rows = merge_requests.order(:id).map do |row| + row.attributes.slice('id', 'state', 'merge_status').symbolize_keys + end + + expect(rows).to eq(expected_rows) + end +end diff --git a/spec/migrations/enqueue_reset_merge_status_spec.rb b/spec/migrations/enqueue_reset_merge_status_spec.rb new file mode 100644 index 00000000000..0d5e33bfd46 --- /dev/null +++ b/spec/migrations/enqueue_reset_merge_status_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190528180441_enqueue_reset_merge_status.rb') + +describe EnqueueResetMergeStatus, :migration, :sidekiq do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:merge_requests) { table(:merge_requests) } + + def create_merge_request(id, extra_params = {}) + params = { + id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}" + }.merge(extra_params) + + merge_requests.create!(params) + end + + it 'correctly schedules background migrations' do + create_merge_request(1, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(2, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(3, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(4, state: 'merged', merge_status: 'can_be_merged') + create_merge_request(5, state: 'opened', merge_status: 'unchecked') + + stub_const("#{described_class.name}::BATCH_SIZE", 2) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(5.minutes, 1, 2) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(10.minutes, 3, 3) + + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end -- cgit v1.2.1 From b965009ddddcd50e76841dbc97d2767292e88a0a Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 21 May 2019 18:14:22 -0300 Subject: Automatically update MR merge-ref along merge status This couples the code that transitions the `MergeRequest#merge_status` and refs/merge-requests/:iid/merge ref update. In general, instead of directly telling `MergeToRefService` to update the merge ref, we should rely on `MergeabilityCheckService` to keep both the merge status and merge ref synced. Now, if the merge_status is `can_be_merged` it means the merge-ref is also updated to the latest. We've also updated the logic to be more systematic and less user-based. --- spec/models/merge_request_spec.rb | 87 +------------------- spec/requests/api/merge_requests_spec.rb | 61 ++++++++------ .../merge_requests/merge_to_ref_service_spec.rb | 41 +++------- .../mergeability_check_service_spec.rb | 92 ++++++++++++++++++++++ 4 files changed, 140 insertions(+), 141 deletions(-) create mode 100644 spec/services/merge_requests/mergeability_check_service_spec.rb (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c72b6e9033d..6e703023ed5 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1962,57 +1962,6 @@ describe MergeRequest do end end - describe '#check_if_can_be_merged' do - let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) } - - shared_examples 'checking if can be merged' do - context 'when it is not broken and has no conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(true) - end - - it 'is marked as mergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') - end - end - - context 'when broken' do - before do - allow(subject).to receive(:broken?) { true } - allow(project.repository).to receive(:can_be_merged?).and_return(false) - end - - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') - end - end - - context 'when it has conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(false) - end - - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') - end - end - end - - context 'when merge_status is unchecked' do - subject { create(:merge_request, source_project: project, merge_status: :unchecked) } - - it_behaves_like 'checking if can be merged' - end - - context 'when merge_status is unchecked' do - subject { create(:merge_request, source_project: project, merge_status: :cannot_be_merged_recheck) } - - it_behaves_like 'checking if can be merged' - end - end - describe '#mergeable?' do let(:project) { create(:project) } @@ -2026,7 +1975,7 @@ describe MergeRequest do it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do allow(subject).to receive(:mergeable_state?) { true } - expect(subject).to receive(:check_if_can_be_merged) + expect(subject).to receive(:check_mergeability) expect(subject).to receive(:can_be_merged?) { true } expect(subject.mergeable?).to be_truthy @@ -2040,7 +1989,7 @@ describe MergeRequest do it 'checks if merge request can be merged' do allow(subject).to receive(:mergeable_ci_state?) { true } - expect(subject).to receive(:check_if_can_be_merged) + expect(subject).to receive(:check_mergeability) subject.mergeable? end @@ -3108,38 +3057,6 @@ describe MergeRequest do end end - describe '#mergeable_to_ref?' do - it 'returns true when merge request is mergeable' do - subject = create(:merge_request) - - expect(subject.mergeable_to_ref?).to be(true) - end - - it 'returns false when merge request is already merged' do - subject = create(:merge_request, :merged) - - expect(subject.mergeable_to_ref?).to be(false) - end - - it 'returns false when merge request is closed' do - subject = create(:merge_request, :closed) - - expect(subject.mergeable_to_ref?).to be(false) - end - - it 'returns false when merge request is work in progress' do - subject = create(:merge_request, title: 'WIP: The feature') - - expect(subject.mergeable_to_ref?).to be(false) - end - - it 'returns false when merge request has no commits' do - subject = create(:merge_request, source_branch: 'empty-branch', target_branch: 'master') - - expect(subject.mergeable_to_ref?).to be(false) - end - end - describe '#merge_participants' do it 'contains author' do expect(subject.merge_participants).to eq([subject.author]) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5c94a87529b..c164c52c975 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1546,52 +1546,65 @@ describe API::MergeRequests do end end - describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref" do - let(:pipeline) { create(:ci_pipeline_without_jobs) } + describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref" do + before do + merge_request.mark_as_unchecked! + end + + let(:merge_request_iid) { merge_request.iid } + let(:url) do - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge_to_ref" + "/projects/#{project.id}/merge_requests/#{merge_request_iid}/merge_ref" end it 'returns the generated ID from the merge service in case of success' do - put api(url, user), params: { merge_commit_message: 'Custom message' } - - commit = project.commit(json_response['commit_id']) + get api(url, user) expect(response).to have_gitlab_http_status(200) - expect(json_response['commit_id']).to be_present - expect(commit.message).to eq('Custom message') + expect(json_response['commit_id']).to eq(merge_request.merge_ref_head.sha) end it "returns 400 if branch can't be merged" do - merge_request.update!(state: 'merged') + merge_request.update!(merge_status: 'cannot_be_merged') - put api(url, user) + get api(url, user) expect(response).to have_gitlab_http_status(400) - expect(json_response['message']) - .to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}") + expect(json_response['message']).to eq('Merge request is not mergeable') end - it 'returns 403 if user has no permissions to merge to the ref' do - user2 = create(:user) - project.add_reporter(user2) + context 'when user has no access to the MR' do + let(:project) { create(:project, :private) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - put api(url, user2) + it 'returns 404' do + project.add_guest(user) - expect(response).to have_gitlab_http_status(403) - expect(json_response['message']).to eq('403 Forbidden') + get api(url, user) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Not found') + end end - it 'returns 404 for an invalid merge request IID' do - put api("/projects/#{project.id}/merge_requests/12345/merge_to_ref", user) + context 'when invalid merge request IID' do + let(:merge_request_iid) { '12345' } - expect(response).to have_gitlab_http_status(404) + it 'returns 404' do + get api(url, user) + + expect(response).to have_gitlab_http_status(404) + end end - it "returns 404 if the merge request id is used instead of iid" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) + context 'when merge request ID is used instead IID' do + let(:merge_request_iid) { merge_request.id } - expect(response).to have_gitlab_http_status(404) + it 'returns 404' do + get api(url, user) + + expect(response).to have_gitlab_http_status(404) + end end end diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 0ac23050caf..5d492e4b013 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -32,10 +32,8 @@ describe MergeRequests::MergeToRefService do expect(result[:status]).to eq(:success) expect(result[:commit_id]).to be_present - expect(result[:source_id]).to eq(merge_request.source_branch_sha) - expect(result[:target_id]).to eq(merge_request.target_branch_sha) expect(repository.ref_exists?(target_ref)).to be(true) - expect(ref_head.id).to eq(result[:commit_id]) + expect(ref_head.sha).to eq(result[:commit_id]) end end @@ -72,10 +70,6 @@ describe MergeRequests::MergeToRefService do let(:merge_request) { create(:merge_request, :simple) } let(:project) { merge_request.project } - before do - project.add_maintainer(user) - end - describe '#execute' do let(:service) do described_class.new(project, user, commit_message: 'Awesome message', @@ -92,6 +86,12 @@ describe MergeRequests::MergeToRefService do it_behaves_like 'successfully evaluates pre-condition checks' context 'commit history comparison with regular MergeService' do + before do + # The merge service needs an authorized user while merge-to-ref + # doesn't. + project.add_maintainer(user) + end + let(:merge_ref_service) do described_class.new(project, user, {}) end @@ -136,9 +136,9 @@ describe MergeRequests::MergeToRefService do let(:merge_method) { :merge } it 'returns error' do - allow(merge_request).to receive(:mergeable_to_ref?) { false } + allow(project).to receive_message_chain(:repository, :merge_to_ref) { nil } - error_message = "Merge request is not mergeable to #{merge_request.merge_ref_path}" + error_message = 'Conflicts detected during merge' result = service.execute(merge_request) @@ -170,28 +170,5 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end - - context 'when merge request is WIP state' do - it 'fails to merge' do - merge_request = create(:merge_request, title: 'WIP: The feature') - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}") - end - end - - it 'returns error when user has no authorization to admin the merge request' do - unauthorized_user = create(:user) - project.add_reporter(unauthorized_user) - - service = described_class.new(project, unauthorized_user) - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('You are not allowed to merge to this ref') - end end end diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb new file mode 100644 index 00000000000..6ad079d5f28 --- /dev/null +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::MergeabilityCheckService do + shared_examples_for 'unmergeable merge request' do + it 'updates or keeps merge status as cannot_be_merged' do + subject + + expect(merge_request.merge_status).to eq('cannot_be_merged') + end + + it 'does not change the merge ref HEAD' do + expect { subject }.not_to change(merge_request, :merge_ref_head) + end + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result).to be_error + end + end + + shared_examples_for 'mergeable merge request' do + it 'updates or keeps merge status as can_be_merged' do + subject + + expect(merge_request.merge_status).to eq('can_be_merged') + end + + it 'updates the merge ref' do + expect { subject }.to change(merge_request, :merge_ref_head).from(nil) + end + + it 'returns ServiceResponse.success' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result).to be_success + end + end + + describe '#execute' do + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, merge_status: :unchecked, source_project: project, target_project: project) } + let(:repo) { project.repository } + + subject { described_class.new(merge_request).execute } + + before do + project.add_developer(merge_request.author) + end + + it_behaves_like 'mergeable merge request' + + context 'when broken' do + before do + allow(merge_request).to receive(:broken?) { true } + allow(project.repository).to receive(:can_be_merged?) { false } + end + + it_behaves_like 'unmergeable merge request' + end + + context 'when it has conflicts' do + before do + allow(merge_request).to receive(:broken?) { false } + allow(project.repository).to receive(:can_be_merged?) { false } + end + + it_behaves_like 'unmergeable merge request' + end + + context 'when MR cannot be merged and has no merge ref' do + before do + merge_request.mark_as_unmergeable! + end + + it_behaves_like 'unmergeable merge request' + end + + context 'when MR cannot be merged and has outdated merge ref' do + before do + MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request) + merge_request.mark_as_unmergeable! + end + + it_behaves_like 'unmergeable merge request' + end + end +end -- cgit v1.2.1 From 4246a62118d919e62b94d75eba641ed374c3f241 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 31 May 2019 17:18:27 -0300 Subject: Add payload to the service response This introduces payload to the ServiceResponse with the merge ref HEAD commit data --- .../mergeability_check_service_spec.rb | 95 ++++++++++++++++++++++ spec/services/service_response_spec.rb | 16 ++++ 2 files changed, 111 insertions(+) (limited to 'spec') diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb index 6ad079d5f28..aa0485467ed 100644 --- a/spec/services/merge_requests/mergeability_check_service_spec.rb +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -39,6 +39,14 @@ describe MergeRequests::MergeabilityCheckService do expect(result).to be_a(ServiceResponse) expect(result).to be_success end + + it 'ServiceResponse has merge_ref_head payload' do + result = subject + + expect(result.payload.keys).to contain_exactly(:merge_ref_head) + expect(result.payload[:merge_ref_head].keys) + .to contain_exactly(:commit_id, :target_id, :source_id) + end end describe '#execute' do @@ -54,6 +62,21 @@ describe MergeRequests::MergeabilityCheckService do it_behaves_like 'mergeable merge request' + context 'when multiple calls to the service' do + it 'returns success' do + subject + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.success?).to be(true) + end + + it 'second call does not change the merge-ref' do + expect { subject }.to change(merge_request, :merge_ref_head).from(nil) + expect { subject }.not_to change(merge_request, :merge_ref_head) + end + end + context 'when broken' do before do allow(merge_request).to receive(:broken?) { true } @@ -61,6 +84,14 @@ describe MergeRequests::MergeabilityCheckService do end it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end end context 'when it has conflicts' do @@ -70,6 +101,14 @@ describe MergeRequests::MergeabilityCheckService do end it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end end context 'when MR cannot be merged and has no merge ref' do @@ -78,6 +117,14 @@ describe MergeRequests::MergeabilityCheckService do end it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end end context 'when MR cannot be merged and has outdated merge ref' do @@ -87,6 +134,54 @@ describe MergeRequests::MergeabilityCheckService do end it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end + end + + context 'when merge request is not given' do + subject { described_class.new(nil).execute } + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.message).to eq('Invalid argument') + end + end + + context 'when read only DB' do + it 'returns ServiceResponse.error' do + allow(Gitlab::Database).to receive(:read_only?) { true } + + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.message).to eq('Unsupported operation') + end + end + + context 'when MR is mergeable but merge-ref does not exists' do + before do + merge_request.mark_as_mergeable! + end + + it 'keeps merge status as can_be_merged' do + expect { subject }.not_to change(merge_request, :merge_status).from('can_be_merged') + end + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge ref was not found') + end end end end diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb index 30bd4d6820b..e790d272e61 100644 --- a/spec/services/service_response_spec.rb +++ b/spec/services/service_response_spec.rb @@ -16,6 +16,13 @@ describe ServiceResponse do expect(response).to be_success expect(response.message).to eq('Good orange') end + + it 'creates a successful response with payload' do + response = described_class.success(payload: { good: 'orange' }) + + expect(response).to be_success + expect(response.payload).to eq(good: 'orange') + end end describe '.error' do @@ -33,6 +40,15 @@ describe ServiceResponse do expect(response.message).to eq('Bad apple') expect(response.http_status).to eq(400) end + + it 'creates a failed response with payload' do + response = described_class.error(message: 'Bad apple', + payload: { bad: 'apple' }) + + expect(response).to be_error + expect(response.message).to eq('Bad apple') + expect(response.payload).to eq(bad: 'apple') + end end describe '#success?' do -- cgit v1.2.1 From cc8e7ea2056ae8132ea9eb0565c8e0a07b1077a6 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 30 May 2019 13:42:06 -0500 Subject: Remove unused avatar sizes - Update avatars to use only sizes from the 8px grid --- spec/javascripts/diffs/components/commit_item_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js index 8fc9b10dd0b..cfe0c4bad71 100644 --- a/spec/javascripts/diffs/components/commit_item_spec.js +++ b/spec/javascripts/diffs/components/commit_item_spec.js @@ -8,7 +8,7 @@ import getDiffWithCommit from '../mock_data/diff_with_commit'; const TEST_AUTHOR_NAME = 'test'; const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com'; -const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`; +const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=40`; const TEST_SIGNATURE_HTML = 'Legit commit'; const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`; @@ -65,7 +65,7 @@ describe('diffs/components/commit_item', () => { const imgElement = avatarElement.querySelector('img'); expect(avatarElement).toHaveAttr('href', commit.author.web_url); - expect(imgElement).toHaveClass('s36'); + expect(imgElement).toHaveClass('s40'); expect(imgElement).toHaveAttr('alt', commit.author.name); expect(imgElement).toHaveAttr('src', commit.author.avatar_url); }); -- cgit v1.2.1 From 7eafe90b90b41e1111bb64d4cc8d8004287da3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Sat, 1 Jun 2019 12:37:29 +0200 Subject: Fix port validations in .gitlab-webide.yml --- spec/lib/gitlab/ci/config/entry/service_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index d5bd139b5f1..d31866a1987 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -112,6 +112,16 @@ describe Gitlab::Ci::Config::Entry::Service do it 'is valid' do expect(entry).to be_valid end + + context 'when unknown port keys detected' do + let(:ports) { [{ number: 80, invalid_key: 'foo' }] } + + it 'is not valid' do + expect(entry).not_to be_valid + expect(entry.errors.first) + .to match /port config contains unknown keys: invalid_key/ + end + end end describe '#ports' do -- cgit v1.2.1 From a52cbf6b00e65a52feb1ab0ff25c75858d39184a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 1 Jun 2019 22:59:17 -0700 Subject: Fix migration failure when groups are missing route When a Group is missing a route, the migration failed previously with a `Invalid single-table inheritance type` error. To fix this, we can disable STI for the migration class because we don't need to know about the type to do this migration. Besides, currently Group is the only type used in the type column. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/58714 --- spec/migrations/generate_missing_routes_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/migrations/generate_missing_routes_spec.rb b/spec/migrations/generate_missing_routes_spec.rb index 32515d353b0..30ad135d4df 100644 --- a/spec/migrations/generate_missing_routes_spec.rb +++ b/spec/migrations/generate_missing_routes_spec.rb @@ -8,7 +8,7 @@ describe GenerateMissingRoutes, :migration do let(:routes) { table(:routes) } it 'creates routes for projects without a route' do - namespace = namespaces.create!(name: 'GitLab', path: 'gitlab') + namespace = namespaces.create!(name: 'GitLab', path: 'gitlab', type: 'Group') routes.create!( path: 'gitlab', -- cgit v1.2.1 From 6a18a411a30e9e7406ba9335ab502ec396add662 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 17 May 2019 19:10:44 +0700 Subject: Make pipeline schedule worker resilient Currently, pipeline schedule worker is unstable because it's sometimes killed by excessive memory consumption. In order to improve the performance, we add the following fixes: 1. next_run_at is always real_next_run, which means the value always takes into account of worker's cron schedule 1. Remove exlusive lock. This is already covered by real_next_run change. 1. Use RunPipelineScheduleWorker for avoiding memory killer. Memory consumption is spread to the multiple sidekiq worker. --- spec/factories/ci/pipeline_schedule.rb | 10 ++ spec/features/projects/pipeline_schedules_spec.rb | 2 +- spec/models/ci/pipeline_schedule_spec.rb | 145 ++++++++++++++------- spec/services/ci/pipeline_schedule_service_spec.rb | 28 ++++ spec/workers/pipeline_schedule_worker_spec.rb | 57 +------- spec/workers/run_pipeline_schedule_worker_spec.rb | 32 ++++- 6 files changed, 171 insertions(+), 103 deletions(-) create mode 100644 spec/services/ci/pipeline_schedule_service_spec.rb (limited to 'spec') diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb index b2b79807429..4b83ba2ac1b 100644 --- a/spec/factories/ci/pipeline_schedule.rb +++ b/spec/factories/ci/pipeline_schedule.rb @@ -7,6 +7,16 @@ FactoryBot.define do description "pipeline schedule" project + trait :every_minute do + cron '*/1 * * * *' + cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + end + + trait :hourly do + cron '* */1 * * *' + cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + end + trait :nightly do cron '0 1 * * *' cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index b1a705f09ce..24041a51383 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -225,7 +225,7 @@ describe 'Pipeline Schedules', :js do context 'when active is true and next_run_at is NULL' do before do create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| - pipeline_schedule.update_attribute(:cron, nil) # Consequently next_run_at will be nil + pipeline_schedule.update_attribute(:next_run_at, nil) # Consequently next_run_at will be nil end end diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 42d4769a921..6382be73ea7 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -48,32 +48,116 @@ describe Ci::PipelineSchedule do end end + describe '.runnable_schedules' do + subject { described_class.runnable_schedules } + + let!(:pipeline_schedule) do + Timecop.freeze(1.day.ago) do + create(:ci_pipeline_schedule, :hourly) + end + end + + it 'returns the runnable schedule' do + is_expected.to eq([pipeline_schedule]) + end + + context 'when there are no runnable schedules' do + let!(:pipeline_schedule) { } + + it 'returns an empty array' do + is_expected.to be_empty + end + end + end + + describe '.preloaded' do + subject { described_class.preloaded } + + before do + create_list(:ci_pipeline_schedule, 3) + end + + it 'preloads the associations' do + subject + + query = ActiveRecord::QueryRecorder.new { subject.each(&:project) } + + expect(query.count).to eq(2) + end + end + describe '#set_next_run_at' do - let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) } + let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) } + let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) } + + let(:expected_next_run_at) do + Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) + .next_time_from(ideal_next_run_at) + end + + let(:cron_worker_next_run_at) do + Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) + .next_time_from(Time.now) + end context 'when creates new pipeline schedule' do - let(:expected_next_run_at) do - Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone) - .next_time_from(Time.now) + it 'updates next_run_at automatically' do + expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at) end + end - it 'updates next_run_at automatically' do - expect(described_class.last.next_run_at).to eq(expected_next_run_at) + context 'when PipelineScheduleWorker runs at a specific interval' do + before do + allow(Settings).to receive(:cron_jobs) do + { + 'pipeline_schedule_worker' => { + 'cron' => '0 1 2 3 *' + } + } + end + end + + it "updates next_run_at to the sidekiq worker's execution time" do + expect(pipeline_schedule.next_run_at.min).to eq(0) + expect(pipeline_schedule.next_run_at.hour).to eq(1) + expect(pipeline_schedule.next_run_at.day).to eq(2) + expect(pipeline_schedule.next_run_at.month).to eq(3) end end - context 'when updates cron of exsisted pipeline schedule' do - let(:new_cron) { '0 0 1 1 *' } + context 'when pipeline schedule runs every minute' do + let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) } - let(:expected_next_run_at) do - Gitlab::Ci::CronParser.new(new_cron, pipeline_schedule.cron_timezone) - .next_time_from(Time.now) + it "updates next_run_at to the sidekiq worker's execution time" do + expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at) + end + end + + context 'when there are two different pipeline schedules in different time zones' do + let(:pipeline_schedule_1) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'Eastern Time (US & Canada)') } + let(:pipeline_schedule_2) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') } + + it 'sets different next_run_at' do + expect(pipeline_schedule_1.next_run_at).not_to eq(pipeline_schedule_2.next_run_at) + end + end + + context 'when there are two different pipeline schedules in the same time zones' do + let(:pipeline_schedule_1) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') } + let(:pipeline_schedule_2) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') } + + it 'sets the sames next_run_at' do + expect(pipeline_schedule_1.next_run_at).to eq(pipeline_schedule_2.next_run_at) end + end + + context 'when updates cron of exsisted pipeline schedule' do + let(:new_cron) { '0 0 1 1 *' } it 'updates next_run_at automatically' do pipeline_schedule.update!(cron: new_cron) - expect(described_class.last.next_run_at).to eq(expected_next_run_at) + expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at) end end end @@ -83,10 +167,11 @@ describe Ci::PipelineSchedule do context 'when reschedules after 10 days from now' do let(:future_time) { 10.days.from_now } + let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) } let(:expected_next_run_at) do - Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone) - .next_time_from(future_time) + Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) + .next_time_from(ideal_next_run_at) end it 'points to proper next_run_at' do @@ -99,38 +184,6 @@ describe Ci::PipelineSchedule do end end - describe '#real_next_run' do - subject do - described_class.last.real_next_run(worker_cron: worker_cron, - worker_time_zone: worker_time_zone) - end - - context 'when GitLab time_zone is UTC' do - before do - allow(Time).to receive(:zone) - .and_return(ActiveSupport::TimeZone[worker_time_zone]) - end - - let(:worker_time_zone) { 'UTC' } - - context 'when cron_timezone is Eastern Time (US & Canada)' do - before do - create(:ci_pipeline_schedule, :nightly, - cron_timezone: 'Eastern Time (US & Canada)') - end - - let(:worker_cron) { '0 1 2 3 *' } - - it 'returns the next time worker executes' do - expect(subject.min).to eq(0) - expect(subject.hour).to eq(1) - expect(subject.day).to eq(2) - expect(subject.month).to eq(3) - end - end - end - end - describe '#job_variables' do let!(:pipeline_schedule) { create(:ci_pipeline_schedule) } diff --git a/spec/services/ci/pipeline_schedule_service_spec.rb b/spec/services/ci/pipeline_schedule_service_spec.rb new file mode 100644 index 00000000000..f2ac53cb25a --- /dev/null +++ b/spec/services/ci/pipeline_schedule_service_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::PipelineScheduleService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { described_class.new(project, user) } + + describe '#execute' do + subject { service.execute(schedule) } + + let(:schedule) { create(:ci_pipeline_schedule, project: project, owner: user) } + + it 'schedules next run' do + expect(schedule).to receive(:schedule_next_run!) + + subject + end + + it 'runs RunPipelineScheduleWorker' do + expect(RunPipelineScheduleWorker) + .to receive(:perform_async).with(schedule.id, schedule.owner.id) + + subject + end + end +end diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index 8c604b13297..9326db34209 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -41,16 +41,6 @@ describe PipelineScheduleWorker do it_behaves_like 'successful scheduling' - context 'when exclusive lease has already been taken by the other instance' do - before do - stub_exclusive_lease_taken(described_class::EXCLUSIVE_LOCK_KEY, timeout: described_class::LOCK_TIMEOUT) - end - - it 'raises an error and does not start creating pipelines' do - expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError) - end - end - context 'when the latest commit contains [ci skip]' do before do allow_any_instance_of(Ci::Pipeline) @@ -77,47 +67,19 @@ describe PipelineScheduleWorker do stub_ci_pipeline_yaml_file(YAML.dump(rspec: { variables: 'rspec' } )) end - it 'creates a failed pipeline with the reason' do - expect { subject }.to change { project.ci_pipelines.count }.by(1) - expect(Ci::Pipeline.last).to be_config_error - expect(Ci::Pipeline.last.yaml_errors).not_to be_nil + it 'does not creates a new pipeline' do + expect { subject }.not_to change { project.ci_pipelines.count } end end end context 'when the schedule is not runnable by the user' do - before do - expect(Gitlab::Sentry) - .to receive(:track_exception) - .with(Ci::CreatePipelineService::CreateError, - issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231', - extra: { schedule_id: pipeline_schedule.id } ).once - end - it 'does not deactivate the schedule' do subject expect(pipeline_schedule.reload.active).to be_truthy end - it 'increments Prometheus counter' do - expect(Gitlab::Metrics) - .to receive(:counter) - .with(:pipeline_schedule_creation_failed_total, "Counter of failed attempts of pipeline schedule creation") - .and_call_original - - subject - end - - it 'logging a pipeline error' do - expect(Rails.logger) - .to receive(:error) - .with(a_string_matching("Insufficient permissions to create a new pipeline")) - .and_call_original - - subject - end - it 'does not create a pipeline' do expect { subject }.not_to change { project.ci_pipelines.count } end @@ -131,21 +93,6 @@ describe PipelineScheduleWorker do before do stub_ci_pipeline_yaml_file(nil) project.add_maintainer(user) - - expect(Gitlab::Sentry) - .to receive(:track_exception) - .with(Ci::CreatePipelineService::CreateError, - issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231', - extra: { schedule_id: pipeline_schedule.id } ).once - end - - it 'logging a pipeline error' do - expect(Rails.logger) - .to receive(:error) - .with(a_string_matching("Missing .gitlab-ci.yml file")) - .and_call_original - - subject end it 'does not create a pipeline' do diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb index 690af22f4dc..7414470f8e7 100644 --- a/spec/workers/run_pipeline_schedule_worker_spec.rb +++ b/spec/workers/run_pipeline_schedule_worker_spec.rb @@ -32,7 +32,37 @@ describe RunPipelineScheduleWorker do it 'calls the Service' do expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service) - expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule) + expect(create_pipeline_service).to receive(:execute!).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule) + + worker.perform(pipeline_schedule.id, user.id) + end + end + + context 'when database statement timeout happens' do + before do + allow(Ci::CreatePipelineService).to receive(:new) { raise ActiveRecord::StatementInvalid } + + expect(Gitlab::Sentry) + .to receive(:track_exception) + .with(ActiveRecord::StatementInvalid, + issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231', + extra: { schedule_id: pipeline_schedule.id } ).once + end + + it 'increments Prometheus counter' do + expect(Gitlab::Metrics) + .to receive(:counter) + .with(:pipeline_schedule_creation_failed_total, "Counter of failed attempts of pipeline schedule creation") + .and_call_original + + worker.perform(pipeline_schedule.id, user.id) + end + + it 'logging a pipeline error' do + expect(Rails.logger) + .to receive(:error) + .with(a_string_matching('ActiveRecord::StatementInvalid')) + .and_call_original worker.perform(pipeline_schedule.id, user.id) end -- cgit v1.2.1 From d4b46936633a3b2a0248b4572b4a1dc7b2ba8531 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 22 May 2019 18:45:27 +0700 Subject: Abstract auto merge processes We have one auto merge strategy today - Merge When Pipeline Succeeds. In order to add more strategies for Merge Train feature, we abstract the architecture to be more extensible. Removed arguments Fix spec --- .../projects/merge_requests_controller_spec.rb | 18 +- spec/factories/merge_requests.rb | 3 +- .../user_merges_when_pipeline_succeeds_spec.rb | 15 +- .../merge_request/user_sees_merge_widget_spec.rb | 3 +- .../vue_mr_widget/stores/get_state_key_spec.js | 2 +- .../states/mr_widget_ready_to_merge_spec.js | 14 +- spec/models/merge_request_spec.rb | 18 +- spec/presenters/merge_request_presenter_spec.rb | 10 +- spec/requests/api/merge_requests_spec.rb | 6 +- .../merge_request_widget_entity_spec.rb | 46 +++++ .../merge_when_pipeline_succeeds_service_spec.rb | 223 +++++++++++++++++++++ spec/services/auto_merge_service_spec.rb | 140 +++++++++++++ .../merge_when_pipeline_succeeds_service_spec.rb | 197 ------------------ .../push_options_handler_service_spec.rb | 5 +- .../merge_requests/refresh_service_spec.rb | 10 +- .../services/merge_requests/update_service_spec.rb | 3 +- spec/workers/pipeline_success_worker_spec.rb | 9 +- 17 files changed, 479 insertions(+), 243 deletions(-) create mode 100644 spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb create mode 100644 spec/services/auto_merge_service_spec.rb delete mode 100644 spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb (limited to 'spec') diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index f4a18dcba51..f8c0ab55eb4 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -429,8 +429,9 @@ describe Projects::MergeRequestsController do it 'sets the MR to merge when the pipeline succeeds' do service = double(:merge_when_pipeline_succeeds_service) + allow(service).to receive(:available_for?) { true } - expect(MergeRequests::MergeWhenPipelineSucceedsService) + expect(AutoMerge::MergeWhenPipelineSucceedsService) .to receive(:new).with(project, anything, anything) .and_return(service) expect(service).to receive(:execute).with(merge_request) @@ -713,9 +714,9 @@ describe Projects::MergeRequestsController do end end - describe 'POST cancel_merge_when_pipeline_succeeds' do + describe 'POST cancel_auto_merge' do subject do - post :cancel_merge_when_pipeline_succeeds, + post :cancel_auto_merge, params: { format: :json, namespace_id: merge_request.project.namespace.to_param, @@ -725,14 +726,15 @@ describe Projects::MergeRequestsController do xhr: true end - it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do - mwps_service = double + it 'calls AutoMergeService' do + auto_merge_service = double - allow(MergeRequests::MergeWhenPipelineSucceedsService) + allow(AutoMergeService) .to receive(:new) - .and_return(mwps_service) + .and_return(auto_merge_service) - expect(mwps_service).to receive(:cancel).with(merge_request) + allow(auto_merge_service).to receive(:available_strategies).with(merge_request) + expect(auto_merge_service).to receive(:cancel).with(merge_request) subject end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index e8df5094b83..0b6a43b13a9 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -95,7 +95,8 @@ FactoryBot.define do end trait :merge_when_pipeline_succeeds do - merge_when_pipeline_succeeds true + auto_merge_enabled true + auto_merge_strategy AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS merge_user { author } end diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index d4ad11b3585..e7b92dc5535 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -74,11 +74,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do source_project: project, title: 'Bug NS-04', author: user, - merge_user: user, - merge_params: { force_remove_source_branch: '1' }) + merge_user: user) end before do + merge_request.merge_params['force_remove_source_branch'] = '0' + merge_request.save! click_link "Cancel automatic merge" end @@ -102,11 +103,11 @@ describe 'Merge request > User merges when pipeline succeeds', :js do context 'when merge when pipeline succeeds is enabled' do let(:merge_request) do - create(:merge_request_with_diffs, :simple, source_project: project, - author: user, - merge_user: user, - title: 'MepMep', - merge_when_pipeline_succeeds: true) + create(:merge_request_with_diffs, :simple, :merge_when_pipeline_succeeds, + source_project: project, + author: user, + merge_user: user, + title: 'MepMep') end let!(:build) do create(:ci_build, pipeline: pipeline) 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 93ddde623fe..1477307ed7b 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -314,7 +314,8 @@ describe 'Merge request > User sees merge widget', :js do context 'view merge request with MWPS enabled but automatically merge fails' do before do merge_request.update( - merge_when_pipeline_succeeds: true, + auto_merge_enabled: true, + auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, merge_user: merge_request.author, merge_error: 'Something went wrong' ) diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js index b356ea85cad..9797549498b 100644 --- a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js +++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js @@ -31,7 +31,7 @@ describe('getStateKey', () => { expect(bound()).toEqual('notAllowedToMerge'); - context.mergeWhenPipelineSucceeds = true; + context.autoMergeEnabled = true; expect(bound()).toEqual('mergeWhenPipelineSucceeds'); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 368c997d318..daca51c6156 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -80,7 +80,7 @@ describe('ReadyToMerge', () => { it('should have default data', () => { expect(vm.mergeWhenBuildSucceeds).toBeFalsy(); expect(vm.useCommitMessageWithDescription).toBeFalsy(); - expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy(); + expect(vm.setToAutoMerge).toBeFalsy(); expect(vm.showCommitMessageEditor).toBeFalsy(); expect(vm.isMakingRequest).toBeFalsy(); expect(vm.isMergingImmediately).toBeFalsy(); @@ -91,17 +91,17 @@ describe('ReadyToMerge', () => { }); describe('computed', () => { - describe('shouldShowMergeWhenPipelineSucceedsText', () => { + describe('shouldShowAutoMergeText', () => { it('should return true with active pipeline', () => { vm.mr.isPipelineActive = true; - expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeTruthy(); + expect(vm.shouldShowAutoMergeText).toBeTruthy(); }); it('should return false with inactive pipeline', () => { vm.mr.isPipelineActive = false; - expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeFalsy(); + expect(vm.shouldShowAutoMergeText).toBeFalsy(); }); }); @@ -325,7 +325,7 @@ describe('ReadyToMerge', () => { vm.handleMergeButtonClick(true); setTimeout(() => { - expect(vm.setToMergeWhenPipelineSucceeds).toBeTruthy(); + expect(vm.setToAutoMerge).toBeTruthy(); expect(vm.isMakingRequest).toBeTruthy(); expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); @@ -345,7 +345,7 @@ describe('ReadyToMerge', () => { vm.handleMergeButtonClick(false, true); setTimeout(() => { - expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy(); + expect(vm.setToAutoMerge).toBeFalsy(); expect(vm.isMakingRequest).toBeTruthy(); expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); @@ -363,7 +363,7 @@ describe('ReadyToMerge', () => { vm.handleMergeButtonClick(); setTimeout(() => { - expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy(); + expect(vm.setToAutoMerge).toBeFalsy(); expect(vm.isMakingRequest).toBeTruthy(); expect(vm.initiateMergePolling).toHaveBeenCalled(); diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c72b6e9033d..43c61c48fe5 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1038,14 +1038,28 @@ describe MergeRequest do end end - describe "#reset_merge_when_pipeline_succeeds" do + describe "#auto_merge_strategy" do + subject { merge_request.auto_merge_strategy } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it { is_expected.to eq('merge_when_pipeline_succeeds') } + + context 'when auto merge is disabled' do + let(:merge_request) { create(:merge_request) } + + it { is_expected.to be_nil } + end + end + + describe "#reset_auto_merge" do let(:merge_if_green) do create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user), merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" } end it "sets the item to false" do - merge_if_green.reset_merge_when_pipeline_succeeds + merge_if_green.reset_auto_merge merge_if_green.reload expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 0e1aed42cc5..6408b0bd748 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -207,25 +207,25 @@ describe MergeRequestPresenter do end end - describe '#cancel_merge_when_pipeline_succeeds_path' do + describe '#cancel_auto_merge_path' do subject do described_class.new(resource, current_user: user) - .cancel_merge_when_pipeline_succeeds_path + .cancel_auto_merge_path end context 'when can cancel mwps' do it 'returns path' do - allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?) + allow(resource).to receive(:can_cancel_auto_merge?) .with(user) .and_return(true) - is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_merge_when_pipeline_succeeds") + is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_auto_merge") end end context 'when cannot cancel mwps' do it 'returns nil' do - allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?) + allow(resource).to receive(:can_cancel_auto_merge?) .with(user) .and_return(false) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5c94a87529b..9f9180bc8c9 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1473,7 +1473,7 @@ describe API::MergeRequests do end it "enables merge when pipeline succeeds if the pipeline is active" do - allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline) allow(pipeline).to receive(:active?).and_return(true) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true } @@ -1484,7 +1484,7 @@ describe API::MergeRequests do end it "enables merge when pipeline succeeds if the pipeline is active and only_allow_merge_if_pipeline_succeeds is true" do - allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline) allow(pipeline).to receive(:active?).and_return(true) project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true) @@ -1950,7 +1950,7 @@ describe API::MergeRequests do describe 'POST :id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do before do - ::MergeRequests::MergeWhenPipelineSucceedsService.new(merge_request.target_project, user).execute(merge_request) + ::AutoMergeService.new(merge_request.target_project, user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS) end it 'removes the merge_when_pipeline_succeeds status' do diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index b89898f26f7..a27c22191f4 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -297,4 +297,50 @@ describe MergeRequestWidgetEntity do end end end + + describe 'auto merge' do + context 'when auto merge is enabled' do + let(:resource) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it 'returns auto merge related information' do + expect(subject[:auto_merge_enabled]).to be_truthy + expect(subject[:auto_merge_strategy]).to eq('merge_when_pipeline_succeeds') + end + end + + context 'when auto merge is not enabled' do + let(:resource) { create(:merge_request) } + + it 'returns auto merge related information' do + expect(subject[:auto_merge_enabled]).to be_falsy + expect(subject[:auto_merge_strategy]).to be_nil + end + end + + context 'when head pipeline is running' do + before do + create(:ci_pipeline, :running, project: project, + ref: resource.source_branch, + sha: resource.diff_head_sha) + resource.update_head_pipeline + end + + it 'returns available auto merge strategies' do + expect(subject[:available_auto_merge_strategies]).to eq(%w[merge_when_pipeline_succeeds]) + end + end + + context 'when head pipeline is finished' do + before do + create(:ci_pipeline, :success, project: project, + ref: resource.source_branch, + sha: resource.diff_head_sha) + resource.update_head_pipeline + end + + it 'returns available auto merge strategies' do + expect(subject[:available_auto_merge_strategies]).to be_empty + end + end + end end diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb new file mode 100644 index 00000000000..a20bf8e17e4 --- /dev/null +++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb @@ -0,0 +1,223 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AutoMerge::MergeWhenPipelineSucceedsService do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + let(:mr_merge_if_green_enabled) do + create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, + source_branch: "master", target_branch: 'feature', + source_project: project, target_project: project, state: "opened") + end + + let(:pipeline) do + create(:ci_pipeline_with_one_job, ref: mr_merge_if_green_enabled.source_branch, + project: project) + end + + let(:service) do + described_class.new(project, user, commit_message: 'Awesome message') + end + + describe "#available_for?" do + subject { service.available_for?(mr_merge_if_green_enabled) } + + let(:pipeline_status) { :running } + + before do + create(:ci_pipeline, pipeline_status, ref: mr_merge_if_green_enabled.source_branch, + sha: mr_merge_if_green_enabled.diff_head_sha, + project: mr_merge_if_green_enabled.source_project) + mr_merge_if_green_enabled.update_head_pipeline + end + + it { is_expected.to be_truthy } + + context 'when the head piipeline succeeded' do + let(:pipeline_status) { :success } + + it { is_expected.to be_falsy } + end + end + + describe "#execute" do + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project, + source_branch: "feature", target_branch: 'master') + end + + context 'first time enabling' do + before do + allow(merge_request) + .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline) + + service.execute(merge_request) + end + + it 'sets the params, merge_user, and flag' do + expect(merge_request).to be_valid + expect(merge_request.merge_when_pipeline_succeeds).to be_truthy + expect(merge_request.merge_params).to include commit_message: 'Awesome message' + expect(merge_request.merge_user).to be user + end + + it 'creates a system note' do + note = merge_request.notes.last + expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}} + end + end + + context 'already approved' do + let(:service) { described_class.new(project, user, new_key: true) } + let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } + + before do + allow(mr_merge_if_green_enabled) + .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline) + + allow(mr_merge_if_green_enabled).to receive(:mergeable?) + .and_return(true) + + allow(pipeline).to receive(:success?).and_return(true) + end + + it 'updates the merge params' do + expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds) + + service.execute(mr_merge_if_green_enabled) + expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) + end + end + end + + describe "#process" do + let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } + let(:merge_request_head) do + project.commit(mr_merge_if_green_enabled.source_branch).id + end + + context 'when triggered by pipeline with valid ref and sha' do + let(:triggering_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: merge_request_head, status: 'success', + head_pipeline_of: mr_merge_if_green_enabled) + end + + it "merges all merge requests with merge when the pipeline succeeds enabled" do + allow(mr_merge_if_green_enabled) + .to receive_messages(head_pipeline: triggering_pipeline, actual_head_pipeline: triggering_pipeline) + + expect(MergeWorker).to receive(:perform_async) + service.process(mr_merge_if_green_enabled) + end + end + + context 'when triggered by an old pipeline' do + let(:old_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: '1234abcdef', status: 'success') + end + + it 'does not merge request' do + expect(MergeWorker).not_to receive(:perform_async) + service.process(mr_merge_if_green_enabled) + end + end + + context 'when triggered by pipeline from a different branch' do + let(:unrelated_pipeline) do + create(:ci_pipeline, project: project, ref: 'feature', + sha: merge_request_head, status: 'success') + end + + it 'does not merge request' do + expect(MergeWorker).not_to receive(:perform_async) + service.process(mr_merge_if_green_enabled) + end + end + + context 'when pipeline is merge request pipeline' do + let(:pipeline) do + create(:ci_pipeline, :success, + source: :merge_request_event, + ref: mr_merge_if_green_enabled.merge_ref_path, + merge_request: mr_merge_if_green_enabled, + merge_requests_as_head_pipeline: [mr_merge_if_green_enabled]) + end + + it 'merges the associated merge request' do + allow(mr_merge_if_green_enabled) + .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline) + + expect(MergeWorker).to receive(:perform_async) + service.process(mr_merge_if_green_enabled) + end + end + end + + describe "#cancel" do + before do + service.cancel(mr_merge_if_green_enabled) + end + + it "resets all the pipeline succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_pipeline_succeeds).to be_falsey + expect(mr_merge_if_green_enabled.merge_params).to eq({}) + expect(mr_merge_if_green_enabled.merge_user).to be nil + end + + it 'Posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'canceled the automatic merge' + end + end + + describe 'pipeline integration' do + context 'when there are multiple stages in the pipeline' do + let(:ref) { mr_merge_if_green_enabled.source_branch } + let(:sha) { project.commit(ref).id } + + let(:pipeline) do + create(:ci_empty_pipeline, ref: ref, sha: sha, project: project) + end + + let!(:build) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'build', stage: 'build') + end + + let!(:test) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'test', stage: 'test') + end + + before do + # This behavior of MergeRequest: we instantiate a new object + # + allow_any_instance_of(MergeRequest) + .to receive(:head_pipeline) + .and_wrap_original do + Ci::Pipeline.find(pipeline.id) + end + end + + it "doesn't merge if any of stages failed" do + expect(MergeWorker).not_to receive(:perform_async) + + build.success + test.reload + test.drop + end + + it 'merges when all stages succeeded' do + expect(MergeWorker).to receive(:perform_async) + + build.success + test.reload + test.success + end + end + end +end diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb new file mode 100644 index 00000000000..d0eefed3150 --- /dev/null +++ b/spec/services/auto_merge_service_spec.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AutoMergeService do + set(:project) { create(:project) } + set(:user) { create(:user) } + let(:service) { described_class.new(project, user) } + + describe '.all_strategies' do + subject { described_class.all_strategies } + + it 'returns all strategies' do + is_expected.to eq(AutoMergeService::STRATEGIES) + end + end + + describe '#available_strategies' do + subject { service.available_strategies(merge_request) } + + let(:merge_request) { create(:merge_request) } + let(:pipeline_status) { :running } + + before do + create(:ci_pipeline, pipeline_status, ref: merge_request.source_branch, + sha: merge_request.diff_head_sha, + project: merge_request.source_project) + + merge_request.update_head_pipeline + end + + it 'returns available strategies' do + is_expected.to include('merge_when_pipeline_succeeds') + end + + context 'when the head piipeline succeeded' do + let(:pipeline_status) { :success } + + it 'returns available strategies' do + is_expected.to be_empty + end + end + end + + describe '.get_service_class' do + subject { described_class.get_service_class(strategy) } + + let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS } + + it 'returns service instance' do + is_expected.to eq(AutoMerge::MergeWhenPipelineSucceedsService) + end + + context 'when strategy is not present' do + let(:strategy) { } + + it 'returns nil' do + is_expected.to be_nil + end + end + end + + describe '#execute' do + subject { service.execute(merge_request, strategy) } + + let(:merge_request) { create(:merge_request) } + let(:pipeline_status) { :running } + let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS } + + before do + create(:ci_pipeline, pipeline_status, ref: merge_request.source_branch, + sha: merge_request.diff_head_sha, + project: merge_request.source_project) + + merge_request.update_head_pipeline + end + + it 'delegates to a relevant service instance' do + expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service| + expect(service).to receive(:execute).with(merge_request) + end + + subject + end + + context 'when the head piipeline succeeded' do + let(:pipeline_status) { :success } + + it 'returns failed' do + is_expected.to eq(:failed) + end + end + end + + describe '#process' do + subject { service.process(merge_request) } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it 'delegates to a relevant service instance' do + expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service| + expect(service).to receive(:process).with(merge_request) + end + + subject + end + + context 'when auto merge is not enabled' do + let(:merge_request) { create(:merge_request) } + + it 'returns nil' do + is_expected.to be_nil + end + end + end + + describe '#cancel' do + subject { service.cancel(merge_request) } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it 'delegates to a relevant service instance' do + expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service| + expect(service).to receive(:cancel).with(merge_request) + end + + subject + end + + context 'when auto merge is not enabled' do + let(:merge_request) { create(:merge_request) } + + it 'returns error' do + expect(subject[:message]).to eq("Can't cancel the automatic merge") + expect(subject[:status]).to eq(:error) + expect(subject[:http_status]).to eq(406) + end + end + end +end diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb deleted file mode 100644 index 96f61f3f103..00000000000 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ /dev/null @@ -1,197 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe MergeRequests::MergeWhenPipelineSucceedsService do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - - let(:mr_merge_if_green_enabled) do - create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, - source_branch: "master", target_branch: 'feature', - source_project: project, target_project: project, state: "opened") - end - - let(:pipeline) do - create(:ci_pipeline_with_one_job, ref: mr_merge_if_green_enabled.source_branch, - project: project) - end - - let(:service) do - described_class.new(project, user, commit_message: 'Awesome message') - end - - describe "#execute" do - let(:merge_request) do - create(:merge_request, target_project: project, source_project: project, - source_branch: "feature", target_branch: 'master') - end - - context 'first time enabling' do - before do - allow(merge_request) - .to receive(:head_pipeline) - .and_return(pipeline) - - service.execute(merge_request) - end - - it 'sets the params, merge_user, and flag' do - expect(merge_request).to be_valid - expect(merge_request.merge_when_pipeline_succeeds).to be_truthy - expect(merge_request.merge_params).to eq commit_message: 'Awesome message' - expect(merge_request.merge_user).to be user - end - - it 'creates a system note' do - note = merge_request.notes.last - expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}} - end - end - - context 'already approved' do - let(:service) { described_class.new(project, user, new_key: true) } - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } - - before do - allow(mr_merge_if_green_enabled).to receive(:head_pipeline) - .and_return(pipeline) - - allow(mr_merge_if_green_enabled).to receive(:mergeable?) - .and_return(true) - - allow(pipeline).to receive(:success?).and_return(true) - end - - it 'updates the merge params' do - expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds) - - service.execute(mr_merge_if_green_enabled) - expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) - end - end - end - - describe "#trigger" do - let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } - let(:merge_request_head) do - project.commit(mr_merge_if_green_enabled.source_branch).id - end - - context 'when triggered by pipeline with valid ref and sha' do - let(:triggering_pipeline) do - create(:ci_pipeline, project: project, ref: merge_request_ref, - sha: merge_request_head, status: 'success', - head_pipeline_of: mr_merge_if_green_enabled) - end - - it "merges all merge requests with merge when the pipeline succeeds enabled" do - expect(MergeWorker).to receive(:perform_async) - service.trigger(triggering_pipeline) - end - end - - context 'when triggered by an old pipeline' do - let(:old_pipeline) do - create(:ci_pipeline, project: project, ref: merge_request_ref, - sha: '1234abcdef', status: 'success') - end - - it 'does not merge request' do - expect(MergeWorker).not_to receive(:perform_async) - service.trigger(old_pipeline) - end - end - - context 'when triggered by pipeline from a different branch' do - let(:unrelated_pipeline) do - create(:ci_pipeline, project: project, ref: 'feature', - sha: merge_request_head, status: 'success') - end - - it 'does not merge request' do - expect(MergeWorker).not_to receive(:perform_async) - service.trigger(unrelated_pipeline) - end - end - - context 'when pipeline is merge request pipeline' do - let(:pipeline) do - create(:ci_pipeline, :success, - source: :merge_request_event, - ref: mr_merge_if_green_enabled.merge_ref_path, - merge_request: mr_merge_if_green_enabled, - merge_requests_as_head_pipeline: [mr_merge_if_green_enabled]) - end - - it 'merges the associated merge request' do - expect(MergeWorker).to receive(:perform_async) - service.trigger(pipeline) - end - end - end - - describe "#cancel" do - before do - service.cancel(mr_merge_if_green_enabled) - end - - it "resets all the pipeline succeeds params" do - expect(mr_merge_if_green_enabled.merge_when_pipeline_succeeds).to be_falsey - expect(mr_merge_if_green_enabled.merge_params).to eq({}) - expect(mr_merge_if_green_enabled.merge_user).to be nil - end - - it 'Posts a system note' do - note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'canceled the automatic merge' - end - end - - describe 'pipeline integration' do - context 'when there are multiple stages in the pipeline' do - let(:ref) { mr_merge_if_green_enabled.source_branch } - let(:sha) { project.commit(ref).id } - - let(:pipeline) do - create(:ci_empty_pipeline, ref: ref, sha: sha, project: project) - end - - let!(:build) do - create(:ci_build, :created, pipeline: pipeline, ref: ref, - name: 'build', stage: 'build') - end - - let!(:test) do - create(:ci_build, :created, pipeline: pipeline, ref: ref, - name: 'test', stage: 'test') - end - - before do - # This behavior of MergeRequest: we instantiate a new object - # - allow_any_instance_of(MergeRequest) - .to receive(:head_pipeline) - .and_wrap_original do - Ci::Pipeline.find(pipeline.id) - end - end - - it "doesn't merge if any of stages failed" do - expect(MergeWorker).not_to receive(:perform_async) - - build.success - test.reload - test.drop - end - - it 'merges when all stages succeeded' do - expect(MergeWorker).to receive(:perform_async) - - build.success - test.reload - test.success - end - end - end -end diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index f7a39bb42d5..54b9c6dae38 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -76,10 +76,11 @@ describe MergeRequests::PushOptionsHandlerService do shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do subject(:last_mr) { MergeRequest.last } - it 'sets merge_when_pipeline_succeeds' do + it 'sets auto_merge_enabled' do service.execute - expect(last_mr.merge_when_pipeline_succeeds).to eq(true) + expect(last_mr.auto_merge_enabled).to eq(true) + expect(last_mr.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS) end it 'sets merge_user to the user' do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7258428589f..6ba67c7165c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -23,7 +23,8 @@ describe MergeRequests::RefreshService do source_branch: 'master', target_branch: 'feature', target_project: @project, - merge_when_pipeline_succeeds: true, + auto_merge_enabled: true, + auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, merge_user: @user) @another_merge_request = create(:merge_request, @@ -31,7 +32,8 @@ describe MergeRequests::RefreshService do source_branch: 'master', target_branch: 'test', target_project: @project, - merge_when_pipeline_succeeds: true, + auto_merge_enabled: true, + auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, merge_user: @user) @fork_merge_request = create(:merge_request, @@ -83,7 +85,7 @@ describe MergeRequests::RefreshService do expect(@merge_request.notes).not_to be_empty expect(@merge_request).to be_open - expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey + expect(@merge_request.auto_merge_enabled).to be_falsey expect(@merge_request.diff_head_sha).to eq(@newrev) expect(@fork_merge_request).to be_open expect(@fork_merge_request.notes).to be_empty @@ -292,7 +294,7 @@ describe MergeRequests::RefreshService do expect(@merge_request.notes).not_to be_empty expect(@merge_request).to be_open - expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey + expect(@merge_request.auto_merge_enabled).to be_falsey expect(@merge_request.diff_head_sha).to eq(@newrev) expect(@fork_merge_request).to be_open expect(@fork_merge_request.notes).to be_empty diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index ba4c9ce60f3..fbfcd95e204 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -217,8 +217,9 @@ describe MergeRequests::UpdateService, :mailer do head_pipeline_of: merge_request ) - expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user) + expect(AutoMerge::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user, {}) .and_return(service_mock) + allow(service_mock).to receive(:available_for?) { true } expect(service_mock).to receive(:execute).with(merge_request) end diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb index 4cbe384b47a..b511edfa620 100644 --- a/spec/workers/pipeline_success_worker_spec.rb +++ b/spec/workers/pipeline_success_worker_spec.rb @@ -5,12 +5,13 @@ require 'spec_helper' describe PipelineSuccessWorker do describe '#perform' do context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline, status: 'success') } + let(:pipeline) { create(:ci_pipeline, status: 'success', ref: merge_request.source_branch, project: merge_request.source_project) } + let(:merge_request) { create(:merge_request) } it 'performs "merge when pipeline succeeds"' do - expect_any_instance_of( - MergeRequests::MergeWhenPipelineSucceedsService - ).to receive(:trigger) + expect_next_instance_of(AutoMergeService) do |auto_merge| + expect(auto_merge).to receive(:process) + end described_class.new.perform(pipeline.id) end -- cgit v1.2.1 From 84e550fad9c95dd19bb1739fc48ef6694c9f737d Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Thu, 30 May 2019 13:47:39 -0300 Subject: Fix frontend tests related to autoMergeStrategy A few minor frontend changes to complete the refactoring from MWPS to the more generic autoMergeStrategy. --- .../vue_mr_widget/stores/get_state_key_spec.js | 6 +++--- .../mr_widget_merge_when_pipeline_succeeds_spec.js | 4 ++-- .../states/mr_widget_ready_to_merge_spec.js | 20 ++++++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js index 9797549498b..0f5d47b3bfe 100644 --- a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js +++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js @@ -4,7 +4,7 @@ describe('getStateKey', () => { it('should return proper state name', () => { const context = { mergeStatus: 'checked', - mergeWhenPipelineSucceeds: false, + autoMergeEnabled: false, canMerge: true, onlyAllowMergeIfPipelineSucceeds: false, isPipelineFailed: false, @@ -33,7 +33,7 @@ describe('getStateKey', () => { context.autoMergeEnabled = true; - expect(bound()).toEqual('mergeWhenPipelineSucceeds'); + expect(bound()).toEqual('autoMergeEnabled'); context.isSHAMismatch = true; @@ -80,7 +80,7 @@ describe('getStateKey', () => { it('returns rebased state key', () => { const context = { mergeStatus: 'checked', - mergeWhenPipelineSucceeds: false, + autoMergeEnabled: false, canMerge: true, onlyAllowMergeIfPipelineSucceeds: true, isPipelineFailed: true, diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js index b9718a78fa4..8e0415b813b 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js @@ -21,7 +21,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { canCancelAutomaticMerge: true, mergeUserId: 1, currentUserId: 1, - setToMWPSBy: {}, + setToAutoMergeBy: {}, sha, targetBranchPath, targetBranch, @@ -106,7 +106,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); expect(vm.service.merge).toHaveBeenCalledWith({ sha, - merge_when_pipeline_succeeds: true, + auto_merge_strategy: 'merge_when_pipeline_succeeds', should_remove_source_branch: true, }); done(); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index daca51c6156..3ae773b6ccb 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -80,7 +80,7 @@ describe('ReadyToMerge', () => { it('should have default data', () => { expect(vm.mergeWhenBuildSucceeds).toBeFalsy(); expect(vm.useCommitMessageWithDescription).toBeFalsy(); - expect(vm.setToAutoMerge).toBeFalsy(); + expect(vm.autoMergeStrategy).toBeUndefined(); expect(vm.showCommitMessageEditor).toBeFalsy(); expect(vm.isMakingRequest).toBeFalsy(); expect(vm.isMergingImmediately).toBeFalsy(); @@ -325,16 +325,20 @@ describe('ReadyToMerge', () => { vm.handleMergeButtonClick(true); setTimeout(() => { - expect(vm.setToAutoMerge).toBeTruthy(); + expect(vm.autoMergeStrategy).toBe('merge_when_pipeline_succeeds'); expect(vm.isMakingRequest).toBeTruthy(); expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); const params = vm.service.merge.calls.argsFor(0)[0]; - expect(params.sha).toEqual(vm.mr.sha); - expect(params.commit_message).toEqual(vm.mr.commitMessage); - expect(params.should_remove_source_branch).toBeFalsy(); - expect(params.merge_when_pipeline_succeeds).toBeTruthy(); + expect(params).toEqual( + jasmine.objectContaining({ + sha: vm.mr.sha, + commit_message: vm.mr.commitMessage, + should_remove_source_branch: false, + auto_merge_strategy: 'merge_when_pipeline_succeeds', + }), + ); done(); }, 333); }); @@ -345,7 +349,7 @@ describe('ReadyToMerge', () => { vm.handleMergeButtonClick(false, true); setTimeout(() => { - expect(vm.setToAutoMerge).toBeFalsy(); + expect(vm.autoMergeStrategy).toBeUndefined(); expect(vm.isMakingRequest).toBeTruthy(); expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); @@ -363,7 +367,7 @@ describe('ReadyToMerge', () => { vm.handleMergeButtonClick(); setTimeout(() => { - expect(vm.setToAutoMerge).toBeFalsy(); + expect(vm.autoMergeStrategy).toBeUndefined(); expect(vm.isMakingRequest).toBeTruthy(); expect(vm.initiateMergePolling).toHaveBeenCalled(); -- cgit v1.2.1 From b9cb49ad45e412d3330393d41b943c7b2957aefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= Date: Mon, 3 Jun 2019 09:32:12 +0200 Subject: Move specs to ce directory --- spec/controllers/groups_controller_spec.rb | 22 ++++++++++++++++++++++ spec/controllers/projects_controller_spec.rb | 12 ++++++++++++ 2 files changed, 34 insertions(+) (limited to 'spec') diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 1cd08200552..47d7e278183 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -141,6 +141,28 @@ describe GroupsController do end describe 'POST #create' do + it 'allows creating a group' do + sign_in(user) + + expect do + post :create, params: { group: { name: 'new_group', path: "new_group" } } + end.to change { Group.count }.by(1) + + expect(response).to have_gitlab_http_status(302) + end + + context 'authorization' do + it 'allows an admin to create a group' do + sign_in(create(:admin)) + + expect do + post :create, params: { group: { name: 'new_group', path: "new_group" } } + end.to change { Group.count }.by(1) + + expect(response).to have_gitlab_http_status(302) + end + end + context 'when creating subgroups', :nested_groups do [true, false].each do |can_create_group_status| context "and can_create_group is #{can_create_group_status}" do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 7f1bbebd128..8d2412f97ef 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -292,6 +292,18 @@ describe ProjectsController do end describe 'GET edit' do + it 'allows an admin user to access the page' do + sign_in(create(:user, :admin)) + + get :edit, + params: { + namespace_id: project.namespace.path, + id: project.path + } + + expect(response).to have_gitlab_http_status(200) + end + it 'sets the badge API endpoint' do sign_in(user) project.add_maintainer(user) -- cgit v1.2.1 From 83a8b779615c968af5afe15a1cbc6903d639f265 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Sun, 12 May 2019 16:10:46 -0500 Subject: Add Namespace and ProjectStatistics to GraphQL API We can query namespaces, and nested projects. Projects now exposes statistics --- .../resolvers/namespace_projects_resolver_spec.rb | 69 ++++++++++++++++++ spec/graphql/types/namespace_type.rb | 7 -- spec/graphql/types/namespace_type_spec.rb | 9 +++ spec/graphql/types/project_statistics_type_spec.rb | 10 +++ spec/graphql/types/project_type_spec.rb | 2 + spec/graphql/types/query_type_spec.rb | 12 +++- .../batch_project_statistics_loader_spec.rb | 18 +++++ spec/models/project_statistics_spec.rb | 14 ++++ spec/requests/api/graphql/group_query_spec.rb | 9 +-- .../api/graphql/namespace/projects_spec.rb | 82 ++++++++++++++++++++++ .../api/graphql/project/project_statistics_spec.rb | 43 ++++++++++++ 11 files changed, 263 insertions(+), 12 deletions(-) create mode 100644 spec/graphql/resolvers/namespace_projects_resolver_spec.rb delete mode 100644 spec/graphql/types/namespace_type.rb create mode 100644 spec/graphql/types/namespace_type_spec.rb create mode 100644 spec/graphql/types/project_statistics_type_spec.rb create mode 100644 spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb create mode 100644 spec/requests/api/graphql/namespace/projects_spec.rb create mode 100644 spec/requests/api/graphql/project/project_statistics_spec.rb (limited to 'spec') diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb new file mode 100644 index 00000000000..395e08081d3 --- /dev/null +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::NamespaceProjectsResolver, :nested_groups do + include GraphqlHelpers + + let(:current_user) { create(:user) } + + context "with a group" do + let(:group) { create(:group) } + let(:namespace) { group } + let(:project1) { create(:project, namespace: namespace) } + let(:project2) { create(:project, namespace: namespace) } + let(:nested_group) { create(:group, parent: group) } + let(:nested_project) { create(:project, group: nested_group) } + + before do + project1.add_developer(current_user) + project2.add_developer(current_user) + nested_project.add_developer(current_user) + end + + describe '#resolve' do + it 'finds all projects' do + expect(resolve_projects).to contain_exactly(project1, project2) + end + + it 'finds all projects including the subgroups' do + expect(resolve_projects(include_subgroups: true)).to contain_exactly(project1, project2, nested_project) + end + + context 'with an user namespace' do + let(:namespace) { current_user.namespace } + + it 'finds all projects' do + expect(resolve_projects).to contain_exactly(project1, project2) + end + + it 'finds all projects including the subgroups' do + expect(resolve_projects(include_subgroups: true)).to contain_exactly(project1, project2) + end + end + end + end + + context "when passing a non existent, batch loaded namespace" do + let(:namespace) do + BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _| + loader.call("non-existent-path", nil) + end + end + + it "returns nil without breaking" do + expect(resolve_projects).to be_empty + end + end + + it 'has an high complexity regardless of arguments' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + + expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24 + expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 + end + + def resolve_projects(args = { include_subgroups: false }, context = { current_user: current_user }) + resolve(described_class, obj: namespace, args: args, ctx: context) + end +end diff --git a/spec/graphql/types/namespace_type.rb b/spec/graphql/types/namespace_type.rb deleted file mode 100644 index 7cd6a79ae5d..00000000000 --- a/spec/graphql/types/namespace_type.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe GitlabSchema.types['Namespace'] do - it { expect(described_class.graphql_name).to eq('Namespace') } -end diff --git a/spec/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb new file mode 100644 index 00000000000..b4144cc4121 --- /dev/null +++ b/spec/graphql/types/namespace_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['Namespace'] do + it { expect(described_class.graphql_name).to eq('Namespace') } + + it { expect(described_class).to have_graphql_field(:projects) } +end diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb new file mode 100644 index 00000000000..485e194edb1 --- /dev/null +++ b/spec/graphql/types/project_statistics_type_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['ProjectStatistics'] do + it "has all the required fields" do + is_expected.to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size, + :build_artifacts_size, :packages_size, :commit_count) + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 075fa7c7e43..cb5ac2e3cb1 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -19,4 +19,6 @@ describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_field(:pipelines) } it { is_expected.to have_graphql_field(:repository) } + + it { is_expected.to have_graphql_field(:statistics) } end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index b4626955816..af1972a2513 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -5,7 +5,17 @@ describe GitlabSchema.types['Query'] do expect(described_class.graphql_name).to eq('Query') end - it { is_expected.to have_graphql_fields(:project, :group, :echo, :metadata) } + it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata) } + + describe 'namespace field' do + subject { described_class.fields['namespace'] } + + it 'finds namespaces by full path' do + is_expected.to have_graphql_arguments(:full_path) + is_expected.to have_graphql_type(Types::NamespaceType) + is_expected.to have_graphql_resolver(Resolvers::NamespaceResolver) + end + end describe 'project field' do subject { described_class.fields['project'] } diff --git a/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb new file mode 100644 index 00000000000..ec2fcad31e5 --- /dev/null +++ b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader do + describe '#find' do + it 'only queries once for project statistics' do + stats = create_list(:project_statistics, 2) + project1 = stats.first.project + project2 = stats.last.project + + expect do + described_class.new(project1.id).find + described_class.new(project2.id).find + end.not_to exceed_query_limit(1) + end + end +end diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index f985c114d4b..358873f9a2f 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -11,6 +11,20 @@ describe ProjectStatistics do it { is_expected.to belong_to(:namespace) } end + describe 'scopes' do + describe '.for_project_ids' do + it 'returns only requested projects' do + stats = create_list(:project_statistics, 3) + project_ids = stats[0..1].map { |s| s.project_id } + expected_ids = stats[0..1].map { |s| s.id } + + requested_stats = described_class.for_project_ids(project_ids).pluck(:id) + + expect(requested_stats).to eq(expected_ids) + end + end + end + describe 'statistics columns' do it "support values up to 8 exabytes" do statistics.update!( diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb index 8ff95cc9af2..db9f2ac9dd0 100644 --- a/spec/requests/api/graphql/group_query_spec.rb +++ b/spec/requests/api/graphql/group_query_spec.rb @@ -86,17 +86,18 @@ describe 'getting group information' do end it 'avoids N+1 queries' do - post_graphql(group_query(group1), current_user: admin) - control_count = ActiveRecord::QueryRecorder.new do post_graphql(group_query(group1), current_user: admin) end.count - create(:project, namespace: group1) + queries = [{ query: group_query(group1) }, + { query: group_query(group2) }] expect do - post_graphql(group_query(group1), current_user: admin) + post_multiplex(queries, current_user: admin) end.not_to exceed_query_limit(control_count) + + expect(graphql_errors).to contain_exactly(nil, nil) end end diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb new file mode 100644 index 00000000000..e05273da4bd --- /dev/null +++ b/spec/requests/api/graphql/namespace/projects_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting projects', :nested_groups do + include GraphqlHelpers + + let(:group) { create(:group) } + let!(:project) { create(:project, namespace: subject) } + let(:nested_group) { create(:group, parent: group) } + let!(:nested_project) { create(:project, group: nested_group) } + let!(:public_project) { create(:project, :public, namespace: subject) } + let(:user) { create(:user) } + let(:include_subgroups) { true } + + subject { group } + + let(:query) do + graphql_query_for( + 'namespace', + { 'fullPath' => subject.full_path }, + <<~QUERY + projects(includeSubgroups: #{include_subgroups}) { + edges { + node { + #{all_graphql_fields_for('Project')} + } + } + } + QUERY + ) + end + + before do + group.add_owner(user) + end + + shared_examples 'a graphql namespace' do + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it "includes the packages size if the user can read the statistics" do + post_graphql(query, current_user: user) + + count = if include_subgroups + subject.all_projects.count + else + subject.projects.count + end + + expect(graphql_data['namespace']['projects']['edges'].size).to eq(count) + end + + context 'with no user' do + it 'finds only public projects' do + post_graphql(query, current_user: nil) + + expect(graphql_data['namespace']['projects']['edges'].size).to eq(1) + project = graphql_data['namespace']['projects']['edges'][0]['node'] + expect(project['id']).to eq(public_project.id.to_s) + end + end + end + + it_behaves_like 'a graphql namespace' + + context 'when the namespace is a user' do + subject { user.namespace } + let(:include_subgroups) { false } + + it_behaves_like 'a graphql namespace' + end + + context 'when not including subgroups' do + let(:include_subgroups) { false } + + it_behaves_like 'a graphql namespace' + end +end diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb new file mode 100644 index 00000000000..8683fa1f390 --- /dev/null +++ b/spec/requests/api/graphql/project/project_statistics_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'rendering namespace statistics' do + include GraphqlHelpers + + let(:project) { create(:project) } + let!(:project_statistics) { create(:project_statistics, project: project, packages_size: 5.megabytes) } + let(:user) { create(:user) } + + let(:query) do + graphql_query_for('project', + { 'fullPath' => project.full_path }, + "statistics { #{all_graphql_fields_for('ProjectStatistics')} }") + end + + before do + project.add_reporter(user) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it "includes the packages size if the user can read the statistics" do + post_graphql(query, current_user: user) + + expect(graphql_data['project']['statistics']['packagesSize']).to eq(5.megabytes) + end + + context 'when the project is public' do + let(:project) { create(:project, :public) } + + it 'includes the statistics regardless of the user' do + post_graphql(query, current_user: nil) + + expect(graphql_data['project']['statistics']).to be_present + end + end +end -- cgit v1.2.1 From 74e9943ca3f5597f1e218e177c328f86c2c2a09f Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Mon, 3 Jun 2019 11:24:10 +0000 Subject: Add dependency_list report fixture --- .../gl-dependency-scanning-report.json | 422 +++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json (limited to 'spec') diff --git a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json new file mode 100644 index 00000000000..1e62d020026 --- /dev/null +++ b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json @@ -0,0 +1,422 @@ +{ + "version": "2.1", + "vulnerabilities": [ + { + "category": "dependency_scanning", + "name": "Vulnerabilities in libxml2", + "message": "Vulnerabilities in libxml2 in nokogiri", + "description": " The version of libxml2 packaged with Nokogiri contains several vulnerabilities.\r\n Nokogiri has mitigated these issues by upgrading to libxml 2.9.5.\r\n\r\n It was discovered that a type confusion error existed in libxml2. An\r\n attacker could use this to specially construct XML data that\r\n could cause a denial of service or possibly execute arbitrary\r\n code. (CVE-2017-0663)\r\n\r\n It was discovered that libxml2 did not properly validate parsed entity\r\n references. An attacker could use this to specially construct XML\r\n data that could expose sensitive information. (CVE-2017-7375)\r\n\r\n It was discovered that a buffer overflow existed in libxml2 when\r\n handling HTTP redirects. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-7376)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overflow in\r\n libxml2 when handling elements. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-9047)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overread\r\n in libxml2 when handling elements. An attacker could use this\r\n to specially construct XML data that could cause a denial of\r\n service. (CVE-2017-9048)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered multiple buffer overreads\r\n in libxml2 when handling parameter-entity references. An attacker\r\n could use these to specially construct XML data that could cause a\r\n denial of service. (CVE-2017-9049, CVE-2017-9050)", + "cve": "rails/Gemfile.lock:nokogiri:gemnasium:06565b64-486d-4326-b906-890d9915804d", + "severity": "Unknown", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "rails/Gemfile.lock", + "dependency": { + "package": { + "name": "nokogiri" + }, + "version": "1.8.0" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d", + "value": "06565b64-486d-4326-b906-890d9915804d", + "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories" + }, + { + "type": "usn", + "name": "USN-3424-1", + "value": "USN-3424-1", + "url": "https://usn.ubuntu.com/3424-1/" + } + ], + "links": [ + { + "url": "https://github.com/sparklemotion/nokogiri/issues/1673" + } + ] + }, + { + "category": "dependency_scanning", + "name": "Infinite recursion in parameter entities", + "message": "Infinite recursion in parameter entities in nokogiri", + "description": "libxml2 incorrectly handles certain parameter entities. An attacker can leverage this with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.", + "cve": "rails/Gemfile.lock:nokogiri:gemnasium:6a0d56f6-2441-492a-9b14-edb95ac31919", + "severity": "Unknown", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "rails/Gemfile.lock", + "dependency": { + "package": { + "name": "nokogiri" + }, + "version": "1.8.0" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-6a0d56f6-2441-492a-9b14-edb95ac31919", + "value": "6a0d56f6-2441-492a-9b14-edb95ac31919", + "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories" + }, + { + "type": "cve", + "name": "CVE-2017-16932", + "value": "CVE-2017-16932", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932" + } + ], + "links": [ + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932" + }, + { + "url": "https://github.com/sparklemotion/nokogiri/issues/1714" + }, + { + "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-16932.html" + }, + { + "url": "https://usn.ubuntu.com/usn/usn-3504-1/" + } + ] + }, + { + "category": "dependency_scanning", + "name": "Denial of Service", + "message": "Denial of Service in nokogiri", + "description": "libxml2 incorrectly handles certain files. An attacker can use this issue with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.\r\n\r\n", + "cve": "rails/Gemfile.lock:nokogiri:gemnasium:78658378-bd8f-4d79-81c8-07c419302426", + "severity": "Unknown", + "solution": "Upgrade to latest version.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "rails/Gemfile.lock", + "dependency": { + "package": { + "name": "nokogiri" + }, + "version": "1.8.0" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-78658378-bd8f-4d79-81c8-07c419302426", + "value": "78658378-bd8f-4d79-81c8-07c419302426", + "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories" + }, + { + "type": "cve", + "name": "CVE-2017-15412", + "value": "CVE-2017-15412", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412" + } + ], + "links": [ + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412" + }, + { + "url": "https://github.com/sparklemotion/nokogiri/issues/1714" + }, + { + "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-15412.html" + } + ] + }, + { + "category": "dependency_scanning", + "name": "Bypass of a protection mechanism in libxslt", + "message": "Bypass of a protection mechanism in libxslt in nokogiri", + "description": "libxslt through 1.1.33 allows bypass of a protection mechanism because callers of xsltCheckRead and xsltCheckWrite permit access even upon receiving a -1 error code. xsltCheckRead can return -1 for a crafted URL that is not actually invalid and is subsequently loaded. Vendored version of libxslt has been patched to remediate this vulnerability. Note that this patch is not yet (as of 2019-04-22) in an upstream release of libxslt.", + "cve": "rails/Gemfile.lock:nokogiri:gemnasium:1a2e2e6e-67ba-4142-bfa1-3391f5416e4c", + "severity": "Unknown", + "solution": "Upgrade to latest version if using vendored version of libxslt OR update the system library libxslt to a fixed version", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "rails/Gemfile.lock", + "dependency": { + "package": { + "name": "nokogiri" + }, + "version": "1.8.0" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-1a2e2e6e-67ba-4142-bfa1-3391f5416e4c", + "value": "1a2e2e6e-67ba-4142-bfa1-3391f5416e4c", + "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories" + }, + { + "type": "cve", + "name": "CVE-2019-11068", + "value": "CVE-2019-11068", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068" + } + ], + "links": [ + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068" + }, + { + "url": "https://github.com/sparklemotion/nokogiri/issues/1892" + }, + { + "url": "https://people.canonical.com/~ubuntu-security/cve/CVE-2019-11068" + }, + { + "url": "https://security-tracker.debian.org/tracker/CVE-2019-11068" + } + ] + }, + { + "category": "dependency_scanning", + "name": "Regular Expression Denial of Service", + "message": "Regular Expression Denial of Service in debug", + "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.", + "cve": "yarn/yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a", + "severity": "Unknown", + "solution": "Upgrade to latest versions.", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "yarn/yarn.lock", + "dependency": { + "package": { + "name": "debug" + }, + "version": "1.0.5" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a", + "value": "37283ed4-0380-40d7-ada7-2d994afcc62a", + "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories" + } + ], + "links": [ + { + "url": "https://github.com/visionmedia/debug/issues/501" + }, + { + "url": "https://github.com/visionmedia/debug/pull/504" + }, + { + "url": "https://nodesecurity.io/advisories/534" + } + ] + }, + { + "category": "dependency_scanning", + "name": "Authentication bypass via incorrect DOM traversal and canonicalization", + "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js", + "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.", + "cve": "yarn/yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98", + "severity": "Unknown", + "solution": "Upgrade to fixed version.\r\n", + "scanner": { + "id": "gemnasium", + "name": "Gemnasium" + }, + "location": { + "file": "yarn/yarn.lock", + "dependency": { + "package": { + "name": "saml2-js" + }, + "version": "1.5.0" + } + }, + "identifiers": [ + { + "type": "gemnasium", + "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98", + "value": "9952e574-7b5b-46fa-a270-aeb694198a98", + "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories" + }, + { + "type": "cve", + "name": "CVE-2017-11429", + "value": "CVE-2017-11429", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429" + } + ], + "links": [ + { + "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279" + }, + { + "url": "https://github.com/Clever/saml2/issues/127" + }, + { + "url": "https://www.kb.cert.org/vuls/id/475445" + } + ] + } + ], + "remediations": [], + "dependency_files": [ + { + "path": "rails/Gemfile.lock", + "package_manager": "bundler", + "dependencies": [ + { + "package": { + "name": "mini_portile2" + }, + "version": "2.2.0" + }, + { + "package": { + "name": "nokogiri" + }, + "version": "1.8.0" + } + ] + }, + { + "path": "yarn/yarn.lock", + "package_manager": "yarn", + "dependencies": [ + { + "package": { + "name": "async" + }, + "version": "0.2.10" + }, + { + "package": { + "name": "async" + }, + "version": "1.5.2" + }, + { + "package": { + "name": "debug" + }, + "version": "1.0.5" + }, + { + "package": { + "name": "ejs" + }, + "version": "0.8.8" + }, + { + "package": { + "name": "ms" + }, + "version": "2.0.0" + }, + { + "package": { + "name": "node-forge" + }, + "version": "0.2.24" + }, + { + "package": { + "name": "saml2-js" + }, + "version": "1.5.0" + }, + { + "package": { + "name": "sax" + }, + "version": "1.2.4" + }, + { + "package": { + "name": "underscore" + }, + "version": "1.9.1" + }, + { + "package": { + "name": "underscore" + }, + "version": "1.6.0" + }, + { + "package": { + "name": "xml-crypto" + }, + "version": "0.8.5" + }, + { + "package": { + "name": "xml-encryption" + }, + "version": "0.7.4" + }, + { + "package": { + "name": "xml2js" + }, + "version": "0.4.19" + }, + { + "package": { + "name": "xmlbuilder" + }, + "version": "2.1.0" + }, + { + "package": { + "name": "xmlbuilder" + }, + "version": "9.0.7" + }, + { + "package": { + "name": "xmldom" + }, + "version": "0.1.19" + }, + { + "package": { + "name": "xmldom" + }, + "version": "0.1.27" + }, + { + "package": { + "name": "xpath.js" + }, + "version": "1.1.0" + }, + { + "package": { + "name": "xpath" + }, + "version": "0.0.5" + } + ] + } + ] +} -- cgit v1.2.1 From 6ca5b19aafae10f0d9dfd3018e27f9b1731101f2 Mon Sep 17 00:00:00 2001 From: Paul Gascou-Vaillancourt Date: Thu, 30 May 2019 13:40:20 -0400 Subject: Add global isSafeURL utility - Added isSafeURL utility based on prior work in gitlab-ee - Also added isAbsoluteOrRootRelative() and getBaseURL() utils, needed by isSafeURL - Removed URL() fallback because URL() is now polyfilled - Updated specs --- spec/frontend/lib/utils/url_utility_spec.js | 194 +++++++++++++++++++++++++ spec/javascripts/lib/utils/url_utility_spec.js | 110 -------------- 2 files changed, 194 insertions(+), 110 deletions(-) create mode 100644 spec/frontend/lib/utils/url_utility_spec.js delete mode 100644 spec/javascripts/lib/utils/url_utility_spec.js (limited to 'spec') diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js new file mode 100644 index 00000000000..eca240c9c18 --- /dev/null +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -0,0 +1,194 @@ +import * as urlUtils from '~/lib/utils/url_utility'; + +describe('URL utility', () => { + describe('webIDEUrl', () => { + afterEach(() => { + gon.relative_url_root = ''; + }); + + describe('without relative_url_root', () => { + it('returns IDE path with route', () => { + expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe( + '/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1', + ); + }); + }); + + describe('with relative_url_root', () => { + beforeEach(() => { + gon.relative_url_root = '/gitlab'; + }); + + it('returns IDE path with route', () => { + expect(urlUtils.webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe( + '/gitlab/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1', + ); + }); + }); + }); + + describe('mergeUrlParams', () => { + it('adds w', () => { + expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag'); + expect(urlUtils.mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag'); + expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1'); + expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe( + 'https://host/path?w=1#frag', + ); + + expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe( + 'https://h/p?k1=v1&w=1#frag', + ); + }); + + it('updates w', () => { + expect(urlUtils.mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag'); + }); + + it('adds multiple params', () => { + expect(urlUtils.mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag'); + }); + + it('adds and updates encoded params', () => { + expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag'); + }); + }); + + describe('removeParams', () => { + describe('when url is passed', () => { + it('removes query param with encoded ampersand', () => { + const url = urlUtils.removeParams(['filter'], '/mail?filter=n%3Djoe%26l%3Dhome'); + + expect(url).toBe('/mail'); + }); + + it('should remove param when url has no other params', () => { + const url = urlUtils.removeParams(['size'], '/feature/home?size=5'); + + expect(url).toBe('/feature/home'); + }); + + it('should remove param when url has other params', () => { + const url = urlUtils.removeParams(['size'], '/feature/home?q=1&size=5&f=html'); + + expect(url).toBe('/feature/home?q=1&f=html'); + }); + + it('should remove param and preserve fragment', () => { + const url = urlUtils.removeParams(['size'], '/feature/home?size=5#H2'); + + expect(url).toBe('/feature/home#H2'); + }); + + it('should remove multiple params', () => { + const url = urlUtils.removeParams(['z', 'a'], '/home?z=11111&l=en_US&a=true#H2'); + + expect(url).toBe('/home?l=en_US#H2'); + }); + }); + }); + + describe('setUrlFragment', () => { + it('should set fragment when url has no fragment', () => { + const url = urlUtils.setUrlFragment('/home/feature', 'usage'); + + expect(url).toBe('/home/feature#usage'); + }); + + it('should set fragment when url has existing fragment', () => { + const url = urlUtils.setUrlFragment('/home/feature#overview', 'usage'); + + expect(url).toBe('/home/feature#usage'); + }); + + it('should set fragment when given fragment includes #', () => { + const url = urlUtils.setUrlFragment('/home/feature#overview', '#install'); + + expect(url).toBe('/home/feature#install'); + }); + }); + + describe('getBaseURL', () => { + beforeEach(() => { + global.window = Object.create(window); + Object.defineProperty(window, 'location', { + value: { + host: 'gitlab.com', + protocol: 'https:', + }, + }); + }); + + it('returns correct base URL', () => { + expect(urlUtils.getBaseURL()).toBe('https://gitlab.com'); + }); + }); + + describe('isAbsoluteOrRootRelative', () => { + const validUrls = ['https://gitlab.com/', 'http://gitlab.com/', '/users/sign_in']; + + const invalidUrls = [' https://gitlab.com/', './file/path', 'notanurl', '']; + + it.each(validUrls)(`returns true for %s`, url => { + expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(true); + }); + + it.each(invalidUrls)(`returns false for %s`, url => { + expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(false); + }); + }); + + describe('isSafeUrl', () => { + const absoluteUrls = [ + 'http://example.org', + 'http://example.org:8080', + 'https://example.org', + 'https://example.org:8080', + 'https://192.168.1.1', + ]; + + const rootRelativeUrls = ['/relative/link']; + + const relativeUrls = ['./relative/link', '../relative/link']; + + const urlsWithoutHost = ['http://', 'https://', 'https:https:https:']; + + /* eslint-disable no-script-url */ + const nonHttpUrls = [ + 'javascript:', + 'javascript:alert("XSS")', + 'jav\tascript:alert("XSS");', + '  javascript:alert("XSS");', + 'ftp://192.168.1.1', + 'file:///', + 'file:///etc/hosts', + ]; + /* eslint-enable no-script-url */ + + // javascript:alert('XSS') + const encodedJavaScriptUrls = [ + 'javascript:alert('XSS')', + 'javascript:alert('XSS')', + 'javascript:alert('XSS')', + '\\u006A\\u0061\\u0076\\u0061\\u0073\\u0063\\u0072\\u0069\\u0070\\u0074\\u003A\\u0061\\u006C\\u0065\\u0072\\u0074\\u0028\\u0027\\u0058\\u0053\\u0053\\u0027\\u0029', + ]; + + const safeUrls = [...absoluteUrls, ...rootRelativeUrls]; + const unsafeUrls = [ + ...relativeUrls, + ...urlsWithoutHost, + ...nonHttpUrls, + ...encodedJavaScriptUrls, + ]; + + describe('with URL constructor support', () => { + it.each(safeUrls)('returns true for %s', url => { + expect(urlUtils.isSafeURL(url)).toBe(true); + }); + + it.each(unsafeUrls)('returns false for %s', url => { + expect(urlUtils.isSafeURL(url)).toBe(false); + }); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/url_utility_spec.js b/spec/javascripts/lib/utils/url_utility_spec.js deleted file mode 100644 index 381c7b2d0a6..00000000000 --- a/spec/javascripts/lib/utils/url_utility_spec.js +++ /dev/null @@ -1,110 +0,0 @@ -import * as urlUtils from '~/lib/utils/url_utility'; - -describe('URL utility', () => { - describe('webIDEUrl', () => { - afterEach(() => { - gon.relative_url_root = ''; - }); - - describe('without relative_url_root', () => { - it('returns IDE path with route', () => { - expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe( - '/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1', - ); - }); - }); - - describe('with relative_url_root', () => { - beforeEach(() => { - gon.relative_url_root = '/gitlab'; - }); - - it('returns IDE path with route', () => { - expect(urlUtils.webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe( - '/gitlab/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1', - ); - }); - }); - }); - - describe('mergeUrlParams', () => { - it('adds w', () => { - expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag'); - expect(urlUtils.mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag'); - expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1'); - expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe( - 'https://host/path?w=1#frag', - ); - - expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe( - 'https://h/p?k1=v1&w=1#frag', - ); - }); - - it('updates w', () => { - expect(urlUtils.mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag'); - }); - - it('adds multiple params', () => { - expect(urlUtils.mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag'); - }); - - it('adds and updates encoded params', () => { - expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag'); - }); - }); - - describe('removeParams', () => { - describe('when url is passed', () => { - it('removes query param with encoded ampersand', () => { - const url = urlUtils.removeParams(['filter'], '/mail?filter=n%3Djoe%26l%3Dhome'); - - expect(url).toBe('/mail'); - }); - - it('should remove param when url has no other params', () => { - const url = urlUtils.removeParams(['size'], '/feature/home?size=5'); - - expect(url).toBe('/feature/home'); - }); - - it('should remove param when url has other params', () => { - const url = urlUtils.removeParams(['size'], '/feature/home?q=1&size=5&f=html'); - - expect(url).toBe('/feature/home?q=1&f=html'); - }); - - it('should remove param and preserve fragment', () => { - const url = urlUtils.removeParams(['size'], '/feature/home?size=5#H2'); - - expect(url).toBe('/feature/home#H2'); - }); - - it('should remove multiple params', () => { - const url = urlUtils.removeParams(['z', 'a'], '/home?z=11111&l=en_US&a=true#H2'); - - expect(url).toBe('/home?l=en_US#H2'); - }); - }); - }); - - describe('setUrlFragment', () => { - it('should set fragment when url has no fragment', () => { - const url = urlUtils.setUrlFragment('/home/feature', 'usage'); - - expect(url).toBe('/home/feature#usage'); - }); - - it('should set fragment when url has existing fragment', () => { - const url = urlUtils.setUrlFragment('/home/feature#overview', 'usage'); - - expect(url).toBe('/home/feature#usage'); - }); - - it('should set fragment when given fragment includes #', () => { - const url = urlUtils.setUrlFragment('/home/feature#overview', '#install'); - - expect(url).toBe('/home/feature#install'); - }); - }); -}); -- cgit v1.2.1 From e8683efea5aaed0c8c4b6efc75395372a3a3662b Mon Sep 17 00:00:00 2001 From: Matthias Baur Date: Mon, 3 Jun 2019 14:52:54 +0200 Subject: Allow lowercase prefix for Youtrack issue ids Relates to #42595. Fixes #62661. --- spec/lib/banzai/filter/external_issue_reference_filter_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index 43222ddb5e2..7c94cf37e32 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -155,6 +155,13 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do it_behaves_like "external issue tracker" end + + context "with a lowercase prefix" do + let(:issue) { ExternalIssue.new("gl-030", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end end context "jira project" do -- cgit v1.2.1 From aea013eab9c0f71e95f75f47fa382f4d8f2d3ace Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 3 Jun 2019 14:36:34 +0000 Subject: Move NoteApp tests to Jest --- spec/frontend/helpers/jquery.js | 6 + spec/frontend/notes/components/note_app_spec.js | 322 ++++++++++++++++++++ spec/javascripts/notes/components/note_app_spec.js | 331 --------------------- 3 files changed, 328 insertions(+), 331 deletions(-) create mode 100644 spec/frontend/helpers/jquery.js create mode 100644 spec/frontend/notes/components/note_app_spec.js delete mode 100644 spec/javascripts/notes/components/note_app_spec.js (limited to 'spec') diff --git a/spec/frontend/helpers/jquery.js b/spec/frontend/helpers/jquery.js new file mode 100644 index 00000000000..6421a592c0c --- /dev/null +++ b/spec/frontend/helpers/jquery.js @@ -0,0 +1,6 @@ +import $ from 'jquery'; + +global.$ = $; +global.jQuery = $; + +export default $; diff --git a/spec/frontend/notes/components/note_app_spec.js b/spec/frontend/notes/components/note_app_spec.js new file mode 100644 index 00000000000..ff833d2c899 --- /dev/null +++ b/spec/frontend/notes/components/note_app_spec.js @@ -0,0 +1,322 @@ +import $ from 'helpers/jquery'; +import Vue from 'vue'; +import { mount, createLocalVue } from '@vue/test-utils'; +import NotesApp from '~/notes/components/notes_app.vue'; +import service from '~/notes/services/notes_service'; +import createStore from '~/notes/stores'; +import '~/behaviors/markdown/render_gfm'; +import { setTestTimeout } from 'helpers/timeout'; +// TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-ce/issues/62491) +import * as mockData from '../../../javascripts/notes/mock_data'; + +const originalInterceptors = [...Vue.http.interceptors]; + +const emptyResponseInterceptor = (request, next) => { + next( + request.respondWith(JSON.stringify([]), { + status: 200, + }), + ); +}; + +setTestTimeout(1000); + +describe('note_app', () => { + let mountComponent; + let wrapper; + let store; + + /** + * waits for fetchNotes() to complete + */ + const waitForDiscussionsRequest = () => + new Promise(resolve => { + const { vm } = wrapper.find(NotesApp); + const unwatch = vm.$watch('isFetching', isFetching => { + if (isFetching) { + return; + } + + unwatch(); + resolve(); + }); + }); + + beforeEach(() => { + $('body').attr('data-page', 'projects:merge_requests:show'); + + store = createStore(); + mountComponent = data => { + const propsData = data || { + noteableData: mockData.noteableDataMock, + notesData: mockData.notesDataMock, + userData: mockData.userDataMock, + }; + const localVue = createLocalVue(); + + return mount( + { + components: { + NotesApp, + }, + template: '
      ', + }, + { + attachToDocument: true, + propsData, + store, + localVue, + sync: false, + }, + ); + }; + }); + + afterEach(() => { + wrapper.destroy(); + Vue.http.interceptors = [...originalInterceptors]; + }); + + describe('set data', () => { + beforeEach(() => { + Vue.http.interceptors.push(emptyResponseInterceptor); + wrapper = mountComponent(); + return waitForDiscussionsRequest(); + }); + + it('should set notes data', () => { + expect(store.state.notesData).toEqual(mockData.notesDataMock); + }); + + it('should set issue data', () => { + expect(store.state.noteableData).toEqual(mockData.noteableDataMock); + }); + + it('should set user data', () => { + expect(store.state.userData).toEqual(mockData.userDataMock); + }); + + it('should fetch discussions', () => { + expect(store.state.discussions).toEqual([]); + }); + }); + + describe('render', () => { + beforeEach(() => { + setFixtures('
      '); + + Vue.http.interceptors.push(mockData.individualNoteInterceptor); + wrapper = mountComponent(); + return waitForDiscussionsRequest(); + }); + + it('should render list of notes', () => { + const note = + mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[ + '/gitlab-org/gitlab-ce/issues/26/discussions.json' + ][0].notes[0]; + + expect( + wrapper + .find('.main-notes-list .note-header-author-name') + .text() + .trim(), + ).toEqual(note.author.name); + + expect(wrapper.find('.main-notes-list .note-text').html()).toContain(note.note_html); + }); + + it('should render form', () => { + expect(wrapper.find('.js-main-target-form').name()).toEqual('form'); + expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual( + 'Write a comment or drag your files here…', + ); + }); + + it('should not render form when commenting is disabled', () => { + wrapper.destroy(); + + store.state.commentsDisabled = true; + wrapper = mountComponent(); + return waitForDiscussionsRequest().then(() => { + expect(wrapper.find('.js-main-target-form').exists()).toBe(false); + }); + }); + + it('should render discussion filter note `commentsDisabled` is true', () => { + wrapper.destroy(); + + store.state.commentsDisabled = true; + wrapper = mountComponent(); + return waitForDiscussionsRequest().then(() => { + expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true); + }); + }); + + it('should render form comment button as disabled', () => { + expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled'); + }); + + it('updates discussions badge', () => { + expect(document.querySelector('.js-discussions-count').textContent).toEqual('2'); + }); + }); + + describe('while fetching data', () => { + beforeEach(() => { + Vue.http.interceptors.push(emptyResponseInterceptor); + wrapper = mountComponent(); + }); + + afterEach(() => waitForDiscussionsRequest()); + + it('renders skeleton notes', () => { + expect(wrapper.find('.animation-container').exists()).toBe(true); + }); + + it('should render form', () => { + expect(wrapper.find('.js-main-target-form').name()).toEqual('form'); + expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual( + 'Write a comment or drag your files here…', + ); + }); + }); + + describe('update note', () => { + describe('individual note', () => { + beforeEach(() => { + Vue.http.interceptors.push(mockData.individualNoteInterceptor); + jest.spyOn(service, 'updateNote'); + wrapper = mountComponent(); + return waitForDiscussionsRequest().then(() => { + wrapper.find('.js-note-edit').trigger('click'); + }); + }); + + it('renders edit form', () => { + expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true); + }); + + it('calls the service to update the note', () => { + wrapper.find('.js-vue-issue-note-form').value = 'this is a note'; + wrapper.find('.js-vue-issue-save').trigger('click'); + + expect(service.updateNote).toHaveBeenCalled(); + }); + }); + + describe('discussion note', () => { + beforeEach(() => { + Vue.http.interceptors.push(mockData.discussionNoteInterceptor); + jest.spyOn(service, 'updateNote'); + wrapper = mountComponent(); + return waitForDiscussionsRequest().then(() => { + wrapper.find('.js-note-edit').trigger('click'); + }); + }); + + it('renders edit form', () => { + expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true); + }); + + it('updates the note and resets the edit form', () => { + wrapper.find('.js-vue-issue-note-form').value = 'this is a note'; + wrapper.find('.js-vue-issue-save').trigger('click'); + + expect(service.updateNote).toHaveBeenCalled(); + }); + }); + }); + + describe('new note form', () => { + beforeEach(() => { + Vue.http.interceptors.push(mockData.individualNoteInterceptor); + wrapper = mountComponent(); + return waitForDiscussionsRequest(); + }); + + it('should render markdown docs url', () => { + const { markdownDocsPath } = mockData.notesDataMock; + + expect( + wrapper + .find(`a[href="${markdownDocsPath}"]`) + .text() + .trim(), + ).toEqual('Markdown'); + }); + + it('should render quick action docs url', () => { + const { quickActionsDocsPath } = mockData.notesDataMock; + + expect( + wrapper + .find(`a[href="${quickActionsDocsPath}"]`) + .text() + .trim(), + ).toEqual('quick actions'); + }); + }); + + describe('edit form', () => { + beforeEach(() => { + Vue.http.interceptors.push(mockData.individualNoteInterceptor); + wrapper = mountComponent(); + return waitForDiscussionsRequest(); + }); + + it('should render markdown docs url', () => { + wrapper.find('.js-note-edit').trigger('click'); + const { markdownDocsPath } = mockData.notesDataMock; + + return Vue.nextTick().then(() => { + expect( + wrapper + .find(`.edit-note a[href="${markdownDocsPath}"]`) + .text() + .trim(), + ).toEqual('Markdown is supported'); + }); + }); + + it('should not render quick actions docs url', () => { + wrapper.find('.js-note-edit').trigger('click'); + const { quickActionsDocsPath } = mockData.notesDataMock; + expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false); + }); + }); + + describe('emoji awards', () => { + beforeEach(() => { + Vue.http.interceptors.push(emptyResponseInterceptor); + wrapper = mountComponent(); + return waitForDiscussionsRequest(); + }); + + it('dispatches toggleAward after toggleAward event', () => { + const toggleAwardEvent = new CustomEvent('toggleAward', { + detail: { + awardName: 'test', + noteId: 1, + }, + }); + const toggleAwardAction = jest.fn().mockName('toggleAward'); + wrapper.vm.$store.hotUpdate({ + actions: { + toggleAward: toggleAwardAction, + stopPolling() {}, + }, + }); + + wrapper.vm.$parent.$el.dispatchEvent(toggleAwardEvent); + + expect(toggleAwardAction).toHaveBeenCalledTimes(1); + const [, payload] = toggleAwardAction.mock.calls[0]; + + expect(payload).toEqual({ + awardName: 'test', + noteId: 1, + }); + }); + }); +}); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js deleted file mode 100644 index ef876dc2941..00000000000 --- a/spec/javascripts/notes/components/note_app_spec.js +++ /dev/null @@ -1,331 +0,0 @@ -import $ from 'jquery'; -import _ from 'underscore'; -import Vue from 'vue'; -import { mount, createLocalVue } from '@vue/test-utils'; -import NotesApp from '~/notes/components/notes_app.vue'; -import service from '~/notes/services/notes_service'; -import createStore from '~/notes/stores'; -import '~/behaviors/markdown/render_gfm'; -import * as mockData from '../mock_data'; - -describe('note_app', () => { - let mountComponent; - let wrapper; - let store; - - beforeEach(() => { - $('body').attr('data-page', 'projects:merge_requests:show'); - - store = createStore(); - mountComponent = data => { - const propsData = data || { - noteableData: mockData.noteableDataMock, - notesData: mockData.notesDataMock, - userData: mockData.userDataMock, - }; - const localVue = createLocalVue(); - - return mount( - { - components: { - NotesApp, - }, - template: '
      ', - }, - { - propsData, - store, - localVue, - sync: false, - }, - ); - }; - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('set data', () => { - const responseInterceptor = (request, next) => { - next( - request.respondWith(JSON.stringify([]), { - status: 200, - }), - ); - }; - - beforeEach(() => { - Vue.http.interceptors.push(responseInterceptor); - wrapper = mountComponent(); - }); - - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, responseInterceptor); - }); - - it('should set notes data', () => { - expect(store.state.notesData).toEqual(mockData.notesDataMock); - }); - - it('should set issue data', () => { - expect(store.state.noteableData).toEqual(mockData.noteableDataMock); - }); - - it('should set user data', () => { - expect(store.state.userData).toEqual(mockData.userDataMock); - }); - - it('should fetch discussions', () => { - expect(store.state.discussions).toEqual([]); - }); - }); - - describe('render', () => { - beforeEach(() => { - setFixtures('
      '); - - Vue.http.interceptors.push(mockData.individualNoteInterceptor); - wrapper = mountComponent(); - }); - - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor); - }); - - it('should render list of notes', done => { - const note = - mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[ - '/gitlab-org/gitlab-ce/issues/26/discussions.json' - ][0].notes[0]; - - setTimeout(() => { - expect( - wrapper - .find('.main-notes-list .note-header-author-name') - .text() - .trim(), - ).toEqual(note.author.name); - - expect(wrapper.find('.main-notes-list .note-text').html()).toContain(note.note_html); - done(); - }, 0); - }); - - it('should render form', () => { - expect(wrapper.find('.js-main-target-form').name()).toEqual('form'); - expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual( - 'Write a comment or drag your files here…', - ); - }); - - it('should not render form when commenting is disabled', () => { - store.state.commentsDisabled = true; - wrapper = mountComponent(); - - expect(wrapper.find('.js-main-target-form').exists()).toBe(false); - }); - - it('should render discussion filter note `commentsDisabled` is true', () => { - store.state.commentsDisabled = true; - wrapper = mountComponent(); - - expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true); - }); - - it('should render form comment button as disabled', () => { - expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled'); - }); - - it('updates discussions badge', done => { - setTimeout(() => { - expect(document.querySelector('.js-discussions-count').textContent).toEqual('2'); - - done(); - }); - }); - }); - - describe('while fetching data', () => { - beforeEach(() => { - wrapper = mountComponent(); - }); - - it('renders skeleton notes', () => { - expect(wrapper.find('.animation-container').exists()).toBe(true); - }); - - it('should render form', () => { - expect(wrapper.find('.js-main-target-form').name()).toEqual('form'); - expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual( - 'Write a comment or drag your files here…', - ); - }); - }); - - describe('update note', () => { - describe('individual note', () => { - beforeEach(done => { - Vue.http.interceptors.push(mockData.individualNoteInterceptor); - spyOn(service, 'updateNote').and.callThrough(); - wrapper = mountComponent(); - setTimeout(() => { - wrapper.find('.js-note-edit').trigger('click'); - Vue.nextTick(done); - }, 0); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, - mockData.individualNoteInterceptor, - ); - }); - - it('renders edit form', () => { - expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true); - }); - - it('calls the service to update the note', done => { - wrapper.find('.js-vue-issue-note-form').value = 'this is a note'; - wrapper.find('.js-vue-issue-save').trigger('click'); - - expect(service.updateNote).toHaveBeenCalled(); - // Wait for the requests to finish before destroying - setTimeout(() => { - done(); - }); - }); - }); - - describe('discussion note', () => { - beforeEach(done => { - Vue.http.interceptors.push(mockData.discussionNoteInterceptor); - spyOn(service, 'updateNote').and.callThrough(); - wrapper = mountComponent(); - - setTimeout(() => { - wrapper.find('.js-note-edit').trigger('click'); - Vue.nextTick(done); - }, 0); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, - mockData.discussionNoteInterceptor, - ); - }); - - it('renders edit form', () => { - expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true); - }); - - it('updates the note and resets the edit form', done => { - wrapper.find('.js-vue-issue-note-form').value = 'this is a note'; - wrapper.find('.js-vue-issue-save').trigger('click'); - - expect(service.updateNote).toHaveBeenCalled(); - // Wait for the requests to finish before destroying - setTimeout(() => { - done(); - }); - }); - }); - }); - - describe('new note form', () => { - beforeEach(() => { - wrapper = mountComponent(); - }); - - it('should render markdown docs url', () => { - const { markdownDocsPath } = mockData.notesDataMock; - - expect( - wrapper - .find(`a[href="${markdownDocsPath}"]`) - .text() - .trim(), - ).toEqual('Markdown'); - }); - - it('should render quick action docs url', () => { - const { quickActionsDocsPath } = mockData.notesDataMock; - - expect( - wrapper - .find(`a[href="${quickActionsDocsPath}"]`) - .text() - .trim(), - ).toEqual('quick actions'); - }); - }); - - describe('edit form', () => { - beforeEach(() => { - Vue.http.interceptors.push(mockData.individualNoteInterceptor); - wrapper = mountComponent(); - }); - - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor); - }); - - it('should render markdown docs url', done => { - setTimeout(() => { - wrapper.find('.js-note-edit').trigger('click'); - const { markdownDocsPath } = mockData.notesDataMock; - - Vue.nextTick(() => { - expect( - wrapper - .find(`.edit-note a[href="${markdownDocsPath}"]`) - .text() - .trim(), - ).toEqual('Markdown is supported'); - done(); - }); - }, 0); - }); - - it('should not render quick actions docs url', done => { - setTimeout(() => { - wrapper.find('.js-note-edit').trigger('click'); - const { quickActionsDocsPath } = mockData.notesDataMock; - - Vue.nextTick(() => { - expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false); - done(); - }); - }, 0); - }); - }); - - describe('emoji awards', () => { - it('dispatches toggleAward after toggleAward event', () => { - const toggleAwardEvent = new CustomEvent('toggleAward', { - detail: { - awardName: 'test', - noteId: 1, - }, - }); - const toggleAwardAction = jasmine.createSpy('toggleAward'); - wrapper.vm.$store.hotUpdate({ - actions: { - toggleAward: toggleAwardAction, - }, - }); - - wrapper.vm.$parent.$el.dispatchEvent(toggleAwardEvent); - - expect(toggleAwardAction).toHaveBeenCalledTimes(1); - const [, payload] = toggleAwardAction.calls.argsFor(0); - - expect(payload).toEqual({ - awardName: 'test', - noteId: 1, - }); - }); - }); -}); -- cgit v1.2.1 From 4504959aa6ce6667b9fd5b68ff24a612cb7c027b Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 3 Jun 2019 16:41:05 +0200 Subject: Make `resolvable_types` a class method This turns Notable::RESOLVABLE_TYPES into a `Notable.resolvable_types`. That allows us to override it in EE. --- spec/models/concerns/noteable_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb index ee613b199ad..e17b98536fa 100644 --- a/spec/models/concerns/noteable_spec.rb +++ b/spec/models/concerns/noteable_spec.rb @@ -260,4 +260,16 @@ describe Noteable do end end end + + describe '.replyable_types' do + it 'exposes the replyable types' do + expect(described_class.replyable_types).to include('Issue', 'MergeRequest') + end + end + + describe '.resolvable_types' do + it 'exposes the replyable types' do + expect(described_class.resolvable_types).to include('MergeRequest') + end + end end -- cgit v1.2.1 From 82e952a8f3fa5dd58eb7113caca6216fab5bab60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cunha?= Date: Mon, 3 Jun 2019 15:42:06 +0100 Subject: Move file one folder level up to avoid namespace conflict Clusters::Cluster::KnativeServicesFinder becomes Clusters::KnativeServicesFinder This is to avoid loading race condition between: ``` module Clusters class Cluster < ApplicationRecord ``` and ``` module Clusters class Cluster ``` --- .../cluster/knative_services_finder_spec.rb | 105 --------------------- .../clusters/knative_services_finder_spec.rb | 105 +++++++++++++++++++++ spec/models/clusters/cluster_spec.rb | 2 +- 3 files changed, 106 insertions(+), 106 deletions(-) delete mode 100644 spec/finders/clusters/cluster/knative_services_finder_spec.rb create mode 100644 spec/finders/clusters/knative_services_finder_spec.rb (limited to 'spec') diff --git a/spec/finders/clusters/cluster/knative_services_finder_spec.rb b/spec/finders/clusters/cluster/knative_services_finder_spec.rb deleted file mode 100644 index 277200d06f4..00000000000 --- a/spec/finders/clusters/cluster/knative_services_finder_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Clusters::Cluster::KnativeServicesFinder do - include KubernetesHelpers - include ReactiveCachingHelpers - - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:service) { cluster.platform_kubernetes } - let(:project) { cluster.cluster_project.project } - let(:namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster, - cluster_project: cluster.cluster_project, - project: project) - end - - before do - stub_kubeclient_knative_services(namespace: namespace.namespace) - stub_kubeclient_service_pods( - kube_response( - kube_knative_pods_body( - project.name, namespace.namespace - ) - ), - namespace: namespace.namespace - ) - end - - shared_examples 'a cached data' do - it 'has an unintialized cache' do - is_expected.to be_blank - end - - context 'when using synchronous reactive cache' do - before do - synchronous_reactive_cache(cluster.knative_services_finder(project)) - end - - context 'when there are functions for cluster namespace' do - it { is_expected.not_to be_blank } - end - - context 'when there are no functions for cluster namespace' do - before do - stub_kubeclient_knative_services( - namespace: namespace.namespace, - response: kube_response({ "kind" => "ServiceList", "items" => [] }) - ) - stub_kubeclient_service_pods( - kube_response({ "kind" => "PodList", "items" => [] }), - namespace: namespace.namespace - ) - end - - it { is_expected.to be_blank } - end - end - end - - describe '#service_pod_details' do - subject { cluster.knative_services_finder(project).service_pod_details(project.name) } - - it_behaves_like 'a cached data' - end - - describe '#services' do - subject { cluster.knative_services_finder(project).services } - - it_behaves_like 'a cached data' - end - - describe '#knative_detected' do - subject { cluster.knative_services_finder(project).knative_detected } - before do - synchronous_reactive_cache(cluster.knative_services_finder(project)) - end - - context 'when knative is installed' do - before do - stub_kubeclient_discover(service.api_url) - end - - it { is_expected.to be_truthy } - it "discovers knative installation" do - expect { subject } - .to change { cluster.kubeclient.knative_client.discovered } - .from(false) - .to(true) - end - end - - context 'when knative is not installed' do - before do - stub_kubeclient_discover_knative_not_found(service.api_url) - end - - it { is_expected.to be_falsy } - it "does not discover knative installation" do - expect { subject }.not_to change { cluster.kubeclient.knative_client.discovered } - end - end - end -end diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb new file mode 100644 index 00000000000..b731c2bd6bf --- /dev/null +++ b/spec/finders/clusters/knative_services_finder_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::KnativeServicesFinder do + include KubernetesHelpers + include ReactiveCachingHelpers + + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:service) { cluster.platform_kubernetes } + let(:project) { cluster.cluster_project.project } + let(:namespace) do + create(:cluster_kubernetes_namespace, + cluster: cluster, + cluster_project: cluster.cluster_project, + project: project) + end + + before do + stub_kubeclient_knative_services(namespace: namespace.namespace) + stub_kubeclient_service_pods( + kube_response( + kube_knative_pods_body( + project.name, namespace.namespace + ) + ), + namespace: namespace.namespace + ) + end + + shared_examples 'a cached data' do + it 'has an unintialized cache' do + is_expected.to be_blank + end + + context 'when using synchronous reactive cache' do + before do + synchronous_reactive_cache(cluster.knative_services_finder(project)) + end + + context 'when there are functions for cluster namespace' do + it { is_expected.not_to be_blank } + end + + context 'when there are no functions for cluster namespace' do + before do + stub_kubeclient_knative_services( + namespace: namespace.namespace, + response: kube_response({ "kind" => "ServiceList", "items" => [] }) + ) + stub_kubeclient_service_pods( + kube_response({ "kind" => "PodList", "items" => [] }), + namespace: namespace.namespace + ) + end + + it { is_expected.to be_blank } + end + end + end + + describe '#service_pod_details' do + subject { cluster.knative_services_finder(project).service_pod_details(project.name) } + + it_behaves_like 'a cached data' + end + + describe '#services' do + subject { cluster.knative_services_finder(project).services } + + it_behaves_like 'a cached data' + end + + describe '#knative_detected' do + subject { cluster.knative_services_finder(project).knative_detected } + before do + synchronous_reactive_cache(cluster.knative_services_finder(project)) + end + + context 'when knative is installed' do + before do + stub_kubeclient_discover(service.api_url) + end + + it { is_expected.to be_truthy } + it "discovers knative installation" do + expect { subject } + .to change { cluster.kubeclient.knative_client.discovered } + .from(false) + .to(true) + end + end + + context 'when knative is not installed' do + before do + stub_kubeclient_discover_knative_not_found(service.api_url) + end + + it { is_expected.to be_falsy } + it "does not discover knative installation" do + expect { subject }.not_to change { cluster.kubeclient.knative_client.discovered } + end + end + end +end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 60a19ccd48a..f206bb41f45 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -40,7 +40,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do it do expect(subject.knative_services_finder(subject.project)) - .to be_instance_of(Clusters::Cluster::KnativeServicesFinder) + .to be_instance_of(Clusters::KnativeServicesFinder) end describe '.enabled' do -- cgit v1.2.1 From 4bf3f54607d412509e7f9415a78fcf6279836c05 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Mon, 3 Jun 2019 15:06:03 +0000 Subject: Cleans up UpdateProjectStatistics concern - Renames attributes from stat to project_statistiscs_name and attribute to statistic_attribute - Reordes methods on UpdateProjectStatistics concern - Removes unused module from Ci::Build --- spec/models/ci/job_artifact_spec.rb | 8 ++++---- .../models/update_project_statistics_shared_examples.rb | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index e6d682c24d9..1ba66565e03 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -5,10 +5,6 @@ require 'spec_helper' describe Ci::JobArtifact do let(:artifact) { create(:ci_job_artifact, :archive) } - it_behaves_like 'UpdateProjectStatistics' do - subject { build(:ci_job_artifact, :archive, size: 106365) } - end - describe "Associations" do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:job) } @@ -23,6 +19,10 @@ describe Ci::JobArtifact do it_behaves_like 'having unique enum values' + it_behaves_like 'UpdateProjectStatistics' do + subject { build(:ci_job_artifact, :archive, size: 106365) } + end + describe '.with_reports' do let!(:artifact) { create(:ci_job_artifact, :archive) } diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb index 7a04e940ee5..1b09c3dd636 100644 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -3,16 +3,16 @@ require 'spec_helper' shared_examples_for 'UpdateProjectStatistics' do - let(:project) { subject.project } - let(:stat) { described_class.statistic_name } - let(:attribute) { described_class.statistic_attribute } + let(:project) { subject.project } + let(:project_statistics_name) { described_class.project_statistics_name } + let(:statistic_attribute) { described_class.statistic_attribute } def reload_stat - project.statistics.reload.send(stat).to_i + project.statistics.reload.send(project_statistics_name).to_i end def read_attribute - subject.read_attribute(attribute).to_i + subject.read_attribute(statistic_attribute).to_i end it { is_expected.to be_new_record } @@ -39,7 +39,8 @@ shared_examples_for 'UpdateProjectStatistics' do .to receive(:increment_statistic) .and_call_original - subject.write_attribute(attribute, read_attribute + delta) + subject.write_attribute(statistic_attribute, read_attribute + delta) + expect { subject.save! } .to change { reload_stat } .by(delta) -- cgit v1.2.1 From ed503d51a39943b482e917028d589cc26ec01c95 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 3 Jun 2019 19:38:16 +0200 Subject: Expose IDs in GraphQL as a GlobalID This exposes all fields named `id` as GlobalIDs so they can be used across our entire GraphQL implementation. When the objects loaded are `ApplicationRecord`s. We'll use our existing batchloading to find them. Otherwise, we'll fall back to the default implementation of `GlobalID`: Calling the `.find` method on the class. --- spec/graphql/features/authorization_spec.rb | 2 +- spec/graphql/gitlab_schema_spec.rb | 58 ++++++++++++++++++++++ spec/requests/api/graphql/gitlab_schema_spec.rb | 13 ++++- spec/requests/api/graphql/group_query_spec.rb | 2 +- .../api/graphql/namespace/projects_spec.rb | 2 +- 5 files changed, 73 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb index f5eb628a982..c427893f9cc 100644 --- a/spec/graphql/features/authorization_spec.rb +++ b/spec/graphql/features/authorization_spec.rb @@ -282,7 +282,7 @@ describe 'Gitlab::Graphql::Authorization' do issue_ids = issue_edges.map { |issue_edge| issue_edge['node']&.fetch('id') } expect(issue_edges.size).to eq(visible_issues.size) - expect(issue_ids).to eq(visible_issues.map { |i| i.id.to_s }) + expect(issue_ids).to eq(visible_issues.map { |i| i.to_global_id.to_s }) end it 'does not check access on fields that will not be rendered' do diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index e9149f4250f..4076c1f824b 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -107,6 +107,64 @@ describe GitlabSchema do end end + describe '.id_from_object' do + it 'returns a global id' do + expect(described_class.id_from_object(build(:project, id: 1))).to be_a(GlobalID) + end + + it "raises a meaningful error if a global id couldn't be generated" do + expect { described_class.id_from_object(build(:commit)) } + .to raise_error(RuntimeError, /include `GlobalID::Identification` into/i) + end + end + + describe '.object_from_id' do + context 'for subclasses of `ApplicationRecord`' do + it 'returns the correct record' do + user = create(:user) + + result = described_class.object_from_id(user.to_global_id.to_s) + + expect(result.__sync).to eq(user) + end + + it 'batchloads the queries' do + user1 = create(:user) + user2 = create(:user) + + expect do + [described_class.object_from_id(user1.to_global_id), + described_class.object_from_id(user2.to_global_id)].map(&:__sync) + end.not_to exceed_query_limit(1) + end + end + + context 'for other classes' do + # We cannot use an anonymous class here as `GlobalID` expects `.name` not + # to return `nil` + class TestGlobalId + include GlobalID::Identification + attr_accessor :id + + def initialize(id) + @id = id + end + end + + it 'falls back to a regular find' do + result = TestGlobalId.new(123) + + expect(TestGlobalId).to receive(:find).with("123").and_return(result) + + expect(described_class.object_from_id(result.to_global_id)).to eq(result) + end + end + + it 'raises the correct error on invalid input' do + expect { described_class.object_from_id("bogus id") }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + end + def field_instrumenters described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins] end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index b6ca9246399..28676bb02f4 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'GitlabSchema configurations' do include GraphqlHelpers - let(:project) { create(:project) } + set(:project) { create(:project) } shared_examples 'imposing query limits' do describe '#max_complexity' do @@ -136,4 +136,15 @@ describe 'GitlabSchema configurations' do post_graphql(query, current_user: nil) end end + + context "global id's" do + it 'uses GlobalID to expose ids' do + post_graphql(graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id)), + current_user: project.owner) + + parsed_id = GlobalID.parse(graphql_data['project']['id']) + + expect(parsed_id).to eq(project.to_global_id) + end + end end diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb index db9f2ac9dd0..e0f1e4dbe9e 100644 --- a/spec/requests/api/graphql/group_query_spec.rb +++ b/spec/requests/api/graphql/group_query_spec.rb @@ -56,7 +56,7 @@ describe 'getting group information' do post_graphql(group_query(group1), current_user: user1) expect(response).to have_gitlab_http_status(200) - expect(graphql_data['group']['id']).to eq(group1.id.to_s) + expect(graphql_data['group']['id']).to eq(group1.to_global_id.to_s) expect(graphql_data['group']['name']).to eq(group1.name) expect(graphql_data['group']['path']).to eq(group1.path) expect(graphql_data['group']['description']).to eq(group1.description) diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb index e05273da4bd..de1cd9586b6 100644 --- a/spec/requests/api/graphql/namespace/projects_spec.rb +++ b/spec/requests/api/graphql/namespace/projects_spec.rb @@ -60,7 +60,7 @@ describe 'getting projects', :nested_groups do expect(graphql_data['namespace']['projects']['edges'].size).to eq(1) project = graphql_data['namespace']['projects']['edges'][0]['node'] - expect(project['id']).to eq(public_project.id.to_s) + expect(project['id']).to eq(public_project.to_global_id.to_s) end end end -- cgit v1.2.1 From f16b13113ff580fbde78f8f6ba42a3f86c04ca12 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 3 Jun 2019 20:15:04 +0200 Subject: Fix incorrect instances of `GraphQL::ID_TYPE` Since the `GraphQL::ID_TYPE` usages should represent globally unique ids, this changes some fields for which this is not the case into strings. The `ID_TYPE` is a specialised, so this change should be backwards compatible. https://graphql-ruby.org/type_definitions/scalars.html --- spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb index 8f427d71a32..d75f0df9fd3 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb @@ -11,7 +11,7 @@ describe 'Setting WIP status of a merge request' do let(:mutation) do variables = { project_path: project.full_path, - iid: merge_request.iid + iid: merge_request.iid.to_s } graphql_mutation(:merge_request_set_wip, variables.merge(input)) end -- cgit v1.2.1 From e0594255b52a565ca3ad5edb789d88777727684f Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 3 Jun 2019 21:58:07 +0000 Subject: Move boardsStore.moving to BoardList component --- spec/javascripts/boards/boards_store_spec.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index d3e6eb78e5a..9b125593869 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -287,4 +287,16 @@ describe('Store', () => { expect(boardsStore.detail.issue).toEqual({}); }); }); + + describe('startMoving', () => { + it('stores list and issue', () => { + const dummyIssue = 'some issue'; + const dummyList = 'some list'; + + boardsStore.startMoving(dummyList, dummyIssue); + + expect(boardsStore.moving.issue).toEqual(dummyIssue); + expect(boardsStore.moving.list).toEqual(dummyList); + }); + }); }); -- cgit v1.2.1 From 6610f2fdcb4b641e9c1c87891e9de1ca28c1f518 Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Fri, 31 May 2019 09:58:23 -0300 Subject: Add auto SSL toggle to Pages domain settings page This commit adds an auto SSL toggle switch to the settings page of GitLab Pages domains. This toggle enable or disabled auto SSL management via Let's Encrypt. Toggling the button dynamically updates the form using client-side JavaScript. All changes are behind feature flags. --- spec/features/projects/pages_lets_encrypt_spec.rb | 131 ++++++++++++++++++++++ spec/features/projects/pages_spec.rb | 21 +++- 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 spec/features/projects/pages_lets_encrypt_spec.rb (limited to 'spec') diff --git a/spec/features/projects/pages_lets_encrypt_spec.rb b/spec/features/projects/pages_lets_encrypt_spec.rb new file mode 100644 index 00000000000..baa217cbe58 --- /dev/null +++ b/spec/features/projects/pages_lets_encrypt_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe "Pages with Let's Encrypt", :https_pages_enabled do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:role) { :maintainer } + let(:certificate_pem) do + <<~PEM + -----BEGIN CERTIFICATE----- + MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0 + LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ + MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw + gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa + SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT + nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w + DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD + VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh + IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ + joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese + 5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg + YHi2yesCrOvVXt+lgPTd + -----END CERTIFICATE----- + PEM + end + + let(:certificate_key) do + <<~KEY + -----BEGIN PRIVATE KEY----- + MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN + SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t + PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB + kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd + j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/ + uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR + 5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O + AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K + EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh + Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C + m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH + EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx + 63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi + nNp/xedE1YxutQ== + -----END PRIVATE KEY----- + KEY + end + + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + project.add_role(user, role) + sign_in(user) + project.namespace.update(owner: user) + allow_any_instance_of(Project).to receive(:pages_deployed?) { true } + end + + context 'when the page_auto_ssl feature flag is enabled' do + before do + stub_feature_flags(pages_auto_ssl: true) + end + + context 'when the auto SSL management is initially disabled' do + let(:domain) do + create(:pages_domain, auto_ssl_enabled: false, project: project) + end + + it 'enables auto SSL and dynamically updates the form accordingly', :js do + visit edit_project_pages_domain_path(project, domain) + + expect(domain.auto_ssl_enabled).to eq false + + expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false' + expect(page).to have_field 'Certificate (PEM)', type: 'textarea' + expect(page).to have_field 'Key (PEM)', type: 'textarea' + + find('.js-auto-ssl-toggle-container .project-feature-toggle').click + + expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true' + expect(page).not_to have_field 'Certificate (PEM)', type: 'textarea' + expect(page).not_to have_field 'Key (PEM)', type: 'textarea' + expect(page).to have_content "The certificate will be shown here once it has been obtained from Let's Encrypt. This process may take up to an hour to complete." + + click_on 'Save Changes' + + expect(domain.reload.auto_ssl_enabled).to eq true + end + end + + context 'when the auto SSL management is initially enabled' do + let(:domain) do + create(:pages_domain, auto_ssl_enabled: true, project: project) + end + + it 'disables auto SSL and dynamically updates the form accordingly', :js do + visit edit_project_pages_domain_path(project, domain) + + expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true' + expect(page).to have_field 'Certificate (PEM)', type: 'textarea', disabled: true + expect(page).not_to have_field 'Key (PEM)', type: 'textarea' + + find('.js-auto-ssl-toggle-container .project-feature-toggle').click + + expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false' + expect(page).to have_field 'Certificate (PEM)', type: 'textarea' + expect(page).to have_field 'Key (PEM)', type: 'textarea' + + fill_in 'Certificate (PEM)', with: certificate_pem + fill_in 'Key (PEM)', with: certificate_key + + click_on 'Save Changes' + + expect(domain.reload.auto_ssl_enabled).to eq false + end + end + end + + context 'when the page_auto_ssl feature flag is disabled' do + let(:domain) do + create(:pages_domain, auto_ssl_enabled: false, project: project) + end + + before do + stub_feature_flags(pages_auto_ssl: false) + + visit edit_project_pages_domain_path(project, domain) + end + + it "does not render the Let's Encrypt field", :js do + expect(page).not_to have_selector '.js-auto-ssl-toggle-container' + end + end +end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index be05c74efdb..9bb0ba81ef5 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -1,6 +1,7 @@ +# frozen_string_literal: true require 'spec_helper' -describe 'Pages' do +shared_examples 'pages domain editing' do let(:project) { create(:project) } let(:user) { create(:user) } let(:role) { :maintainer } @@ -318,3 +319,21 @@ describe 'Pages' do end end end + +describe 'Pages' do + context 'when pages_auto_ssl feature flag is disabled' do + before do + stub_feature_flags(pages_auto_ssl: false) + end + + include_examples 'pages domain editing' + end + + context 'when pages_auto_ssl feature flag is enabled' do + before do + stub_feature_flags(pages_auto_ssl: true) + end + + include_examples 'pages domain editing' + end +end -- cgit v1.2.1 From cfaf012c532a269a230f67b31e623b18e3f8f8b1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 3 Jun 2019 15:04:59 -0700 Subject: Fix project settings not being able to update Previously import_url would always be present in the update parameters, which would cause the validation to fail. We now only include this parameter only if there is URL given. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/62708 --- spec/controllers/concerns/import_url_params_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec') diff --git a/spec/controllers/concerns/import_url_params_spec.rb b/spec/controllers/concerns/import_url_params_spec.rb index fc5dfb5263f..adbe6e5d3bf 100644 --- a/spec/controllers/concerns/import_url_params_spec.rb +++ b/spec/controllers/concerns/import_url_params_spec.rb @@ -8,6 +8,18 @@ describe ImportUrlParams do controller.import_url_params end + context 'empty URL' do + let(:params) do + ActionController::Parameters.new(project: { + title: 'Test' + }) + end + + it 'returns empty hash' do + expect(import_url_params).to eq({}) + end + end + context 'url and password separately provided' do let(:params) do ActionController::Parameters.new(project: { -- cgit v1.2.1 From 111d47f3fb45a119b0ffa9fe3422b4a2821eb0bc Mon Sep 17 00:00:00 2001 From: rossfuhrman Date: Mon, 3 Jun 2019 22:42:39 +0000 Subject: Backporting EE fix --- .../security-reports/master/gl-dast-report.json | 74 +++++++++++----------- 1 file changed, 38 insertions(+), 36 deletions(-) (limited to 'spec') diff --git a/spec/fixtures/security-reports/master/gl-dast-report.json b/spec/fixtures/security-reports/master/gl-dast-report.json index 3a308bf047e..df459d9419d 100644 --- a/spec/fixtures/security-reports/master/gl-dast-report.json +++ b/spec/fixtures/security-reports/master/gl-dast-report.json @@ -1,40 +1,42 @@ { - "site": { - "alerts": [ - { - "sourceid": "3", - "wascid": "15", - "cweid": "16", - "reference": "

      http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx

      https://www.owasp.org/index.php/List_of_useful_HTTP_headers

      ", - "otherinfo": "

      This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.

      At \"High\" threshold this scanner will not alert on client or server error responses.

      ", - "solution": "

      Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.

      If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.

      ", - "count": "2", - "pluginid": "10021", - "alert": "X-Content-Type-Options Header Missing", - "name": "X-Content-Type-Options Header Missing", - "riskcode": "1", - "confidence": "2", - "riskdesc": "Low (Medium)", - "desc": "

      The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.

      ", - "instances": [ - { - "param": "X-Content-Type-Options", - "method": "GET", - "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" - }, - { - "param": "X-Content-Type-Options", - "method": "GET", - "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/" - } - ] - } - ], - "@ssl": "false", - "@port": "80", - "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io", - "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" - }, + "site": [ + { + "alerts": [ + { + "sourceid": "3", + "wascid": "15", + "cweid": "16", + "reference": "

      http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx

      https://www.owasp.org/index.php/List_of_useful_HTTP_headers

      ", + "otherinfo": "

      This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.

      At \"High\" threshold this scanner will not alert on client or server error responses.

      ", + "solution": "

      Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.

      If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.

      ", + "count": "2", + "pluginid": "10021", + "alert": "X-Content-Type-Options Header Missing", + "name": "X-Content-Type-Options Header Missing", + "riskcode": "1", + "confidence": "2", + "riskdesc": "Low (Medium)", + "desc": "

      The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.

      ", + "instances": [ + { + "param": "X-Content-Type-Options", + "method": "GET", + "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" + }, + { + "param": "X-Content-Type-Options", + "method": "GET", + "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/" + } + ] + } + ], + "@ssl": "false", + "@port": "80", + "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io", + "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" + } + ], "@generated": "Fri, 13 Apr 2018 09:22:01", "@version": "2.7.0" } -- cgit v1.2.1 From bee3c7e847e4c682f3b808ebfddcc0b6746c2d60 Mon Sep 17 00:00:00 2001 From: Nathan Friend Date: Mon, 3 Jun 2019 22:51:02 +0000 Subject: Comply with `no-implicit-coercion` rule (CE) This commit is the result of running `yarn eslint --fix` after enabling the `no-implicit-coercion` ESLint rule. This rule has been added to our ESLint config here: https://gitlab.com/gitlab-org/gitlab-eslint-config/merge_requests/14 --- spec/frontend/helpers/vue_test_utils_helper.js | 4 +++- spec/javascripts/helpers/vue_test_utils_helper.js | 4 +++- spec/javascripts/matchers.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js index 19e27388eeb..121e99c9783 100644 --- a/spec/frontend/helpers/vue_test_utils_helper.js +++ b/spec/frontend/helpers/vue_test_utils_helper.js @@ -16,4 +16,6 @@ const vNodeContainsText = (vnode, text) => * @param {String} text */ export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) => - !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length; + Boolean( + shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length, + ); diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js index 19e27388eeb..121e99c9783 100644 --- a/spec/javascripts/helpers/vue_test_utils_helper.js +++ b/spec/javascripts/helpers/vue_test_utils_helper.js @@ -16,4 +16,6 @@ const vNodeContainsText = (vnode, text) => * @param {String} text */ export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) => - !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length; + Boolean( + shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length, + ); diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js index 406527b08a3..7d1921cabcf 100644 --- a/spec/javascripts/matchers.js +++ b/spec/javascripts/matchers.js @@ -28,7 +28,7 @@ export default { reference.getAttribute('xlink:href').endsWith(`#${iconName}`), ); const result = { - pass: !!matchingIcon, + pass: Boolean(matchingIcon), }; if (result.pass) { -- cgit v1.2.1 From 9b91e6816ce6b7544856fd2b52049836b5e2d250 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 27 May 2019 19:30:31 +0700 Subject: Cancel auto merge when merge request is closed We should cancel auto merge when merge request is closed. --- spec/services/merge_requests/close_service_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index ffa612cf315..29b7e0f17e2 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -52,6 +52,14 @@ describe MergeRequests::CloseService do it 'marks todos as done' do expect(todo.reload).to be_done end + + context 'when auto merge is enabled' do + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it 'cancels the auto merge' do + expect(@merge_request).not_to be_auto_merge_enabled + end + end end it 'updates metrics' do -- cgit v1.2.1 From 197a3d053359e66535c41935eac065ee424cbb07 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 27 May 2019 19:21:36 +0700 Subject: Introduce sidekiq worker for auto merge process As we have a central domain for auto merge process today, we should use a single worker for any auto merge process. --- spec/models/ci/pipeline_spec.rb | 34 ++++++++++++++++++++++++++ spec/workers/auto_merge_process_worker_spec.rb | 31 +++++++++++++++++++++++ spec/workers/pipeline_success_worker_spec.rb | 27 -------------------- 3 files changed, 65 insertions(+), 27 deletions(-) create mode 100644 spec/workers/auto_merge_process_worker_spec.rb delete mode 100644 spec/workers/pipeline_success_worker_spec.rb (limited to 'spec') diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index a0319b3eb0a..a8701f0efa4 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1381,6 +1381,40 @@ describe Ci::Pipeline, :mailer do end end + describe 'auto merge' do + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + let(:pipeline) do + create(:ci_pipeline, :running, project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha) + end + + before do + merge_request.update_head_pipeline + end + + %w[succeed! drop! cancel! skip!].each do |action| + context "when the pipeline recieved #{action} event" do + it 'performs AutoMergeProcessWorker' do + expect(AutoMergeProcessWorker).to receive(:perform_async).with(merge_request.id) + + pipeline.public_send(action) + end + end + end + + context 'when auto merge is not enabled in the merge request' do + let(:merge_request) { create(:merge_request) } + + it 'performs AutoMergeProcessWorker' do + expect(AutoMergeProcessWorker).not_to receive(:perform_async) + + pipeline.succeed! + end + end + end + def create_build(name, *traits, queued_at: current, started_from: 0, **opts) create(:ci_build, *traits, name: name, diff --git a/spec/workers/auto_merge_process_worker_spec.rb b/spec/workers/auto_merge_process_worker_spec.rb new file mode 100644 index 00000000000..616727ce5ca --- /dev/null +++ b/spec/workers/auto_merge_process_worker_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AutoMergeProcessWorker do + describe '#perform' do + subject { described_class.new.perform(merge_request&.id) } + + context 'when merge request is found' do + let(:merge_request) { create(:merge_request) } + + it 'executes AutoMergeService' do + expect_next_instance_of(AutoMergeService) do |auto_merge| + expect(auto_merge).to receive(:process) + end + + subject + end + end + + context 'when merge request is not found' do + let(:merge_request) { nil } + + it 'does not execute AutoMergeService' do + expect(AutoMergeService).not_to receive(:new) + + subject + end + end + end +end diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb deleted file mode 100644 index b511edfa620..00000000000 --- a/spec/workers/pipeline_success_worker_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe PipelineSuccessWorker do - describe '#perform' do - context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline, status: 'success', ref: merge_request.source_branch, project: merge_request.source_project) } - let(:merge_request) { create(:merge_request) } - - it 'performs "merge when pipeline succeeds"' do - expect_next_instance_of(AutoMergeService) do |auto_merge| - expect(auto_merge).to receive(:process) - end - - described_class.new.perform(pipeline.id) - end - end - - context 'when pipeline does not exist' do - it 'does not raise exception' do - expect { described_class.new.perform(123) } - .not_to raise_error - end - end - end -end -- cgit v1.2.1 From a23ea78efe4156ae9f7a77eff3b1b1bb027dcda4 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Tue, 4 Jun 2019 06:09:06 +0000 Subject: Add expand/collapse button Add ability to expand/collapse operation settings --- .../operation_settings/components/external_dashboard_spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js index de1dd219fe0..23dc3c3db8c 100644 --- a/spec/frontend/operation_settings/components/external_dashboard_spec.js +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -21,6 +21,14 @@ describe('operation settings external dashboard component', () => { expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard'); }); + describe('expand/collapse button', () => { + it('renders as an expand button by default', () => { + const button = wrapper.find(GlButton); + + expect(button.text()).toBe('Expand'); + }); + }); + describe('sub-header', () => { let subHeader; -- cgit v1.2.1 From cc4b9a377a4150265d9efc869601e9675e82ec11 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Jun 2019 08:34:35 +0100 Subject: Update rendering of merge request in merge request widget Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/40246 --- .../merge_request/user_merges_when_pipeline_succeeds_spec.rb | 8 ++++---- spec/features/merge_request/user_sees_merge_widget_spec.rb | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index d4ad11b3585..16662afb6e7 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -158,8 +158,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do # Wait for the `ci_status` and `merge_check` requests wait_for_requests - page.within('.mr-widget-body') do - expect(page).to have_content('Something went wrong') + page.within('.mr-section-container') do + expect(page).to have_content('Merge failed: Something went wrong') end end end @@ -177,8 +177,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do # Wait for the `ci_status` and `merge_check` requests wait_for_requests - page.within('.mr-widget-body') do - expect(page).to have_content('Something went wrong') + page.within('.mr-section-container') do + expect(page).to have_content('Merge failed: Something went wrong') 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 93ddde623fe..6f802c3d072 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -326,8 +326,8 @@ describe 'Merge request > User sees merge widget', :js do # Wait for the `ci_status` and `merge_check` requests wait_for_requests - page.within('.mr-widget-body') do - expect(page).to have_content('Something went wrong') + page.within('.mr-section-container') do + expect(page).to have_content('Merge failed: Something went wrong') end end end @@ -347,8 +347,8 @@ describe 'Merge request > User sees merge widget', :js do # Wait for the `ci_status` and `merge_check` requests wait_for_requests - page.within('.mr-widget-body') do - expect(page).to have_content('Something went wrong') + page.within('.mr-section-container') do + expect(page).to have_content('Merge failed: Something went wrong') end end end -- cgit v1.2.1 From c1bb78a0ce232685fb18e13bf757b615aee0b75b Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 30 May 2019 17:39:42 -0500 Subject: FE: Remove legacy approval feature in MR files --- spec/javascripts/vue_mr_widget/mr_widget_options_spec.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 7653c10b94b..918717c4547 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -21,7 +21,6 @@ describe('mrWidgetOptions', () => { const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch'; beforeEach(() => { - gon.features = { approvalRules: false }; // Prevent component mounting delete mrWidgetOptions.el; @@ -32,7 +31,6 @@ describe('mrWidgetOptions', () => { }); afterEach(() => { - gon.features = null; vm.$destroy(); }); -- cgit v1.2.1 From eabc7289f600882d9ecc7008c4d11c186eb8cd79 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 3 Jun 2019 20:40:05 +0800 Subject: Remove spec feature stubbing --- spec/features/merge_request/user_creates_merge_request_spec.rb | 2 -- spec/features/projects/settings/forked_project_settings_spec.rb | 1 - spec/features/projects_spec.rb | 1 - 3 files changed, 4 deletions(-) (limited to 'spec') diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb index bcc11217389..d05ef2a8f12 100644 --- a/spec/features/merge_request/user_creates_merge_request_spec.rb +++ b/spec/features/merge_request/user_creates_merge_request_spec.rb @@ -8,8 +8,6 @@ describe "User creates a merge request", :js do let(:user) { create(:user) } before do - stub_feature_flags(approval_rules: false) - project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb index dc0278370aa..df33d215602 100644 --- a/spec/features/projects/settings/forked_project_settings_spec.rb +++ b/spec/features/projects/settings/forked_project_settings_spec.rb @@ -7,7 +7,6 @@ describe 'Projects > Settings > For a forked project', :js do let(:forked_project) { fork_project(original_project, user) } before do - stub_feature_flags(approval_rules: false) original_project.add_maintainer(user) forked_project.add_maintainer(user) sign_in(user) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 4fe45311b2d..27f6ed56283 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -6,7 +6,6 @@ describe 'Project' do before do stub_feature_flags(vue_file_list: false) - stub_feature_flags(approval_rules: false) end describe 'creating from template' do -- cgit v1.2.1 From d4a83ce5a3f8bc6024091e3c75d81ce3fa413a56 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 4 Jun 2019 09:11:55 +0000 Subject: Ignore Puma empty worker stats In some cases (during worker start) it's possible that Puma.stats returns an empty hash for worker's last status. In that case we just skip sampling of the worker until these stats are available. --- .../gitlab/metrics/samplers/puma_sampler_spec.rb | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb index c471c30a194..f4a6e1fc7d9 100644 --- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb @@ -61,6 +61,33 @@ describe Gitlab::Metrics::Samplers::PumaSampler do end end + context 'with empty worker stats' do + let(:puma_stats) do + <<~EOS + { + "workers": 2, + "phase": 2, + "booted_workers": 2, + "old_workers": 0, + "worker_status": [{ + "pid": 32534, + "index": 0, + "phase": 1, + "booted": true, + "last_checkin": "2019-05-15T07:57:55Z", + "last_status": {} + }] + } + EOS + end + + it 'does not log worker stats' do + expect(subject).not_to receive(:set_worker_metrics) + + subject.sample + end + end + context 'in single mode' do let(:puma_stats) do <<~EOS -- cgit v1.2.1 From e72efed323741d0f934b79aae812c1bea57d244b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zaj=C4=85c?= Date: Tue, 4 Jun 2019 09:56:32 +0000 Subject: Change s_() calls to _() calls There are no namespaces in the strings so we don't need those --- spec/helpers/emails_helper_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'spec') diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index 0434af25866..e6aacb5b92b 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -8,19 +8,19 @@ describe EmailsHelper do context "and format is text" do it "returns plain text" do - expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") + expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") end end context "and format is HTML" do it "returns HTML" do - expect(closure_reason_text(merge_request, format: :html)).to eq(" via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}") + expect(closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}") end end context "and format is unknown" do it "returns plain text" do - expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") + expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") end end end @@ -29,7 +29,7 @@ describe EmailsHelper do let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" } it "returns plain text" do - expect(closure_reason_text(closed_via)).to eq(" via #{closed_via}") + expect(closure_reason_text(closed_via)).to eq("via #{closed_via}") end end -- cgit v1.2.1 From 9797b27e1a096f684b870e55aab0b5d1250e73f8 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Mon, 27 May 2019 12:42:58 +0300 Subject: Adjust burndown chart opened issues count * Adjust opened issues count in the milestone burndown chart to match current opened issues in the milestone. * Fix completion rate calculation to include milestones only. --- spec/models/concerns/milestoneish_spec.rb | 78 ++++++++----------------------- spec/models/milestone_spec.rb | 22 --------- 2 files changed, 20 insertions(+), 80 deletions(-) (limited to 'spec') diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 22d4b2cc517..7e9a8306612 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -194,58 +194,6 @@ describe Milestone, 'Milestoneish' do end end - describe '#closed_items_count' do - it 'does not count confidential issues for non project members' do - expect(milestone.closed_items_count(non_member)).to eq 2 - end - - it 'does not count confidential issues for project members with guest role' do - expect(milestone.closed_items_count(guest)).to eq 2 - end - - it 'counts confidential issues for author' do - expect(milestone.closed_items_count(author)).to eq 4 - end - - it 'counts confidential issues for assignee' do - expect(milestone.closed_items_count(assignee)).to eq 4 - end - - it 'counts confidential issues for project members' do - expect(milestone.closed_items_count(member)).to eq 6 - end - - it 'counts all issues for admin' do - expect(milestone.closed_items_count(admin)).to eq 6 - end - end - - describe '#total_items_count' do - it 'does not count confidential issues for non project members' do - expect(milestone.total_items_count(non_member)).to eq 4 - end - - it 'does not count confidential issues for project members with guest role' do - expect(milestone.total_items_count(guest)).to eq 4 - end - - it 'counts confidential issues for author' do - expect(milestone.total_items_count(author)).to eq 7 - end - - it 'counts confidential issues for assignee' do - expect(milestone.total_items_count(assignee)).to eq 7 - end - - it 'counts confidential issues for project members' do - expect(milestone.total_items_count(member)).to eq 10 - end - - it 'counts all issues for admin' do - expect(milestone.total_items_count(admin)).to eq 10 - end - end - describe '#complete?' do it 'returns false when has items opened' do expect(milestone.complete?(non_member)).to eq false @@ -260,28 +208,42 @@ describe Milestone, 'Milestoneish' do end describe '#percent_complete' do + context 'division by zero' do + let(:new_milestone) { build_stubbed(:milestone) } + + it { expect(new_milestone.percent_complete(admin)).to eq(0) } + end + end + + describe '#count_issues_by_state' do it 'does not count confidential issues for non project members' do - expect(milestone.percent_complete(non_member)).to eq 50 + expect(milestone.closed_issues_count(non_member)).to eq 2 + expect(milestone.total_issues_count(non_member)).to eq 3 end it 'does not count confidential issues for project members with guest role' do - expect(milestone.percent_complete(guest)).to eq 50 + expect(milestone.closed_issues_count(guest)).to eq 2 + expect(milestone.total_issues_count(guest)).to eq 3 end it 'counts confidential issues for author' do - expect(milestone.percent_complete(author)).to eq 57 + expect(milestone.closed_issues_count(author)).to eq 4 + expect(milestone.total_issues_count(author)).to eq 6 end it 'counts confidential issues for assignee' do - expect(milestone.percent_complete(assignee)).to eq 57 + expect(milestone.closed_issues_count(assignee)).to eq 4 + expect(milestone.total_issues_count(assignee)).to eq 6 end it 'counts confidential issues for project members' do - expect(milestone.percent_complete(member)).to eq 60 + expect(milestone.closed_issues_count(member)).to eq 6 + expect(milestone.total_issues_count(member)).to eq 9 end it 'counts confidential issues for admin' do - expect(milestone.percent_complete(admin)).to eq 60 + expect(milestone.closed_issues_count(admin)).to eq 6 + expect(milestone.total_issues_count(admin)).to eq 9 end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 752a7965704..e47f2de1ae9 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -182,32 +182,10 @@ describe Milestone do end end - describe '#percent_complete' do - before do - allow(milestone).to receive_messages( - closed_items_count: 3, - total_items_count: 4 - ) - end - - it { expect(milestone.percent_complete(user)).to eq(75) } - end - describe '#can_be_closed?' do it { expect(milestone.can_be_closed?).to be_truthy } end - describe '#total_items_count' do - before do - create :closed_issue, milestone: milestone, project: project - create :merge_request, milestone: milestone, source_project: project - end - - it 'returns total count of issues and merge requests assigned to milestone' do - expect(milestone.total_items_count(user)).to eq 2 - end - end - describe '#can_be_closed?' do before do milestone = create :milestone, project: project -- cgit v1.2.1 From c7d50ddf5504828296c97447b281be17282a056e Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Tue, 4 Jun 2019 23:25:33 +1200 Subject: Fix connection to Tiller error while uninstalling Both the `install-` and `uninstall-` pods loads the `values-content-configuration-` configmap into the pod (see `#volume_specification`). This configmap contains the cert necessary to connect to Tiller. The cert though is only valid for 30 minutes. So this fixes the bug where the configmap when uninstalling should be updated as well. --- spec/lib/gitlab/kubernetes/helm/api_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb index 24ce397ec3d..0de809833e6 100644 --- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb @@ -36,6 +36,8 @@ describe Gitlab::Kubernetes::Helm::Api do describe '#uninstall' do before do allow(client).to receive(:create_pod).and_return(nil) + allow(client).to receive(:get_config_map).and_return(nil) + allow(client).to receive(:create_config_map).and_return(nil) allow(client).to receive(:delete_pod).and_return(nil) allow(namespace).to receive(:ensure_exists!).once end @@ -53,6 +55,28 @@ describe Gitlab::Kubernetes::Helm::Api do subject.uninstall(command) end + + context 'with a ConfigMap' do + let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application_name, files).generate } + + it 'creates a ConfigMap on kubeclient' do + expect(client).to receive(:create_config_map).with(resource).once + + subject.install(command) + end + + context 'config map already exists' do + before do + expect(client).to receive(:get_config_map).with("values-content-configuration-#{application_name}", gitlab_namespace).and_return(resource) + end + + it 'updates the config map' do + expect(client).to receive(:update_config_map).with(resource).once + + subject.install(command) + end + end + end end describe '#install' do -- cgit v1.2.1 From 4e251d01de8014a494049710ac27146a3f049d89 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Tue, 4 Jun 2019 12:11:41 +0000 Subject: Expose wiki_size on GraphQL API --- spec/graphql/types/project_statistics_type_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb index 485e194edb1..e9feac57a36 100644 --- a/spec/graphql/types/project_statistics_type_spec.rb +++ b/spec/graphql/types/project_statistics_type_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe GitlabSchema.types['ProjectStatistics'] do it "has all the required fields" do is_expected.to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size, - :build_artifacts_size, :packages_size, :commit_count) + :build_artifacts_size, :packages_size, :commit_count, + :wiki_size) end end -- cgit v1.2.1 From 598fa4cdd8e698d4ed19ad6101989aa286c9131c Mon Sep 17 00:00:00 2001 From: mfluharty Date: Tue, 7 May 2019 13:37:29 -0600 Subject: Default masked to false for new variables Set the default value to false Adjust tests to expect false as the default Update documentation to make new default clear --- .../features/variable_list_shared_examples.rb | 32 +++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'spec') diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 92a19dd22a2..01531864c1f 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -45,12 +45,12 @@ shared_examples 'variable list' do end end - it 'defaults to masked' do + it 'defaults to unmasked' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('key') find('.js-ci-variable-input-value').set('key_value') - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') end click_button('Save variables') @@ -62,7 +62,7 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') end end @@ -234,12 +234,14 @@ shared_examples 'variable list' do end it 'edits variable to be unmasked' do - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('unmasked_key') + find('.js-ci-variable-input-value').set('unmasked_value') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') find('.ci-variable-masked-item .js-project-feature-toggle').click - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') end click_button('Save variables') @@ -247,12 +249,6 @@ shared_examples 'variable list' do visit page_path - page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do - expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') - end - end - - it 'edits variable to be masked' do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') @@ -268,6 +264,14 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') + end + end + + it 'edits variable to be masked' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('masked_key') + find('.js-ci-variable-input-value').set('masked_value') + expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') find('.ci-variable-masked-item .js-project-feature-toggle').click @@ -348,10 +352,11 @@ shared_examples 'variable list' do end end - it 'shows validation error box about empty values' do + it 'shows validation error box about masking empty values' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('empty_value') find('.js-ci-variable-input-value').set('') + find('.ci-variable-masked-item .js-project-feature-toggle').click end click_button('Save variables') @@ -367,6 +372,7 @@ shared_examples 'variable list' do page.within('.js-ci-variable-list-section .js-row:last-child') do find('.js-ci-variable-input-key').set('unmaskable_value') find('.js-ci-variable-input-value').set('???') + find('.ci-variable-masked-item .js-project-feature-toggle').click end click_button('Save variables') -- cgit v1.2.1 From 867ce78799d850adcd61611e5a3b2ad5df8a5f61 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 4 Jun 2019 14:48:46 +0000 Subject: Extract toggleFilter() into issue boards store --- spec/javascripts/boards/boards_store_spec.js | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 9b125593869..e77768e3597 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -12,6 +12,7 @@ import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/services/board_service'; import boardsStore from '~/boards/stores/boards_store'; +import eventHub from '~/boards/eventhub'; import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('Store', () => { @@ -53,6 +54,39 @@ describe('Store', () => { }); }); + describe('toggleFilter', () => { + const dummyFilter = 'x=42'; + let updateTokensSpy; + + beforeEach(() => { + updateTokensSpy = jasmine.createSpy('updateTokens'); + eventHub.$once('updateTokens', updateTokensSpy); + + // prevent using window.history + spyOn(boardsStore, 'updateFiltersUrl').and.callFake(() => {}); + }); + + it('adds the filter if it is not present', () => { + boardsStore.filter.path = 'something'; + + boardsStore.toggleFilter(dummyFilter); + + expect(boardsStore.filter.path).toEqual(`something&${dummyFilter}`); + expect(updateTokensSpy).toHaveBeenCalled(); + expect(boardsStore.updateFiltersUrl).toHaveBeenCalled(); + }); + + it('removes the filter if it is present', () => { + boardsStore.filter.path = `something&${dummyFilter}`; + + boardsStore.toggleFilter(dummyFilter); + + expect(boardsStore.filter.path).toEqual('something'); + expect(updateTokensSpy).toHaveBeenCalled(); + expect(boardsStore.updateFiltersUrl).toHaveBeenCalled(); + }); + }); + describe('lists', () => { it('creates new list without persisting to DB', () => { boardsStore.addList(listObj); -- cgit v1.2.1 From 0c93e470fd2ff59bf59a13cc012ff33fd7a40704 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 4 Jun 2019 15:34:17 +0000 Subject: Extract setIssueDetail() into issue boards store --- spec/javascripts/boards/boards_store_spec.js | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index e77768e3597..68e66346bfd 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -322,6 +322,17 @@ describe('Store', () => { }); }); + describe('setIssueDetail', () => { + it('sets issue details', () => { + boardsStore.detail.issue = 'some details'; + + const dummyValue = 'new details'; + boardsStore.setIssueDetail(dummyValue); + + expect(boardsStore.detail.issue).toEqual(dummyValue); + }); + }); + describe('startMoving', () => { it('stores list and issue', () => { const dummyIssue = 'some issue'; -- cgit v1.2.1 From e02cd451b2d6a81f4847f26b29ed1eb6c74f8dab Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Thu, 30 May 2019 13:47:32 -0500 Subject: Add single_stat chart component to the monitoring bundle This merge requests just adds the component without integrating it to the dashboard, it adds tests as well --- .../monitoring/charts/single_stat_spec.js | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 spec/javascripts/monitoring/charts/single_stat_spec.js (limited to 'spec') diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js new file mode 100644 index 00000000000..12b73002f97 --- /dev/null +++ b/spec/javascripts/monitoring/charts/single_stat_spec.js @@ -0,0 +1,28 @@ +import { shallowMount } from '@vue/test-utils'; +import SingleStatChart from '~/monitoring/components/charts/single_stat.vue'; + +describe('Single Stat Chart component', () => { + let singleStatChart; + + beforeEach(() => { + singleStatChart = shallowMount(SingleStatChart, { + propsData: { + title: 'Time to render', + value: 1, + unit: 'sec', + }, + }); + }); + + afterEach(() => { + singleStatChart.destroy(); + }); + + describe('computed', () => { + describe('valueWithUnit', () => { + it('should interpolate the value and unit props', () => { + expect(singleStatChart.vm.valueWithUnit).toBe('1sec'); + }); + }); + }); +}); -- cgit v1.2.1 From a9850b25c3624fe31d0230ad3f0df00fec6b7d48 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 24 May 2019 13:14:24 -0500 Subject: Jestify autosave spec and add localStorage helper The helper was needed because `jest.spyOn` would not work on this special window object property. The only way we could successfully spy on `localStorage` was with this helper. --- spec/frontend/autosave_spec.js | 151 +++++++++++++++++++++++++ spec/frontend/helpers/local_storage_helper.js | 41 +++++++ spec/javascripts/autosave_spec.js | 154 -------------------------- 3 files changed, 192 insertions(+), 154 deletions(-) create mode 100644 spec/frontend/autosave_spec.js create mode 100644 spec/frontend/helpers/local_storage_helper.js delete mode 100644 spec/javascripts/autosave_spec.js (limited to 'spec') diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js new file mode 100644 index 00000000000..4d9c8f96d62 --- /dev/null +++ b/spec/frontend/autosave_spec.js @@ -0,0 +1,151 @@ +import $ from 'jquery'; +import Autosave from '~/autosave'; +import AccessorUtilities from '~/lib/utils/accessor'; +import { useLocalStorageSpy } from 'helpers/local_storage_helper'; + +describe('Autosave', () => { + useLocalStorageSpy(); + + let autosave; + const field = $(''); + const key = 'key'; + + describe('class constructor', () => { + beforeEach(() => { + jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true); + jest.spyOn(Autosave.prototype, 'restore').mockImplementation(() => {}); + }); + + it('should set .isLocalStorageAvailable', () => { + autosave = new Autosave(field, key); + + expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); + expect(autosave.isLocalStorageAvailable).toBe(true); + }); + }); + + describe('restore', () => { + beforeEach(() => { + autosave = { + field, + key, + }; + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = false; + + Autosave.prototype.restore.call(autosave); + }); + + it('should not call .getItem', () => { + expect(window.localStorage.getItem).not.toHaveBeenCalled(); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = true; + }); + + it('should call .getItem', () => { + Autosave.prototype.restore.call(autosave); + + expect(window.localStorage.getItem).toHaveBeenCalledWith(key); + }); + + it('triggers jquery event', () => { + jest.spyOn(autosave.field, 'trigger').mockImplementation(() => {}); + + Autosave.prototype.restore.call(autosave); + + expect(field.trigger).toHaveBeenCalled(); + }); + + it('triggers native event', done => { + autosave.field.get(0).addEventListener('change', () => { + done(); + }); + + Autosave.prototype.restore.call(autosave); + }); + }); + + describe('if field gets deleted from DOM', () => { + beforeEach(() => { + autosave.field = $('.not-a-real-element'); + }); + + it('does not trigger event', () => { + jest.spyOn(field, 'trigger'); + + expect(field.trigger).not.toHaveBeenCalled(); + }); + }); + }); + + describe('save', () => { + beforeEach(() => { + autosave = { reset: jest.fn() }; + autosave.field = field; + field.val('value'); + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = false; + + Autosave.prototype.save.call(autosave); + }); + + it('should not call .setItem', () => { + expect(window.localStorage.setItem).not.toHaveBeenCalled(); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = true; + + Autosave.prototype.save.call(autosave); + }); + + it('should call .setItem', () => { + expect(window.localStorage.setItem).toHaveBeenCalled(); + }); + }); + }); + + describe('reset', () => { + beforeEach(() => { + autosave = { + key, + }; + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = false; + + Autosave.prototype.reset.call(autosave); + }); + + it('should not call .removeItem', () => { + expect(window.localStorage.removeItem).not.toHaveBeenCalled(); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + beforeEach(() => { + autosave.isLocalStorageAvailable = true; + + Autosave.prototype.reset.call(autosave); + }); + + it('should call .removeItem', () => { + expect(window.localStorage.removeItem).toHaveBeenCalledWith(key); + }); + }); + }); +}); diff --git a/spec/frontend/helpers/local_storage_helper.js b/spec/frontend/helpers/local_storage_helper.js new file mode 100644 index 00000000000..48e66b11767 --- /dev/null +++ b/spec/frontend/helpers/local_storage_helper.js @@ -0,0 +1,41 @@ +/** + * Manage the instance of a custom `window.localStorage` + * + * This only encapsulates the setup / teardown logic so that it can easily be + * reused with different implementations (i.e. a spy or a [fake][1]) + * + * [1]: https://stackoverflow.com/a/41434763/1708147 + * + * @param {() => any} fn Function that returns the object to use for localStorage + */ +const useLocalStorage = fn => { + const origLocalStorage = window.localStorage; + let currentLocalStorage; + + Object.defineProperty(window, 'localStorage', { + get: () => currentLocalStorage, + }); + + beforeEach(() => { + currentLocalStorage = fn(); + }); + + afterEach(() => { + currentLocalStorage = origLocalStorage; + }); +}; + +/** + * Create an object with the localStorage interface but `jest.fn()` implementations. + */ +export const createLocalStorageSpy = () => ({ + clear: jest.fn(), + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn(), +}); + +/** + * Before each test, overwrite `window.localStorage` with a spy implementation. + */ +export const useLocalStorageSpy = () => useLocalStorage(createLocalStorageSpy); diff --git a/spec/javascripts/autosave_spec.js b/spec/javascripts/autosave_spec.js deleted file mode 100644 index dcb1c781591..00000000000 --- a/spec/javascripts/autosave_spec.js +++ /dev/null @@ -1,154 +0,0 @@ -import $ from 'jquery'; -import Autosave from '~/autosave'; -import AccessorUtilities from '~/lib/utils/accessor'; - -describe('Autosave', () => { - let autosave; - const field = $(''); - const key = 'key'; - - describe('class constructor', () => { - beforeEach(() => { - spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true); - spyOn(Autosave.prototype, 'restore'); - }); - - it('should set .isLocalStorageAvailable', () => { - autosave = new Autosave(field, key); - - expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); - expect(autosave.isLocalStorageAvailable).toBe(true); - }); - }); - - describe('restore', () => { - beforeEach(() => { - autosave = { - field, - key, - }; - - spyOn(window.localStorage, 'getItem'); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.restore.call(autosave); - }); - - it('should not call .getItem', () => { - expect(window.localStorage.getItem).not.toHaveBeenCalled(); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = true; - }); - - it('should call .getItem', () => { - Autosave.prototype.restore.call(autosave); - - expect(window.localStorage.getItem).toHaveBeenCalledWith(key); - }); - - it('triggers jquery event', () => { - spyOn(autosave.field, 'trigger').and.callThrough(); - - Autosave.prototype.restore.call(autosave); - - expect(field.trigger).toHaveBeenCalled(); - }); - - it('triggers native event', done => { - autosave.field.get(0).addEventListener('change', () => { - done(); - }); - - Autosave.prototype.restore.call(autosave); - }); - }); - - describe('if field gets deleted from DOM', () => { - beforeEach(() => { - autosave.field = $('.not-a-real-element'); - }); - - it('does not trigger event', () => { - spyOn(field, 'trigger').and.callThrough(); - - expect(field.trigger).not.toHaveBeenCalled(); - }); - }); - }); - - describe('save', () => { - beforeEach(() => { - autosave = jasmine.createSpyObj('autosave', ['reset']); - autosave.field = field; - field.val('value'); - - spyOn(window.localStorage, 'setItem'); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.save.call(autosave); - }); - - it('should not call .setItem', () => { - expect(window.localStorage.setItem).not.toHaveBeenCalled(); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = true; - - Autosave.prototype.save.call(autosave); - }); - - it('should call .setItem', () => { - expect(window.localStorage.setItem).toHaveBeenCalled(); - }); - }); - }); - - describe('reset', () => { - beforeEach(() => { - autosave = { - key, - }; - - spyOn(window.localStorage, 'removeItem'); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = false; - - Autosave.prototype.reset.call(autosave); - }); - - it('should not call .removeItem', () => { - expect(window.localStorage.removeItem).not.toHaveBeenCalled(); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(() => { - autosave.isLocalStorageAvailable = true; - - Autosave.prototype.reset.call(autosave); - }); - - it('should call .removeItem', () => { - expect(window.localStorage.removeItem).toHaveBeenCalledWith(key); - }); - }); - }); -}); -- cgit v1.2.1 From 57f6be00bcaa20287c696d67128655738d740b8b Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Tue, 4 Jun 2019 17:55:52 +0000 Subject: Handle external dashboard form submission Connect frontend UI with backend api for external dashboard link --- .../components/external_dashboard_spec.js | 150 ++++++++++++++------- .../operation_settings/store/mutations_spec.js | 19 +++ spec/javascripts/monitoring/dashboard_spec.js | 4 +- 3 files changed, 124 insertions(+), 49 deletions(-) create mode 100644 spec/frontend/operation_settings/store/mutations_spec.js (limited to 'spec') diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js index 23dc3c3db8c..986aada0b03 100644 --- a/spec/frontend/operation_settings/components/external_dashboard_spec.js +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -1,23 +1,48 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount, shallowMount, createLocalVue } from '@vue/test-utils'; import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui'; import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue'; +import store from '~/operation_settings/store'; +import axios from '~/lib/utils/axios_utils'; +import { refreshCurrentPage } from '~/lib/utils/url_utility'; +import createFlash from '~/flash'; import { TEST_HOST } from 'helpers/test_constants'; +jest.mock('~/lib/utils/axios_utils'); +jest.mock('~/lib/utils/url_utility'); +jest.mock('~/flash'); + describe('operation settings external dashboard component', () => { let wrapper; - const externalDashboardPath = `http://mock-external-domain.com/external/dashboard/path`; + const operationsSettingsEndpoint = `${TEST_HOST}/mock/ops/settings/endpoint`; + const externalDashboardUrl = `http://mock-external-domain.com/external/dashboard/url`; const externalDashboardHelpPagePath = `${TEST_HOST}/help/page/path`; - - beforeEach(() => { - wrapper = shallowMount(ExternalDashboard, { - propsData: { - externalDashboardPath, - externalDashboardHelpPagePath, + const localVue = createLocalVue(); + const mountComponent = (shallow = true) => { + const config = [ + ExternalDashboard, + { + localVue, + store: store({ + operationsSettingsEndpoint, + externalDashboardUrl, + externalDashboardHelpPagePath, + }), }, - }); + ]; + wrapper = shallow ? shallowMount(...config) : mount(...config); + }; + + afterEach(() => { + if (wrapper.destroy) { + wrapper.destroy(); + } + axios.patch.mockReset(); + refreshCurrentPage.mockReset(); + createFlash.mockReset(); }); it('renders header text', () => { + mountComponent(); expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard'); }); @@ -33,6 +58,7 @@ describe('operation settings external dashboard component', () => { let subHeader; beforeEach(() => { + mountComponent(); subHeader = wrapper.find('.js-section-sub-header'); }); @@ -51,57 +77,87 @@ describe('operation settings external dashboard component', () => { }); describe('form', () => { - let form; + describe('input label', () => { + let formGroup; - beforeEach(() => { - form = wrapper.find('form'); - }); + beforeEach(() => { + mountComponent(); + formGroup = wrapper.find(GlFormGroup); + }); - describe('external dashboard url', () => { - describe('input label', () => { - let formGroup; + it('uses label text', () => { + expect(formGroup.attributes().label).toBe('Full dashboard URL'); + }); - beforeEach(() => { - formGroup = form.find(GlFormGroup); - }); + it('uses description text', () => { + expect(formGroup.attributes().description).toBe( + 'Enter the URL of the dashboard you want to link to', + ); + }); + }); - it('uses label text', () => { - expect(formGroup.attributes().label).toBe('Full dashboard URL'); - }); + describe('input field', () => { + let input; - it('uses description text', () => { - expect(formGroup.attributes().description).toBe( - 'Enter the URL of the dashboard you want to link to', - ); - }); + beforeEach(() => { + mountComponent(); + input = wrapper.find(GlFormInput); }); - describe('input field', () => { - let input; - - beforeEach(() => { - input = form.find(GlFormInput); - }); + it('defaults to externalDashboardUrl', () => { + expect(input.attributes().value).toBe(externalDashboardUrl); + }); - it('defaults to externalDashboardPath prop', () => { - expect(input.attributes().value).toBe(externalDashboardPath); - }); + it('uses a placeholder', () => { + expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards'); + }); + }); - it('uses a placeholder', () => { - expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards'); - }); + describe('submit button', () => { + const endpointRequest = [ + operationsSettingsEndpoint, + { + project: { + metrics_setting_attributes: { + external_dashboard_url: externalDashboardUrl, + }, + }, + }, + ]; + + it('renders button label', () => { + mountComponent(); + const submit = wrapper.find(GlButton); + expect(submit.text()).toBe('Save Changes'); }); - describe('submit button', () => { - let submit; + it('submits form on click', () => { + mountComponent(false); + axios.patch.mockResolvedValue(); + wrapper.find(GlButton).trigger('click'); + + expect(axios.patch).toHaveBeenCalledWith(...endpointRequest); - beforeEach(() => { - submit = form.find(GlButton); - }); + return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled()); + }); - it('renders button label', () => { - expect(submit.text()).toBe('Save Changes'); - }); + it('creates flash banner on error', () => { + mountComponent(false); + const message = 'mockErrorMessage'; + axios.patch.mockRejectedValue({ response: { data: { message } } }); + wrapper.find(GlButton).trigger('click'); + + expect(axios.patch).toHaveBeenCalledWith(...endpointRequest); + + return wrapper.vm + .$nextTick() + .then(jest.runAllTicks) + .then(() => + expect(createFlash).toHaveBeenCalledWith( + `There was an error saving your changes. ${message}`, + 'alert', + ), + ); }); }); }); diff --git a/spec/frontend/operation_settings/store/mutations_spec.js b/spec/frontend/operation_settings/store/mutations_spec.js new file mode 100644 index 00000000000..1854142c89a --- /dev/null +++ b/spec/frontend/operation_settings/store/mutations_spec.js @@ -0,0 +1,19 @@ +import mutations from '~/operation_settings/store/mutations'; +import createState from '~/operation_settings/store/state'; + +describe('operation settings mutations', () => { + let localState; + + beforeEach(() => { + localState = createState(); + }); + + describe('SET_EXTERNAL_DASHBOARD_URL', () => { + it('sets externalDashboardUrl', () => { + const mockUrl = 'mockUrl'; + mutations.SET_EXTERNAL_DASHBOARD_URL(localState, mockUrl); + + expect(localState.externalDashboardUrl).toBe(mockUrl); + }); + }); +}); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 6e16ab64be2..cea8cb18918 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -393,7 +393,7 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, showTimeWindowDropdown: false, - externalDashboardPath: '/mockPath', + externalDashboardUrl: '/mockUrl', }, store, }); @@ -419,7 +419,7 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, showTimeWindowDropdown: false, - externalDashboardPath: '', + externalDashboardUrl: '', }, store, }); -- cgit v1.2.1 From 0e4033860ba59a48eab041fd6fca0cf8a30c9c9b Mon Sep 17 00:00:00 2001 From: Paul Gascou-Vaillancourt Date: Tue, 4 Jun 2019 16:07:36 -0400 Subject: Fix submit button selector in specs --- .../operation_settings/components/external_dashboard_spec.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js index 986aada0b03..a881de8fbfe 100644 --- a/spec/frontend/operation_settings/components/external_dashboard_spec.js +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -114,6 +114,8 @@ describe('operation settings external dashboard component', () => { }); describe('submit button', () => { + const findSubmitButton = () => wrapper.find('.settings-content form').find(GlButton); + const endpointRequest = [ operationsSettingsEndpoint, { @@ -127,14 +129,14 @@ describe('operation settings external dashboard component', () => { it('renders button label', () => { mountComponent(); - const submit = wrapper.find(GlButton); + const submit = findSubmitButton(); expect(submit.text()).toBe('Save Changes'); }); it('submits form on click', () => { mountComponent(false); axios.patch.mockResolvedValue(); - wrapper.find(GlButton).trigger('click'); + findSubmitButton().trigger('click'); expect(axios.patch).toHaveBeenCalledWith(...endpointRequest); @@ -145,7 +147,7 @@ describe('operation settings external dashboard component', () => { mountComponent(false); const message = 'mockErrorMessage'; axios.patch.mockRejectedValue({ response: { data: { message } } }); - wrapper.find(GlButton).trigger('click'); + findSubmitButton().trigger('click'); expect(axios.patch).toHaveBeenCalledWith(...endpointRequest); -- cgit v1.2.1 From 977ba4cc150092107944f3a46734b2540d321dcf Mon Sep 17 00:00:00 2001 From: Ash McKenzie Date: Tue, 4 Jun 2019 14:44:02 +1000 Subject: Ensure DB is writable before continuing jobs In the context of a Geo setup, some jobs can be running on a Geo secondary where the database is read-only and therefore we should guard against various jobs attempting to write. --- spec/workers/pages_domain_verification_cron_worker_spec.rb | 7 +++++++ spec/workers/pages_domain_verification_worker_spec.rb | 7 +++++++ 2 files changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb index 186824a444f..3fb86adee11 100644 --- a/spec/workers/pages_domain_verification_cron_worker_spec.rb +++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb @@ -10,6 +10,13 @@ describe PagesDomainVerificationCronWorker do let!(:reverify) { create(:pages_domain, :reverify) } let!(:disabled) { create(:pages_domain, :disabled) } + it 'does nothing if the database is read-only' do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + expect(PagesDomainVerificationWorker).not_to receive(:perform_async).with(reverify.id) + + worker.perform + end + it 'enqueues a PagesDomainVerificationWorker for domains needing verification' do [reverify, disabled].each do |domain| expect(PagesDomainVerificationWorker).to receive(:perform_async).with(domain.id) diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb index 2f23dcb487f..f51ac1f4323 100644 --- a/spec/workers/pages_domain_verification_worker_spec.rb +++ b/spec/workers/pages_domain_verification_worker_spec.rb @@ -8,6 +8,13 @@ describe PagesDomainVerificationWorker do let(:domain) { create(:pages_domain) } describe '#perform' do + it 'does nothing if the database is read-only' do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + expect(PagesDomain).not_to receive(:find_by).with(id: domain.id) + + worker.perform(domain.id) + end + it 'does nothing for a non-existent domain' do domain.destroy -- cgit v1.2.1 From cfaac7532210ef1ce03f335a3198bb7d2ad3979a Mon Sep 17 00:00:00 2001 From: drew cimino Date: Fri, 26 Apr 2019 11:37:20 -0400 Subject: && and || operators for CI Pipeline expressions. Refactored regex pattern matching to eagerly return tokens Packaged behind a default-enabled feature flag and added operator documentation. --- .../ci/pipeline/expression/lexeme/and_spec.rb | 77 +++++++ .../ci/pipeline/expression/lexeme/equals_spec.rb | 55 +++-- .../ci/pipeline/expression/lexeme/matches_spec.rb | 120 +++++++---- .../pipeline/expression/lexeme/not_equals_spec.rb | 60 ++++-- .../pipeline/expression/lexeme/not_matches_spec.rb | 120 +++++++---- .../ci/pipeline/expression/lexeme/or_spec.rb | 77 +++++++ .../ci/pipeline/expression/lexeme/pattern_spec.rb | 94 ++++++-- .../gitlab/ci/pipeline/expression/lexer_spec.rb | 50 +++++ .../gitlab/ci/pipeline/expression/parser_spec.rb | 48 ++++- .../ci/pipeline/expression/statement_spec.rb | 237 +++++++++++++-------- 10 files changed, 730 insertions(+), 208 deletions(-) create mode 100644 spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb create mode 100644 spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb (limited to 'spec') diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb new file mode 100644 index 00000000000..006ce4d8078 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb @@ -0,0 +1,77 @@ +require 'fast_spec_helper' +require 'rspec-parameterized' + +describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do + let(:left) { double('left', evaluate: nil) } + let(:right) { double('right', evaluate: nil) } + + describe '.build' do + it 'creates a new instance of the token' do + expect(described_class.build('&&', left, right)).to be_a(described_class) + end + + context 'with non-evaluable operands' do + let(:left) { double('left') } + let(:right) { double('right') } + + it 'raises an operator error' do + expect { described_class.build('&&', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end + end + + describe '.type' do + it 'is an operator' do + expect(described_class.type).to eq :operator + end + end + + describe '.precedence' do + it 'has a precedence' do + expect(described_class.precedence).to be_an Integer + end + end + + describe '#evaluate' do + let(:operator) { described_class.new(left, right) } + + subject { operator.evaluate } + + before do + allow(left).to receive(:evaluate).and_return(left_value) + allow(right).to receive(:evaluate).and_return(right_value) + end + + context 'when left and right are truthy' do + where(:left_value, :right_value) do + [true, 1, 'a'].permutation(2).to_a + end + + with_them do + it { is_expected.to be_truthy } + it { is_expected.to eq(right_value) } + end + end + + context 'when left or right is falsey' do + where(:left_value, :right_value) do + [true, false, nil].permutation(2).to_a + end + + with_them do + it { is_expected.to be_falsey } + end + end + + context 'when left and right are falsey' do + where(:left_value, :right_value) do + [false, nil].permutation(2).to_a + end + + with_them do + it { is_expected.to be_falsey } + it { is_expected.to eq(left_value) } + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb index 019a2ed184d..fcbd2863289 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb @@ -5,9 +5,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do let(:right) { double('right') } describe '.build' do - it 'creates a new instance of the token' do - expect(described_class.build('==', left, right)) - .to be_a(described_class) + context 'with non-evaluable operands' do + it 'creates a new instance of the token' do + expect { described_class.build('==', left, right) } + .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end + + context 'with evaluable operands' do + it 'creates a new instance of the token' do + allow(left).to receive(:evaluate).and_return('my-string') + allow(right).to receive(:evaluate).and_return('my-string') + + expect(described_class.build('==', left, right)) + .to be_a(described_class) + end end end @@ -17,23 +29,40 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do end end + describe '.precedence' do + it 'has a precedence' do + expect(described_class.precedence).to be_an Integer + end + end + describe '#evaluate' do - it 'returns false when left and right are not equal' do - allow(left).to receive(:evaluate).and_return(1) - allow(right).to receive(:evaluate).and_return(2) + let(:operator) { described_class.new(left, right) } - operator = described_class.new(left, right) + subject { operator.evaluate } - expect(operator.evaluate(VARIABLE: 3)).to eq false + before do + allow(left).to receive(:evaluate).and_return(left_value) + allow(right).to receive(:evaluate).and_return(right_value) end - it 'returns true when left and right are equal' do - allow(left).to receive(:evaluate).and_return(1) - allow(right).to receive(:evaluate).and_return(1) + context 'when left and right are equal' do + where(:left_value, :right_value) do + [%w(string string)] + end + + with_them do + it { is_expected.to eq(true) } + end + end - operator = described_class.new(left, right) + context 'when left and right are not equal' do + where(:left_value, :right_value) do + ['one string', 'two string'].permutation(2).to_a + end - expect(operator.evaluate(VARIABLE: 3)).to eq true + with_them do + it { is_expected.to eq(false) } + end end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb index 49e5af52f4d..97da66d2bcc 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -6,9 +6,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right) { double('right') } describe '.build' do - it 'creates a new instance of the token' do - expect(described_class.build('=~', left, right)) - .to be_a(described_class) + context 'with non-evaluable operands' do + it 'creates a new instance of the token' do + expect { described_class.build('=~', left, right) } + .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end + + context 'with evaluable operands' do + it 'creates a new instance of the token' do + allow(left).to receive(:evaluate).and_return('my-string') + allow(right).to receive(:evaluate).and_return('/my-string/') + + expect(described_class.build('=~', left, right)) + .to be_a(described_class) + end end end @@ -18,63 +30,93 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do end end + describe '.precedence' do + it 'has a precedence' do + expect(described_class.precedence).to be_an Integer + end + end + describe '#evaluate' do - it 'returns false when left and right do not match' do - allow(left).to receive(:evaluate).and_return('my-string') - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('something')) + let(:operator) { described_class.new(left, right) } - operator = described_class.new(left, right) + subject { operator.evaluate } - expect(operator.evaluate).to eq false + before do + allow(left).to receive(:evaluate).and_return(left_value) + allow(right).to receive(:evaluate).and_return(right_value) end - it 'returns true when left and right match' do - allow(left).to receive(:evaluate).and_return('my-awesome-string') - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('awesome.string$')) + context 'when left and right do not match' do + let(:left_value) { 'my-string' } + let(:right_value) { Gitlab::UntrustedRegexp.new('something') } - operator = described_class.new(left, right) - - expect(operator.evaluate).to eq true + it { is_expected.to eq(nil) } end - it 'supports matching against a nil value' do - allow(left).to receive(:evaluate).and_return(nil) - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('pattern')) + context 'when left and right match' do + let(:left_value) { 'my-awesome-string' } + let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') } + + it { is_expected.to eq(3) } + end - operator = described_class.new(left, right) + context 'when left is nil' do + let(:left_value) { nil } + let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') } - expect(operator.evaluate).to eq false + it { is_expected.to eq(nil) } end - it 'supports multiline strings' do - allow(left).to receive(:evaluate).and_return <<~TEXT - My awesome contents + context 'when left is a multiline string and matches right' do + let(:left_value) do + <<~TEXT + My awesome contents + + My-text-string! + TEXT + end + + let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } + + it { is_expected.to eq(24) } + end - My-text-string! - TEXT + context 'when left is a multiline string and does not match right' do + let(:left_value) do + <<~TEXT + My awesome contents - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('text-string')) + My-terrible-string! + TEXT + end - operator = described_class.new(left, right) + let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } - expect(operator.evaluate).to eq true + it { is_expected.to eq(nil) } end - it 'supports regexp flags' do - allow(left).to receive(:evaluate).and_return <<~TEXT - My AWESOME content - TEXT + context 'when a matching pattern uses regex flags' do + let(:left_value) do + <<~TEXT + My AWESOME content + TEXT + end + + let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') } + + it { is_expected.to eq(3) } + end - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome')) + context 'when a non-matching pattern uses regex flags' do + let(:left_value) do + <<~TEXT + My AWESOME content + TEXT + end - operator = described_class.new(left, right) + let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') } - expect(operator.evaluate).to eq true + it { is_expected.to eq(nil) } end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb index 9aa2f4efd67..38d30c9035a 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb @@ -5,9 +5,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do let(:right) { double('right') } describe '.build' do - it 'creates a new instance of the token' do - expect(described_class.build('!=', left, right)) - .to be_a(described_class) + context 'with non-evaluable operands' do + it 'creates a new instance of the token' do + expect { described_class.build('!=', left, right) } + .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end + + context 'with evaluable operands' do + it 'creates a new instance of the token' do + allow(left).to receive(:evaluate).and_return('my-string') + allow(right).to receive(:evaluate).and_return('my-string') + + expect(described_class.build('!=', left, right)) + .to be_a(described_class) + end end end @@ -17,23 +29,45 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do end end + describe '.precedence' do + it 'has a precedence' do + expect(described_class.precedence).to be_an Integer + end + end + describe '#evaluate' do - it 'returns true when left and right are not equal' do - allow(left).to receive(:evaluate).and_return(1) - allow(right).to receive(:evaluate).and_return(2) + let(:operator) { described_class.new(left, right) } - operator = described_class.new(left, right) + subject { operator.evaluate } - expect(operator.evaluate(VARIABLE: 3)).to eq true + before do + allow(left).to receive(:evaluate).and_return(left_value) + allow(right).to receive(:evaluate).and_return(right_value) end - it 'returns false when left and right are equal' do - allow(left).to receive(:evaluate).and_return(1) - allow(right).to receive(:evaluate).and_return(1) + context 'when left and right are equal' do + using RSpec::Parameterized::TableSyntax + + where(:left_value, :right_value) do + 'string' | 'string' + 1 | 1 + '' | '' + nil | nil + end + + with_them do + it { is_expected.to eq(false) } + end + end - operator = described_class.new(left, right) + context 'when left and right are not equal' do + where(:left_value, :right_value) do + ['one string', 'two string', 1, 2, '', nil, false, true].permutation(2).to_a + end - expect(operator.evaluate(VARIABLE: 3)).to eq false + with_them do + it { is_expected.to eq(true) } + end end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb index fa3b9651fb4..99110ff8d88 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb @@ -6,9 +6,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do let(:right) { double('right') } describe '.build' do - it 'creates a new instance of the token' do - expect(described_class.build('!~', left, right)) - .to be_a(described_class) + context 'with non-evaluable operands' do + it 'creates a new instance of the token' do + expect { described_class.build('!~', left, right) } + .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end + + context 'with evaluable operands' do + it 'creates a new instance of the token' do + allow(left).to receive(:evaluate).and_return('my-string') + allow(right).to receive(:evaluate).and_return('my-string') + + expect(described_class.build('!~', left, right)) + .to be_a(described_class) + end end end @@ -18,63 +30,93 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do end end + describe '.precedence' do + it 'has a precedence' do + expect(described_class.precedence).to be_an Integer + end + end + describe '#evaluate' do - it 'returns true when left and right do not match' do - allow(left).to receive(:evaluate).and_return('my-string') - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('something')) + let(:operator) { described_class.new(left, right) } - operator = described_class.new(left, right) + subject { operator.evaluate } - expect(operator.evaluate).to eq true + before do + allow(left).to receive(:evaluate).and_return(left_value) + allow(right).to receive(:evaluate).and_return(right_value) end - it 'returns false when left and right match' do - allow(left).to receive(:evaluate).and_return('my-awesome-string') - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('awesome.string$')) + context 'when left and right do not match' do + let(:left_value) { 'my-string' } + let(:right_value) { Gitlab::UntrustedRegexp.new('something') } - operator = described_class.new(left, right) - - expect(operator.evaluate).to eq false + it { is_expected.to eq(true) } end - it 'supports matching against a nil value' do - allow(left).to receive(:evaluate).and_return(nil) - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('pattern')) + context 'when left and right match' do + let(:left_value) { 'my-awesome-string' } + let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') } + + it { is_expected.to eq(false) } + end - operator = described_class.new(left, right) + context 'when left is nil' do + let(:left_value) { nil } + let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') } - expect(operator.evaluate).to eq true + it { is_expected.to eq(true) } end - it 'supports multiline strings' do - allow(left).to receive(:evaluate).and_return <<~TEXT - My awesome contents + context 'when left is a multiline string and matches right' do + let(:left_value) do + <<~TEXT + My awesome contents + + My-text-string! + TEXT + end + + let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } + + it { is_expected.to eq(false) } + end - My-text-string! - TEXT + context 'when left is a multiline string and does not match right' do + let(:left_value) do + <<~TEXT + My awesome contents - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('text-string')) + My-terrible-string! + TEXT + end - operator = described_class.new(left, right) + let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } - expect(operator.evaluate).to eq false + it { is_expected.to eq(true) } end - it 'supports regexp flags' do - allow(left).to receive(:evaluate).and_return <<~TEXT - My AWESOME content - TEXT + context 'when a matching pattern uses regex flags' do + let(:left_value) do + <<~TEXT + My AWESOME content + TEXT + end + + let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') } + + it { is_expected.to eq(false) } + end - allow(right).to receive(:evaluate) - .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome')) + context 'when a non-matching pattern uses regex flags' do + let(:left_value) do + <<~TEXT + My AWESOME content + TEXT + end - operator = described_class.new(left, right) + let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') } - expect(operator.evaluate).to eq false + it { is_expected.to eq(true) } end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb new file mode 100644 index 00000000000..d542eebc613 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb @@ -0,0 +1,77 @@ +require 'fast_spec_helper' +require 'rspec-parameterized' + +describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do + let(:left) { double('left', evaluate: nil) } + let(:right) { double('right', evaluate: nil) } + + describe '.build' do + it 'creates a new instance of the token' do + expect(described_class.build('||', left, right)).to be_a(described_class) + end + + context 'with non-evaluable operands' do + let(:left) { double('left') } + let(:right) { double('right') } + + it 'raises an operator error' do + expect { described_class.build('||', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end + end + + describe '.type' do + it 'is an operator' do + expect(described_class.type).to eq :operator + end + end + + describe '.precedence' do + it 'has a precedence' do + expect(described_class.precedence).to be_an Integer + end + end + + describe '#evaluate' do + let(:operator) { described_class.new(left, right) } + + subject { operator.evaluate } + + before do + allow(left).to receive(:evaluate).and_return(left_value) + allow(right).to receive(:evaluate).and_return(right_value) + end + + context 'when left and right are truthy' do + where(:left_value, :right_value) do + [true, 1, 'a'].permutation(2).to_a + end + + with_them do + it { is_expected.to be_truthy } + it { is_expected.to eq(left_value) } + end + end + + context 'when left or right is truthy' do + where(:left_value, :right_value) do + [true, false, 'a'].permutation(2).to_a + end + + with_them do + it { is_expected.to be_truthy } + end + end + + context 'when left and right are falsey' do + where(:left_value, :right_value) do + [false, nil].permutation(2).to_a + end + + with_them do + it { is_expected.to be_falsey } + it { is_expected.to eq(right_value) } + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index cff7f57ceff..30ea3f3e28e 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -1,4 +1,4 @@ -require 'fast_spec_helper' +require 'spec_helper' describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do describe '.build' do @@ -30,16 +30,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do .to eq Gitlab::UntrustedRegexp.new('pattern') end - it 'is a greedy scanner for regexp boundaries' do - scanner = StringScanner.new('/some .* / pattern/') - - token = described_class.scan(scanner) - - expect(token).not_to be_nil - expect(token.build.evaluate) - .to eq Gitlab::UntrustedRegexp.new('some .* / pattern') - end - it 'does not allow to use an empty pattern' do scanner = StringScanner.new(%(//)) @@ -68,12 +58,90 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do .to eq Gitlab::UntrustedRegexp.new('(?im)pattern') end - it 'does not support arbitrary flags' do + it 'ignores unsupported flags' do scanner = StringScanner.new('/pattern/x') token = described_class.scan(scanner) - expect(token).to be_nil + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('pattern') + end + + it 'is a eager scanner for regexp boundaries' do + scanner = StringScanner.new('/some .* / pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some .* ') + end + + it 'does not match on escaped regexp boundaries' do + scanner = StringScanner.new('/some .* \/ pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some .* / pattern') + end + + it 'recognizes \ as an escape character for /' do + scanner = StringScanner.new('/some numeric \/$ pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some numeric /$ pattern') + end + + it 'does not recognize \ as an escape character for $' do + scanner = StringScanner.new('/some numeric \$ pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern') + end + + context 'with the ci_variables_complex_expressions feature flag disabled' do + before do + stub_feature_flags(ci_variables_complex_expressions: false) + end + + it 'is a greedy scanner for regexp boundaries' do + scanner = StringScanner.new('/some .* / pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some .* / pattern') + end + + it 'does not recognize the \ escape character for /' do + scanner = StringScanner.new('/some .* \/ pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern') + end + + it 'does not recognize the \ escape character for $' do + scanner = StringScanner.new('/some numeric \$ pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern') + end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb index 3f11b3f7673..d8db9c262a1 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb @@ -58,6 +58,56 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do expect { lexer.tokens } .to raise_error described_class::SyntaxError end + + context 'with complex expressions' do + using RSpec::Parameterized::TableSyntax + + subject { described_class.new(expression).tokens.map(&:value) } + + where(:expression, :tokens) do + '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '&&', '$EMPTY_VARIABLE', '=~', '/nope/'] + '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE'] + '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE', '!=', '"nope"'] + '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '&&', '$EMPTY_VARIABLE'] + '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '||', '$EMPTY_VARIABLE', '=~', '/nope/'] + '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE'] + '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE', '!=', '"nope"'] + '$PRESENT_VARIABLE || $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '||', '$EMPTY_VARIABLE'] + '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | ['$PRESENT_VARIABLE', '&&', 'null', '||', '$EMPTY_VARIABLE', '==', '""'] + end + + with_them do + it { is_expected.to eq(tokens) } + end + end + + context 'with the ci_variables_complex_expressions feature flag turned off' do + before do + stub_feature_flags(ci_variables_complex_expressions: false) + end + + it 'incorrectly tokenizes conjunctive match statements as one match statement' do + tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens + + expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/']) + end + + it 'incorrectly tokenizes disjunctive match statements as one statement' do + tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens + + expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/']) + end + + it 'raises an error about && operators' do + expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens } + .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!') + end + + it 'raises an error about || operators' do + expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens } + .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!') + end + end end describe '#lexemes' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb index 2b78b1dd4a7..e88ec5561b6 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb @@ -2,25 +2,67 @@ require 'fast_spec_helper' describe Gitlab::Ci::Pipeline::Expression::Parser do describe '#tree' do - context 'when using operators' do + context 'when using two operators' do + it 'returns a reverse descent parse tree' do + expect(described_class.seed('$VAR1 == "123"').tree) + .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals + end + end + + context 'when using three operators' do it 'returns a reverse descent parse tree' do expect(described_class.seed('$VAR1 == "123" == $VAR2').tree) .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals end end - context 'when using a single token' do + context 'when using a single variable token' do it 'returns a single token instance' do expect(described_class.seed('$VAR').tree) .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable end end + context 'when using a single string token' do + it 'returns a single token instance' do + expect(described_class.seed('"some value"').tree) + .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::String + end + end + context 'when expression is empty' do it 'returns a null token' do - expect(described_class.seed('').tree) + expect { described_class.seed('').tree } + .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError + end + end + + context 'when expression is null' do + it 'returns a null token' do + expect(described_class.seed('null').tree) .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null end end + + context 'when two value tokens have no operator' do + it 'raises a parsing error' do + expect { described_class.seed('$VAR "text"').tree } + .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError + end + end + + context 'when an operator has no left side' do + it 'raises an OperatorError' do + expect { described_class.seed('== "123"').tree } + .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end + + context 'when an operator has no right side' do + it 'raises an OperatorError' do + expect { described_class.seed('$VAR ==').tree } + .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError + end + end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index a9fd809409b..057e2f3fbe8 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -1,4 +1,6 @@ -require 'fast_spec_helper' +# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed +# require 'fast_spec_helper' +require 'spec_helper' require 'rspec-parameterized' describe Gitlab::Ci::Pipeline::Expression::Statement do @@ -7,8 +9,12 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do end let(:variables) do - { 'PRESENT_VARIABLE' => 'my variable', - EMPTY_VARIABLE: '' } + { + 'PRESENT_VARIABLE' => 'my variable', + 'PATH_VARIABLE' => 'a/path/variable/value', + 'FULL_PATH_VARIABLE' => '/a/full/path/variable/value', + 'EMPTY_VARIABLE' => '' + } end describe '.new' do @@ -21,105 +27,158 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do end end - describe '#parse_tree' do - context 'when expression is empty' do - let(:text) { '' } - - it 'raises an error' do - expect { subject.parse_tree } - .to raise_error described_class::StatementError - end - end + describe '#evaluate' do + using RSpec::Parameterized::TableSyntax - context 'when expression grammar is incorrect' do - table = [ - '$VAR "text"', # missing operator - '== "123"', # invalid left side - '"some string"', # only string provided - '$VAR ==', # invalid right side - 'null', # missing lexemes - '' # empty statement - ] - - table.each do |syntax| - context "when expression grammar is #{syntax.inspect}" do - let(:text) { syntax } - - it 'raises a statement error exception' do - expect { subject.parse_tree } - .to raise_error described_class::StatementError - end - - it 'is an invalid statement' do - expect(subject).not_to be_valid - end - end - end + where(:expression, :value) do + '$PRESENT_VARIABLE == "my variable"' | true + '"my variable" == $PRESENT_VARIABLE' | true + '$PRESENT_VARIABLE == null' | false + '$EMPTY_VARIABLE == null' | false + '"" == $EMPTY_VARIABLE' | true + '$EMPTY_VARIABLE' | '' + '$UNDEFINED_VARIABLE == null' | true + 'null == $UNDEFINED_VARIABLE' | true + '$PRESENT_VARIABLE' | 'my variable' + '$UNDEFINED_VARIABLE' | nil + "$PRESENT_VARIABLE =~ /var.*e$/" | 3 + '$PRESENT_VARIABLE =~ /va\r.*e$/' | nil + '$PRESENT_VARIABLE =~ /va\/r.*e$/' | nil + "$PRESENT_VARIABLE =~ /var.*e$/" | 3 + "$PRESENT_VARIABLE =~ /^var.*/" | nil + "$EMPTY_VARIABLE =~ /var.*/" | nil + "$UNDEFINED_VARIABLE =~ /var.*/" | nil + "$PRESENT_VARIABLE =~ /VAR.*/i" | 3 + '$PATH_VARIABLE =~ /path\/variable/' | 2 + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | 0 + '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | 7 + '$PRESENT_VARIABLE != "my variable"' | false + '"my variable" != $PRESENT_VARIABLE' | false + '$PRESENT_VARIABLE != null' | true + '$EMPTY_VARIABLE != null' | true + '"" != $EMPTY_VARIABLE' | false + '$UNDEFINED_VARIABLE != null' | false + 'null != $UNDEFINED_VARIABLE' | false + "$PRESENT_VARIABLE !~ /var.*e$/" | false + "$PRESENT_VARIABLE !~ /^var.*/" | true + '$PRESENT_VARIABLE !~ /^v\ar.*/' | true + '$PRESENT_VARIABLE !~ /^v\/ar.*/' | true + "$EMPTY_VARIABLE !~ /var.*/" | true + "$UNDEFINED_VARIABLE !~ /var.*/" | true + "$PRESENT_VARIABLE !~ /VAR.*/i" | false + + '$PRESENT_VARIABLE && "string"' | 'string' + '$PRESENT_VARIABLE && $PRESENT_VARIABLE' | 'my variable' + '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | '' + '$PRESENT_VARIABLE && null' | nil + '"string" && $PRESENT_VARIABLE' | 'my variable' + '$EMPTY_VARIABLE && $PRESENT_VARIABLE' | 'my variable' + 'null && $PRESENT_VARIABLE' | nil + '$EMPTY_VARIABLE && "string"' | 'string' + '$EMPTY_VARIABLE && $EMPTY_VARIABLE' | '' + '"string" && $EMPTY_VARIABLE' | '' + '"string" && null' | nil + 'null && "string"' | nil + '"string" && "string"' | 'string' + 'null && null' | nil + + '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | nil + '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | 'my variable' + '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | true + '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | '' + '$PRESENT_VARIABLE && $UNDEFINED_VARIABLE' | nil + '$UNDEFINED_VARIABLE && $EMPTY_VARIABLE' | nil + '$UNDEFINED_VARIABLE && $PRESENT_VARIABLE' | nil + + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | 2 + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | nil + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil + + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 0 + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 2 + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | 0 + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | nil + + '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | 0 + '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | true + '$PRESENT_VARIABLE != "nope" || $EMPTY_VARIABLE == ""' | true + + '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | true + '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE' | 'my variable' + '$UNDEFINED_VARIABLE || $PRESENT_VARIABLE' | 'my variable' + '$UNDEFINED_VARIABLE == null || $PRESENT_VARIABLE' | true + '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE == null' | 'my variable' end - context 'when expression grammar is correct' do - context 'when using an operator' do - let(:text) { '$VAR == "value"' } - - it 'returns a reverse descent parse tree' do - expect(subject.parse_tree) - .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals - end + with_them do + let(:text) { expression } - it 'is a valid statement' do - expect(subject).to be_valid - end + it "evaluates to `#{params[:value].inspect}`" do + expect(subject.evaluate).to eq(value) end - context 'when using a single token' do - let(:text) { '$PRESENT_VARIABLE' } + # This test is used to ensure that our parser + # returns exactly the same results as if we + # were evaluating using ruby's `eval` + context 'when using Ruby eval' do + let(:expression_ruby) do + expression + .gsub(/null/, 'nil') + .gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) { "variables['#{Regexp.last_match(1)}']" } + end - it 'returns a single token instance' do - expect(subject.parse_tree) - .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable + it 'behaves exactly the same' do + expect(instance_eval(expression_ruby)).to eq(subject.evaluate) end end end - end - describe '#evaluate' do - using RSpec::Parameterized::TableSyntax + context 'with the ci_variables_complex_expressions feature flag disabled' do + before do + stub_feature_flags(ci_variables_complex_expressions: false) + end - where(:expression, :value) do - '$PRESENT_VARIABLE == "my variable"' | true - '"my variable" == $PRESENT_VARIABLE' | true - '$PRESENT_VARIABLE == null' | false - '$EMPTY_VARIABLE == null' | false - '"" == $EMPTY_VARIABLE' | true - '$EMPTY_VARIABLE' | '' - '$UNDEFINED_VARIABLE == null' | true - 'null == $UNDEFINED_VARIABLE' | true - '$PRESENT_VARIABLE' | 'my variable' - '$UNDEFINED_VARIABLE' | nil - "$PRESENT_VARIABLE =~ /var.*e$/" | true - "$PRESENT_VARIABLE =~ /^var.*/" | false - "$EMPTY_VARIABLE =~ /var.*/" | false - "$UNDEFINED_VARIABLE =~ /var.*/" | false - "$PRESENT_VARIABLE =~ /VAR.*/i" | true - '$PRESENT_VARIABLE != "my variable"' | false - '"my variable" != $PRESENT_VARIABLE' | false - '$PRESENT_VARIABLE != null' | true - '$EMPTY_VARIABLE != null' | true - '"" != $EMPTY_VARIABLE' | false - '$UNDEFINED_VARIABLE != null' | false - 'null != $UNDEFINED_VARIABLE' | false - "$PRESENT_VARIABLE !~ /var.*e$/" | false - "$PRESENT_VARIABLE !~ /^var.*/" | true - "$EMPTY_VARIABLE !~ /var.*/" | true - "$UNDEFINED_VARIABLE !~ /var.*/" | true - "$PRESENT_VARIABLE !~ /VAR.*/i" | false - end + where(:expression, :value) do + '$PRESENT_VARIABLE == "my variable"' | true + '"my variable" == $PRESENT_VARIABLE' | true + '$PRESENT_VARIABLE == null' | false + '$EMPTY_VARIABLE == null' | false + '"" == $EMPTY_VARIABLE' | true + '$EMPTY_VARIABLE' | '' + '$UNDEFINED_VARIABLE == null' | true + 'null == $UNDEFINED_VARIABLE' | true + '$PRESENT_VARIABLE' | 'my variable' + '$UNDEFINED_VARIABLE' | nil + "$PRESENT_VARIABLE =~ /var.*e$/" | true + "$PRESENT_VARIABLE =~ /^var.*/" | false + "$EMPTY_VARIABLE =~ /var.*/" | false + "$UNDEFINED_VARIABLE =~ /var.*/" | false + "$PRESENT_VARIABLE =~ /VAR.*/i" | true + '$PATH_VARIABLE =~ /path/variable/' | true + '$PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true + '$PRESENT_VARIABLE != "my variable"' | false + '"my variable" != $PRESENT_VARIABLE' | false + '$PRESENT_VARIABLE != null' | true + '$EMPTY_VARIABLE != null' | true + '"" != $EMPTY_VARIABLE' | false + '$UNDEFINED_VARIABLE != null' | false + 'null != $UNDEFINED_VARIABLE' | false + "$PRESENT_VARIABLE !~ /var.*e$/" | false + "$PRESENT_VARIABLE !~ /^var.*/" | true + "$EMPTY_VARIABLE !~ /var.*/" | true + "$UNDEFINED_VARIABLE !~ /var.*/" | true + "$PRESENT_VARIABLE !~ /VAR.*/i" | false + end - with_them do - let(:text) { expression } + with_them do + let(:text) { expression } - it "evaluates to `#{params[:value].inspect}`" do - expect(subject.evaluate).to eq value + it "evaluates to `#{params[:value].inspect}`" do + expect(subject.evaluate).to eq value + end end end end @@ -137,6 +196,8 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do '$INVALID = 1' | false "$PRESENT_VARIABLE =~ /var.*/" | true "$UNDEFINED_VARIABLE =~ /var.*/" | false + "$PRESENT_VARIABLE !~ /var.*/" | false + "$UNDEFINED_VARIABLE !~ /var.*/" | true end with_them do -- cgit v1.2.1 From 2eecfd8f9d111c6518930b818a16daea8263b37f Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Tue, 4 Jun 2019 20:59:48 -0800 Subject: Use Redis for CacheMarkDownField on non AR models This allows using `CacheMarkdownField` for models that are not backed by ActiveRecord. When the including class inherits `ActiveRecord::Base` we include `Gitlab::MarkdownCache::ActiveRecord::Extension`. This will cause the markdown fields to be rendered and the generated HTML stored in a `_html` attribute on the record. We also store the version used for generating the markdown. All other classes that include this model will include the `Gitlab::MarkdownCache::Redis::Extension`. This add the `_html` attributes to that model and will generate the html in them. The generated HTML will be cached in redis under the key `markdown_cache::`. The class this included in must therefore respond to `id`. --- .../markdown/gitlab_flavored_markdown_spec.rb | 3 +- spec/helpers/markup_helper_spec.rb | 3 +- spec/lib/banzai/commit_renderer_spec.rb | 4 +- spec/lib/banzai/object_renderer_spec.rb | 28 +- spec/lib/banzai/renderer_spec.rb | 14 +- .../markdown_cache/active_record/extension_spec.rb | 177 ++++++++ spec/lib/gitlab/markdown_cache/field_data_spec.rb | 15 + .../gitlab/markdown_cache/redis/extension_spec.rb | 76 ++++ spec/lib/gitlab/markdown_cache/redis/store_spec.rb | 68 ++++ spec/models/concerns/cache_markdown_field_spec.rb | 446 +++++++-------------- spec/models/resource_label_event_spec.rb | 4 +- 11 files changed, 513 insertions(+), 325 deletions(-) create mode 100644 spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb create mode 100644 spec/lib/gitlab/markdown_cache/field_data_spec.rb create mode 100644 spec/lib/gitlab/markdown_cache/redis/extension_spec.rb create mode 100644 spec/lib/gitlab/markdown_cache/redis/store_spec.rb (limited to 'spec') diff --git a/spec/features/markdown/gitlab_flavored_markdown_spec.rb b/spec/features/markdown/gitlab_flavored_markdown_spec.rb index 6997ca48427..8fda3c7193e 100644 --- a/spec/features/markdown/gitlab_flavored_markdown_spec.rb +++ b/spec/features/markdown/gitlab_flavored_markdown_spec.rb @@ -20,8 +20,7 @@ describe "GitLab Flavored Markdown" do let(:commit) { project.commit } before do - allow_any_instance_of(Commit).to receive(:title) - .and_return("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details") + create_commit("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details", project, user, 'master') end it "renders title in commits#index" do diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index c3956ba08fd..597c8f836a9 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -78,7 +78,8 @@ describe MarkupHelper do let(:link) { '/commits/0a1b2c3d' } let(:issues) { create_list(:issue, 2, project: project) } - it 'handles references nested in links with all the text' do + # Clean the cache to make sure the title is re-rendered from the stubbed one + it 'handles references nested in links with all the text', :clean_gitlab_redis_cache do allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real") actual = helper.link_to_markdown_field(commit, :title, link) diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb index 1f53657c59c..316dbf052c3 100644 --- a/spec/lib/banzai/commit_renderer_spec.rb +++ b/spec/lib/banzai/commit_renderer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Banzai::CommitRenderer do - describe '.render' do + describe '.render', :clean_gitlab_redis_cache do it 'renders a commit description and title' do user = build(:user) project = create(:project, :repository) @@ -13,7 +13,7 @@ describe Banzai::CommitRenderer do described_class::ATTRIBUTES.each do |attr| expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original - expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, {}) + expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, { skip_project_check: false }).and_call_original end described_class.render([project.commit], project, user) diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 3b52f6666d0..7b855251a74 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -11,7 +11,7 @@ describe Banzai::ObjectRenderer do ) end - let(:object) { Note.new(note: 'hello', note_html: '

      hello

      ', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) } + let(:object) { Note.new(note: 'hello', note_html: '

      hello

      ', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) } describe '#render' do context 'with cache' do @@ -60,24 +60,38 @@ describe Banzai::ObjectRenderer do end context 'without cache' do - let(:commit) { project.commit } + let(:cacheless_class) do + Class.new do + attr_accessor :title, :redacted_title_html, :project + + def banzai_render_context(field) + { project: project, pipeline: :single_line } + end + end + end + let(:cacheless_thing) do + cacheless_class.new.tap do |thing| + thing.title = "Merge branch 'branch-merged' into 'master'" + thing.project = project + end + end it 'renders and redacts an Array of objects' do - renderer.render([commit], :title) + renderer.render([cacheless_thing], :title) - expect(commit.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'") + expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'") end it 'calls Banzai::Redactor to perform redaction' do expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original - renderer.render([commit], :title) + renderer.render([cacheless_thing], :title) end it 'retrieves field content using Banzai::Renderer.cacheless_render_field' do - expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title, {}).and_call_original + expect(Banzai::Renderer).to receive(:cacheless_render_field).with(cacheless_thing, :title, {}).and_call_original - renderer.render([commit], :title) + renderer.render([cacheless_thing], :title) end end end diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb index 650cecfc778..aa828e2f0e9 100644 --- a/spec/lib/banzai/renderer_spec.rb +++ b/spec/lib/banzai/renderer_spec.rb @@ -11,16 +11,24 @@ describe Banzai::Renderer do object end + def fake_cacheless_object + object = double('cacheless object') + + allow(object).to receive(:respond_to?).with(:cached_markdown_fields).and_return(false) + + object + end + describe '#render_field' do let(:renderer) { described_class } context 'without cache' do - let(:commit) { create(:project, :repository).commit } + let(:commit) { fake_cacheless_object } it 'returns cacheless render field' do - expect(renderer).to receive(:cacheless_render_field).with(commit, :title, {}) + expect(renderer).to receive(:cacheless_render_field).with(commit, :field, {}) - renderer.render_field(commit, :title) + renderer.render_field(commit, :field) end end diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb new file mode 100644 index 00000000000..6700b53e790 --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::MarkdownCache::ActiveRecord::Extension do + class ARThingWithMarkdownFields < ActiveRecord::Base + self.table_name = 'issues' + include CacheMarkdownField + cache_markdown_field :title, whitelisted: true + cache_markdown_field :description, pipeline: :single_line + + attr_accessor :author, :project + end + + let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } + let(:thing) { ARThingWithMarkdownFields.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + + let(:markdown) { '`Foo`' } + let(:html) { '

      Foo

      ' } + + let(:updated_markdown) { '`Bar`' } + let(:updated_html) { '

      Bar

      ' } + + context 'an unchanged markdown field' do + let(:thing) { ARThingWithMarkdownFields.new(title: markdown) } + + before do + thing.title = thing.title + thing.save + end + + it { expect(thing.title).to eq(markdown) } + it { expect(thing.title_html).to eq(html) } + it { expect(thing.title_html_changed?).not_to be_truthy } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'a changed markdown field' do + let(:thing) { ARThingWithMarkdownFields.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + + before do + thing.title = updated_markdown + thing.save + end + + it { expect(thing.title_html).to eq(updated_html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'when a markdown field is set repeatedly to an empty string' do + it do + expect(thing).to receive(:refresh_markdown_cache).once + thing.title = '' + thing.save + thing.title = '' + thing.save + end + end + + context 'when a markdown field is set repeatedly to a string which renders as empty html' do + it do + expect(thing).to receive(:refresh_markdown_cache).once + thing.title = '[//]: # (This is also a comment.)' + thing.save + thing.title = '[//]: # (This is also a comment.)' + thing.save + end + end + + context 'a non-markdown field changed' do + let(:thing) { ARThingWithMarkdownFields.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + + before do + thing.state = 'closed' + thing.save + end + + it { expect(thing.state).to eq('closed') } + it { expect(thing.title).to eq(markdown) } + it { expect(thing.title_html).to eq(html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'version is out of date' do + let(:thing) { ARThingWithMarkdownFields.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) } + + before do + thing.save + end + + it { expect(thing.title_html).to eq(updated_html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'when an invalidating field is changed' do + it 'invalidates the cache when project changes' do + thing.project = :new_project + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + thing.save + + expect(thing.title_html).to eq(updated_html) + expect(thing.description_html).to eq(updated_html) + expect(thing.cached_markdown_version).to eq(cache_version) + end + + it 'invalidates the cache when author changes' do + thing.author = :new_author + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + thing.save + + expect(thing.title_html).to eq(updated_html) + expect(thing.description_html).to eq(updated_html) + expect(thing.cached_markdown_version).to eq(cache_version) + end + end + + describe '.attributes' do + it 'excludes cache attributes that is blacklisted by default' do + expect(thing.attributes.keys.sort).not_to include(%w[description_html]) + end + end + + describe '#cached_html_up_to_date?' do + let(:thing) { ARThingWithMarkdownFields.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) } + subject { thing.cached_html_up_to_date?(:title) } + + it 'returns false if markdown has been changed but html has not' do + thing.title = "changed!" + + is_expected.to be_falsy + end + + it 'returns true if markdown has not been changed but html has' do + thing.title_html = updated_html + + is_expected.to be_truthy + end + + it 'returns true if markdown and html have both been changed' do + thing.title = updated_markdown + thing.title_html = updated_html + + is_expected.to be_truthy + end + + it 'returns false if the markdown field is set but the html is not' do + thing.title_html = nil + + is_expected.to be_falsy + end + end + + describe '#refresh_markdown_cache!' do + before do + thing.title = updated_markdown + end + + it 'skips saving if not persisted' do + expect(thing).to receive(:persisted?).and_return(false) + expect(thing).not_to receive(:update_columns) + + thing.refresh_markdown_cache! + end + + it 'saves the changes' do + expect(thing).to receive(:persisted?).and_return(true) + + expect(thing).to receive(:update_columns) + .with("title_html" => updated_html, + "description_html" => "", + "cached_markdown_version" => cache_version) + + thing.refresh_markdown_cache! + end + end +end diff --git a/spec/lib/gitlab/markdown_cache/field_data_spec.rb b/spec/lib/gitlab/markdown_cache/field_data_spec.rb new file mode 100644 index 00000000000..393bf85aa43 --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/field_data_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::MarkdownCache::FieldData do + subject(:field_data) { described_class.new } + + before do + field_data[:description] = { project: double('project in context') } + end + + it 'translates a markdown field name into a html field name' do + expect(field_data.html_field(:description)).to eq("description_html") + end +end diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb new file mode 100644 index 00000000000..d3d3cd6f03c --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do + class ThingWithMarkdownFields + include CacheMarkdownField + + def initialize(title: nil, description: nil) + @title, @description = title, description + end + + attr_reader :title, :description + + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description + + def id + "test-markdown-cache" + end + end + + let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } + let(:thing) { ThingWithMarkdownFields.new(title: "`Hello`", description: "`World`") } + let(:expected_cache_key) { "markdown_cache:ThingWithMarkdownFields:test-markdown-cache" } + + it 'defines the html attributes' do + thing = ThingWithMarkdownFields.new + + expect(thing).to respond_to(:title_html, :description_html, :cached_markdown_version) + end + + it 'loads the markdown from the cache only once' do + expect(thing).to receive(:load_cached_markdown).once.and_call_original + + thing.title_html + thing.description_html + end + + it 'correctly loads the markdown if it was stored in redis' do + Gitlab::Redis::Cache.with do |r| + r.mapped_hmset(expected_cache_key, + title_html: 'hello', + description_html: 'world', + cached_markdown_version: cache_version) + end + + expect(thing.title_html).to eq('hello') + expect(thing.description_html).to eq('world') + expect(thing.cached_markdown_version).to eq(cache_version) + end + + describe "#refresh_markdown_cache!" do + it "stores the value in redis" do + expected_results = { "title_html" => "`Hello`", + "description_html" => "

      World

      ", + "cached_markdown_version" => cache_version.to_s } + + thing.refresh_markdown_cache! + + results = Gitlab::Redis::Cache.with do |r| + r.mapped_hmget(expected_cache_key, + "title_html", "description_html", "cached_markdown_version") + end + + expect(results).to eq(expected_results) + end + + it "assigns the values" do + thing.refresh_markdown_cache! + + expect(thing.title_html).to eq('`Hello`') + expect(thing.description_html).to eq("

      World

      ") + expect(thing.cached_markdown_version).to eq(cache_version) + end + end +end diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb new file mode 100644 index 00000000000..59c038cfb2f --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do + let(:storable_class) do + Class.new do + cattr_reader :cached_markdown_fields do + Gitlab::MarkdownCache::FieldData.new.tap do |field_data| + field_data[:field_1] = {} + field_data[:field_2] = {} + end + end + + attr_accessor :field_1, :field_2, :field_1_html, :field_2_html, :cached_markdown_version + + def id + 'test-redisbacked-store' + end + end + end + let(:storable) { storable_class.new } + let(:cache_key) { "markdown_cache:#{storable_class}:#{storable.id}" } + + subject(:store) { described_class.new(storable) } + + def read_values + Gitlab::Redis::Cache.with do |r| + r.mapped_hmget(cache_key, + :field_1_html, :field_2_html, :cached_markdown_version) + end + end + + def store_values(values) + Gitlab::Redis::Cache.with do |r| + r.mapped_hmset(cache_key, + values) + end + end + + describe '#save' do + it 'stores updates to html fields and version' do + values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 } + + store.save(values_to_store) + + expect(read_values) + .to eq({ field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1" }) + end + end + + describe '#read' do + it 'reads the html fields and version from redis if they were stored' do + stored_values = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 } + + store_values(stored_values) + + expect(store.read.symbolize_keys).to eq(stored_values) + end + + it 'is mared loaded after reading' do + expect(store).not_to be_loaded + + store.read + + expect(store).to be_loaded + end + end +end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 78637ff10c6..52f8b052ad4 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -2,383 +2,213 @@ require 'spec_helper' -describe CacheMarkdownField do - # The minimum necessary ActiveModel to test this concern - class ThingWithMarkdownFields - include ActiveModel::Model - include ActiveModel::Dirty - - include ActiveModel::Serialization - - class_attribute :attribute_names - self.attribute_names = [] - - def attributes - attribute_names.each_with_object({}) do |name, hsh| - hsh[name.to_s] = send(name) - end +describe CacheMarkdownField, :clean_gitlab_redis_cache do + let(:ar_class) do + Class.new(ActiveRecord::Base) do + self.table_name = 'issues' + include CacheMarkdownField + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description end + end - extend ActiveModel::Callbacks - define_model_callbacks :create, :update - - include CacheMarkdownField - cache_markdown_field :foo - cache_markdown_field :baz, pipeline: :single_line - cache_markdown_field :zoo, whitelisted: true + let(:other_class) do + Class.new do + include CacheMarkdownField - def self.add_attr(name) - self.attribute_names += [name] - define_attribute_methods(name) - attr_reader(name) - define_method("#{name}=") do |value| - write_attribute(name, value) + def initialize(args = {}) + @title, @description, @cached_markdown_version = args[:title], args[:description], args[:cached_markdown_version] + @title_html, @description_html = args[:title_html], args[:description_html] + @author, @project = args[:author], args[:project] end - end - add_attr :cached_markdown_version + attr_accessor :title, :description, :cached_markdown_version - [:foo, :foo_html, :bar, :baz, :baz_html, :zoo, :zoo_html].each do |name| - add_attr(name) - end - - def initialize(*) - super - - # Pretend new is load - clear_changes_information - end - - def read_attribute(name) - instance_variable_get("@#{name}") - end - - def write_attribute(name, value) - send("#{name}_will_change!") unless value == read_attribute(name) - instance_variable_set("@#{name}", value) - end + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description - def save - run_callbacks :update do - changes_applied + def id + "test-markdown-cache" end end - - def has_attribute?(attr_name) - attribute_names.include?(attr_name) - end - end - - def thing_subclass(new_attr) - Class.new(ThingWithMarkdownFields) { add_attr(new_attr) } end let(:markdown) { '`Foo`' } - let(:html) { '

      Foo

      ' } + let(:html) { '

      Foo

      ' } let(:updated_markdown) { '`Bar`' } - let(:updated_html) { '

      Bar

      ' } - - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } - - before do - stub_commonmark_sourcepos_disabled - end + let(:updated_html) { '

      Bar

      ' } - describe '.attributes' do - it 'excludes cache attributes that is blacklisted by default' do - expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html]) - end - end - - context 'an unchanged markdown field' do - before do - thing.foo = thing.foo - thing.save - end + let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } - it { expect(thing.foo).to eq(markdown) } - it { expect(thing.foo_html).to eq(html) } - it { expect(thing.foo_html_changed?).not_to be_truthy } - it { expect(thing.cached_markdown_version).to eq(cache_version) } + def thing_subclass(klass, extra_attribute) + Class.new(klass) { attr_accessor(extra_attribute) } end - context 'a changed markdown field' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } + shared_examples 'a class with cached markdown fields' do + describe '#cached_html_up_to_date?' do + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } - before do - thing.foo = updated_markdown - thing.save - end + subject { thing.cached_html_up_to_date?(:title) } - it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } - end + it 'returns false when the version is absent' do + thing.cached_markdown_version = nil - context 'when a markdown field is set repeatedly to an empty string' do - it do - expect(thing).to receive(:refresh_markdown_cache).once - thing.foo = '' - thing.save - thing.foo = '' - thing.save - end - end - - context 'when a markdown field is set repeatedly to a string which renders as empty html' do - it do - expect(thing).to receive(:refresh_markdown_cache).once - thing.foo = '[//]: # (This is also a comment.)' - thing.save - thing.foo = '[//]: # (This is also a comment.)' - thing.save - end - end - - context 'when a markdown field and html field are both changed' do - it do - expect(thing).not_to receive(:refresh_markdown_cache) - thing.foo = '_look over there!_' - thing.foo_html = 'look over there!' - thing.save - end - end - - context 'a non-markdown field changed' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } - - before do - thing.bar = 'OK' - thing.save - end - - it { expect(thing.bar).to eq('OK') } - it { expect(thing.foo).to eq(markdown) } - it { expect(thing.foo_html).to eq(html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } - end - - context 'version is out of date' do - let(:thing) { ThingWithMarkdownFields.new(foo: updated_markdown, foo_html: html, cached_markdown_version: nil) } - - before do - thing.save - end - - it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } - end - - describe '#cached_html_up_to_date?' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - - subject { thing.cached_html_up_to_date?(:foo) } - - it 'returns false when the version is absent' do - thing.cached_markdown_version = nil - - is_expected.to be_falsy - end - - it 'returns false when the cached version is too old' do - thing.cached_markdown_version = cache_version - 1 - - is_expected.to be_falsy - end - - it 'returns false when the cached version is in future' do - thing.cached_markdown_version = cache_version + 1 - - is_expected.to be_falsy - end - - it 'returns false when the local version was bumped' do - allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) - thing.cached_markdown_version = cache_version - - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true when the local version is default' do - thing.cached_markdown_version = cache_version + it 'returns false when the version is too early' do + thing.cached_markdown_version -= 1 - is_expected.to be_truthy - end + is_expected.to be_falsy + end - it 'returns true when the cached version is just right' do - allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) - thing.cached_markdown_version = cache_version + 2 + it 'returns false when the version is too late' do + thing.cached_markdown_version += 1 - is_expected.to be_truthy - end + is_expected.to be_falsy + end - it 'returns false if markdown has been changed but html has not' do - thing.foo = updated_html + it 'returns false when the local version was bumped' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + thing.cached_markdown_version = cache_version - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true if markdown has not been changed but html has' do - thing.foo_html = updated_html + it 'returns true when the local version is default' do + thing.cached_markdown_version = cache_version - is_expected.to be_truthy - end + is_expected.to be_truthy + end - it 'returns true if markdown and html have both been changed' do - thing.foo = updated_markdown - thing.foo_html = updated_html + it 'returns true when the cached version is just right' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + thing.cached_markdown_version = cache_version + 2 - is_expected.to be_truthy + is_expected.to be_truthy + end end - it 'returns false if the markdown field is set but the html is not' do - thing.foo_html = nil + describe '#latest_cached_markdown_version' do + let(:thing) { klass.new } + subject { thing.latest_cached_markdown_version } - is_expected.to be_falsy + it 'returns default version' do + thing.cached_markdown_version = nil + is_expected.to eq(cache_version) + end end - end - - describe '#latest_cached_markdown_version' do - subject { thing.latest_cached_markdown_version } - it 'returns default version' do - thing.cached_markdown_version = nil - is_expected.to eq(cache_version) - end - end + describe '#refresh_markdown_cache' do + let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) } - describe '#refresh_markdown_cache' do - before do - thing.foo = updated_markdown - end + before do + thing.description = updated_markdown + end - it 'fills all html fields' do - thing.refresh_markdown_cache + it 'fills all html fields' do + thing.refresh_markdown_cache - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + expect(thing.description_html).to eq(updated_html) + end - it 'does not save the result' do - expect(thing).not_to receive(:update_columns) + it 'does not save the result' do + expect(thing).not_to receive(:save_markdown) - thing.refresh_markdown_cache - end + thing.refresh_markdown_cache + end - it 'updates the markdown cache version' do - thing.cached_markdown_version = nil - thing.refresh_markdown_cache + it 'updates the markdown cache version' do + thing.cached_markdown_version = nil + thing.refresh_markdown_cache - expect(thing.cached_markdown_version).to eq(cache_version) + expect(thing.cached_markdown_version).to eq(cache_version) + end end - end - - describe '#refresh_markdown_cache!' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - before do - thing.foo = updated_markdown - end + describe '#refresh_markdown_cache!' do + let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) } - it 'fills all html fields' do - thing.refresh_markdown_cache! + before do + thing.description = updated_markdown + end - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + it 'fills all html fields' do + thing.refresh_markdown_cache! - it 'skips saving if not persisted' do - expect(thing).to receive(:persisted?).and_return(false) - expect(thing).not_to receive(:update_columns) + expect(thing.description_html).to eq(updated_html) + end - thing.refresh_markdown_cache! - end + it 'saves the changes' do + expect(thing) + .to receive(:save_markdown) + .with("description_html" => updated_html, "title_html" => "", "cached_markdown_version" => cache_version) - it 'saves the changes using #update_columns' do - expect(thing).to receive(:persisted?).and_return(true) - expect(thing).to receive(:update_columns) - .with( - "foo_html" => updated_html, - "baz_html" => "", - "zoo_html" => "", - "cached_markdown_version" => cache_version - ) - - thing.refresh_markdown_cache! + thing.refresh_markdown_cache! + end end - end - describe '#banzai_render_context' do - subject(:context) { thing.banzai_render_context(:foo) } + describe '#banzai_render_context' do + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + subject(:context) { thing.banzai_render_context(:title) } - it 'sets project to nil if the object lacks a project' do - is_expected.to have_key(:project) - expect(context[:project]).to be_nil - end + it 'sets project to nil if the object lacks a project' do + is_expected.to have_key(:project) + expect(context[:project]).to be_nil + end - it 'excludes author if the object lacks an author' do - is_expected.not_to have_key(:author) - end + it 'excludes author if the object lacks an author' do + is_expected.not_to have_key(:author) + end - it 'raises if the context for an unrecognised field is requested' do - expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError) - end + it 'raises if the context for an unrecognised field is requested' do + expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError) + end - it 'includes the pipeline' do - baz = thing.banzai_render_context(:baz) + it 'includes the pipeline' do + title_context = thing.banzai_render_context(:title) - expect(baz[:pipeline]).to eq(:single_line) - end + expect(title_context[:pipeline]).to eq(:single_line) + end - it 'returns copies of the context template' do - template = thing.cached_markdown_fields[:baz] - copy = thing.banzai_render_context(:baz) + it 'returns copies of the context template' do + template = thing.cached_markdown_fields[:description] + copy = thing.banzai_render_context(:description) - expect(copy).not_to be(template) - end + expect(copy).not_to be(template) + end - context 'with a project' do - let(:project) { create(:project, group: create(:group)) } - let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) } + context 'with a project' do + let(:project) { build(:project, group: create(:group)) } + let(:thing) { thing_subclass(klass, :project).new(title: markdown, title_html: html, project: project) } - it 'sets the project in the context' do - is_expected.to have_key(:project) - expect(context[:project]).to eq(project) + it 'sets the project in the context' do + is_expected.to have_key(:project) + expect(context[:project]).to eq(project) + end end - it 'invalidates the cache when project changes' do - thing.project = :new_project - allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) - - thing.save + context 'with an author' do + let(:thing) { thing_subclass(klass, :author).new(title: markdown, title_html: html, author: :author_value) } - expect(thing.foo_html).to eq(updated_html) - expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(cache_version) + it 'sets the author in the context' do + is_expected.to have_key(:author) + expect(context[:author]).to eq(:author_value) + end end end + end - context 'with an author' do - let(:thing) { thing_subclass(:author).new(foo: markdown, foo_html: html, author: :author_value) } - - it 'sets the author in the context' do - is_expected.to have_key(:author) - expect(context[:author]).to eq(:author_value) - end + context 'for Active record classes' do + let(:klass) { ar_class } - it 'invalidates the cache when author changes' do - thing.author = :new_author - allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + it_behaves_like 'a class with cached markdown fields' + end - thing.save + context 'for other classes' do + let(:klass) { other_class } - expect(thing.foo_html).to eq(updated_html) - expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(cache_version) - end - end + it_behaves_like 'a class with cached markdown fields' end end diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb index 7eeb2fae57d..cb52f154299 100644 --- a/spec/models/resource_label_event_spec.rb +++ b/spec/models/resource_label_event_spec.rb @@ -82,13 +82,13 @@ RSpec.describe ResourceLabelEvent, type: :model do end it 'returns true if markdown is outdated' do - subject.attributes = { cached_markdown_version: ((CacheMarkdownField::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 } + subject.attributes = { cached_markdown_version: ((Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 } expect(subject.outdated_markdown?).to be true end it 'returns false if label and reference are set' do - subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } + subject.attributes = { reference: 'whatever', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } expect(subject.outdated_markdown?).to be false end -- cgit v1.2.1 From 56d52340da0f8f15179b83f1206544a2590c22ff Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Wed, 5 Jun 2019 13:50:37 +0800 Subject: Use #cache_key of subject for generated redis key This commit also includes some changes in specs to use `Class.new` approach. --- .../markdown_cache/active_record/extension_spec.rb | 26 ++++++++++-------- .../gitlab/markdown_cache/redis/extension_spec.rb | 32 ++++++++++++---------- spec/lib/gitlab/markdown_cache/redis/store_spec.rb | 6 +++- spec/models/concerns/cache_markdown_field_spec.rb | 4 +++ 4 files changed, 41 insertions(+), 27 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb index 6700b53e790..18052b1991c 100644 --- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb +++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb @@ -2,17 +2,19 @@ require 'spec_helper' describe Gitlab::MarkdownCache::ActiveRecord::Extension do - class ARThingWithMarkdownFields < ActiveRecord::Base - self.table_name = 'issues' - include CacheMarkdownField - cache_markdown_field :title, whitelisted: true - cache_markdown_field :description, pipeline: :single_line + let(:klass) do + Class.new(ActiveRecord::Base) do + self.table_name = 'issues' + include CacheMarkdownField + cache_markdown_field :title, whitelisted: true + cache_markdown_field :description, pipeline: :single_line - attr_accessor :author, :project + attr_accessor :author, :project + end end let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } - let(:thing) { ARThingWithMarkdownFields.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } let(:markdown) { '`Foo`' } let(:html) { '

      Foo

      ' } @@ -21,7 +23,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do let(:updated_html) { '

      Bar

      ' } context 'an unchanged markdown field' do - let(:thing) { ARThingWithMarkdownFields.new(title: markdown) } + let(:thing) { klass.new(title: markdown) } before do thing.title = thing.title @@ -35,7 +37,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do end context 'a changed markdown field' do - let(:thing) { ARThingWithMarkdownFields.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } before do thing.title = updated_markdown @@ -67,7 +69,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do end context 'a non-markdown field changed' do - let(:thing) { ARThingWithMarkdownFields.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } before do thing.state = 'closed' @@ -81,7 +83,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do end context 'version is out of date' do - let(:thing) { ARThingWithMarkdownFields.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) } + let(:thing) { klass.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) } before do thing.save @@ -122,7 +124,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do end describe '#cached_html_up_to_date?' do - let(:thing) { ARThingWithMarkdownFields.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) } + let(:thing) { klass.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) } subject { thing.cached_html_up_to_date?(:title) } it 'returns false if markdown has been changed but html has not' do diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb index d3d3cd6f03c..24cfb399cc3 100644 --- a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb +++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb @@ -2,30 +2,34 @@ require 'spec_helper' describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do - class ThingWithMarkdownFields - include CacheMarkdownField + let(:klass) do + Class.new do + include CacheMarkdownField - def initialize(title: nil, description: nil) - @title, @description = title, description - end + def initialize(title: nil, description: nil) + @title, @description = title, description + end + + attr_reader :title, :description - attr_reader :title, :description + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description - cache_markdown_field :title, pipeline: :single_line - cache_markdown_field :description + def id + "test-markdown-cache" + end - def id - "test-markdown-cache" + def cache_key + "cache-key" + end end end let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } - let(:thing) { ThingWithMarkdownFields.new(title: "`Hello`", description: "`World`") } - let(:expected_cache_key) { "markdown_cache:ThingWithMarkdownFields:test-markdown-cache" } + let(:thing) { klass.new(title: "`Hello`", description: "`World`") } + let(:expected_cache_key) { "markdown_cache:cache-key" } it 'defines the html attributes' do - thing = ThingWithMarkdownFields.new - expect(thing).to respond_to(:title_html, :description_html, :cached_markdown_version) end diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb index 59c038cfb2f..e7701cf1306 100644 --- a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb +++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb @@ -16,10 +16,14 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do def id 'test-redisbacked-store' end + + def cache_key + "cache-key" + end end end let(:storable) { storable_class.new } - let(:cache_key) { "markdown_cache:#{storable_class}:#{storable.id}" } + let(:cache_key) { "markdown_cache:#{storable.cache_key}" } subject(:store) { described_class.new(storable) } diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 52f8b052ad4..9e6b5d56805 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -30,6 +30,10 @@ describe CacheMarkdownField, :clean_gitlab_redis_cache do def id "test-markdown-cache" end + + def cache_key + "cache-key" + end end end -- cgit v1.2.1 From 7aac4e5c3f290a4ae32ef0ca332a2ddac6bff774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Wed, 5 Jun 2019 07:27:08 +0000 Subject: Fix whitespace changes visibility when the related file was initially collapsed --- spec/javascripts/diffs/store/actions_spec.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index 6309a8823d7..f129fbb57a3 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -396,6 +396,7 @@ describe('DiffsStoreActions', () => { }); describe('loadCollapsedDiff', () => { + const state = { showWhitespace: true }; it('should fetch data and call mutation with response and the give parameter', done => { const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' }; const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] }; @@ -403,7 +404,7 @@ describe('DiffsStoreActions', () => { const commit = jasmine.createSpy('commit'); mock.onGet(file.loadCollapsedDiffUrl).reply(200, data); - loadCollapsedDiff({ commit, getters: { commitId: null } }, file) + loadCollapsedDiff({ commit, getters: { commitId: null }, state }, file) .then(() => { expect(commit).toHaveBeenCalledWith(types.ADD_COLLAPSED_DIFFS, { file, data }); @@ -421,10 +422,10 @@ describe('DiffsStoreActions', () => { spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} })); - loadCollapsedDiff({ commit() {}, getters }, file); + loadCollapsedDiff({ commit() {}, getters, state }, file); expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, { - params: { commit_id: null }, + params: { commit_id: null, w: '0' }, }); }); @@ -436,10 +437,10 @@ describe('DiffsStoreActions', () => { spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} })); - loadCollapsedDiff({ commit() {}, getters }, file); + loadCollapsedDiff({ commit() {}, getters, state }, file); expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, { - params: { commit_id: '123' }, + params: { commit_id: '123', w: '0' }, }); }); }); -- cgit v1.2.1 From 4644a2daf5ec5e86e2b2989f04e99e4f081f6fef Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Jun 2019 14:38:18 +0100 Subject: Add web_url to tree entry in GraphQL API --- .../frontend/repository/components/table/row_spec.js | 12 ++++++++++++ spec/graphql/types/tree/blob_type_spec.rb | 2 +- spec/graphql/types/tree/tree_entry_type_spec.rb | 2 +- .../gitlab/graphql/representation/tree_entry_spec.rb | 20 ++++++++++++++++++++ spec/presenters/blob_presenter_spec.rb | 10 ++++++++++ spec/presenters/tree_entry_presenter_spec.rb | 16 ++++++++++++++++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 spec/lib/gitlab/graphql/representation/tree_entry_spec.rb create mode 100644 spec/presenters/tree_entry_presenter_spec.rb (limited to 'spec') diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 6b4508c418e..a70dc7bb866 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -86,4 +86,16 @@ describe('Repository table row component', () => { expect(vm.find('.commit-sha').text()).toContain('1'); }); + + it('renders link with href', () => { + factory({ + id: '1', + path: 'test', + type: 'blob', + url: 'https://test.com', + currentPath: '/', + }); + + expect(vm.find('a').attributes('href')).toEqual('https://test.com'); + }); }); diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb index fa29bb5fff7..b12e214ca84 100644 --- a/spec/graphql/types/tree/blob_type_spec.rb +++ b/spec/graphql/types/tree/blob_type_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' describe Types::Tree::BlobType do it { expect(described_class.graphql_name).to eq('Blob') } - it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) } + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) } end diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb index 397cabde8e5..ea1b6426034 100644 --- a/spec/graphql/types/tree/tree_entry_type_spec.rb +++ b/spec/graphql/types/tree/tree_entry_type_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' describe Types::Tree::TreeEntryType do it { expect(described_class.graphql_name).to eq('TreeEntry') } - it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) } + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) } end diff --git a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb new file mode 100644 index 00000000000..d45e690160c --- /dev/null +++ b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Representation::TreeEntry do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + + describe '.decorate' do + it 'returns NilClass when given nil' do + expect(described_class.decorate(nil, repository)).to be_nil + end + + it 'returns array of TreeEntry' do + entries = described_class.decorate(repository.tree.blobs, repository) + + expect(entries.first).to be_a(described_class) + end + end +end diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index bb1db9a3d51..eacf383be7d 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -14,6 +14,16 @@ describe BlobPresenter, :seed_helper do end let(:blob) { Blob.new(git_blob) } + describe '.web_url' do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, repository) } + + subject { described_class.new(blob) } + + it { expect(subject.web_url).to eq("http://localhost/#{project.full_path}/blob/#{blob.commit_id}/#{blob.path}") } + end + describe '#highlight' do subject { described_class.new(blob) } diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb new file mode 100644 index 00000000000..d74ee5dc28f --- /dev/null +++ b/spec/presenters/tree_entry_presenter_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe TreeEntryPresenter do + include Gitlab::Routing.url_helpers + + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:tree) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.trees.first, repository) } + let(:presenter) { described_class.new(tree) } + + describe '.web_url' do + it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/tree/#{tree.commit_id}/#{tree.path}") } + end +end -- cgit v1.2.1 From 1f2244f16bc2990000a77911520b0c06095522c2 Mon Sep 17 00:00:00 2001 From: Wolphin Date: Wed, 5 Jun 2019 08:25:55 +0000 Subject: Add multiple extends support --- spec/lib/gitlab/ci/config/entry/job_spec.rb | 2 +- spec/lib/gitlab/ci/config/extendable/entry_spec.rb | 46 +++++++++++++++++----- spec/lib/gitlab/ci/yaml_processor_spec.rb | 2 +- 3 files changed, 39 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 0560eb42e4d..e0552ae8c57 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -94,7 +94,7 @@ describe Gitlab::Ci::Config::Entry::Job do it 'returns error about wrong value type' do expect(entry).not_to be_valid - expect(entry.errors).to include "job extends should be a string" + expect(entry.errors).to include "job extends should be an array of strings or a string" end end diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb index 0a148375d11..d63612053b6 100644 --- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb +++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb @@ -44,12 +44,12 @@ describe Gitlab::Ci::Config::Extendable::Entry do end end - describe '#extends_key' do + describe '#extends_keys' do context 'when entry is extensible' do it 'returns symbolized extends key value' do entry = described_class.new(:test, test: { extends: 'something' }) - expect(entry.extends_key).to eq :something + expect(entry.extends_keys).to eq [:something] end end @@ -57,7 +57,7 @@ describe Gitlab::Ci::Config::Extendable::Entry do it 'returns nil' do entry = described_class.new(:test, test: 'something') - expect(entry.extends_key).to be_nil + expect(entry.extends_keys).to be_nil end end end @@ -76,7 +76,7 @@ describe Gitlab::Ci::Config::Extendable::Entry do end end - describe '#base_hash!' do + describe '#base_hashes!' do subject { described_class.new(:test, hash) } context 'when base hash is not extensible' do @@ -87,8 +87,8 @@ describe Gitlab::Ci::Config::Extendable::Entry do } end - it 'returns unchanged base hash' do - expect(subject.base_hash!).to eq(script: 'rspec') + it 'returns unchanged base hashes' do + expect(subject.base_hashes!).to eq([{ script: 'rspec' }]) end end @@ -101,12 +101,12 @@ describe Gitlab::Ci::Config::Extendable::Entry do } end - it 'extends the base hash first' do - expect(subject.base_hash!).to eq(extends: 'first', script: 'rspec') + it 'extends the base hashes first' do + expect(subject.base_hashes!).to eq([{ extends: 'first', script: 'rspec' }]) end it 'mutates original context' do - subject.base_hash! + subject.base_hashes! expect(hash.fetch(:second)).to eq(extends: 'first', script: 'rspec') end @@ -171,6 +171,34 @@ describe Gitlab::Ci::Config::Extendable::Entry do end end + context 'when extending multiple hashes correctly' do + let(:hash) do + { + first: { script: 'my value', image: 'ubuntu' }, + second: { image: 'alpine' }, + test: { extends: %w(first second) } + } + end + + let(:result) do + { + first: { script: 'my value', image: 'ubuntu' }, + second: { image: 'alpine' }, + test: { extends: %w(first second), script: 'my value', image: 'alpine' } + } + end + + it 'returns extended part of the hash' do + expect(subject.extend!).to eq result[:test] + end + + it 'mutates original context' do + subject.extend! + + expect(hash).to eq result + end + end + context 'when hash is not extensible' do let(:hash) do { diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 29276d5b686..635b4e556e8 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1470,7 +1470,7 @@ module Gitlab expect { Gitlab::Ci::YamlProcessor.new(config) } .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'rspec: unknown key in `extends`') + 'rspec: unknown keys in `extends` (something)') end end -- cgit v1.2.1 From e58e24b4d4256a42c0c7e764227f3c3c53fdb1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 5 Jun 2019 11:09:51 +0000 Subject: Revert "Merge branch..." This reverts merge request !27503 --- spec/features/projects/jobs_spec.rb | 6 +++--- spec/features/projects/pipelines/pipeline_spec.rb | 8 ++++---- spec/lib/gitlab/ci/status/build/factory_spec.rb | 8 ++++---- spec/models/ci/build_spec.rb | 4 ++-- spec/requests/api/jobs_spec.rb | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) (limited to 'spec') diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index d0878c4088a..03562bd382e 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -936,8 +936,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do find('.js-cancel-job').click end - it 'loads the page and shows no controls' do - expect(page).not_to have_content 'Retry' + it 'loads the page and shows all needed controls' do + expect(page).to have_content 'Retry' end end end @@ -946,7 +946,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context "Job from project", :js do before do job.run! - job.drop!(:script_failure) + job.cancel! visit project_job_path(project, job) wait_for_requests diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 506aa867490..25b3ac00604 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -115,11 +115,11 @@ describe 'Pipeline', :js do end end - it 'cancels the running build and does not show retry button' do + it 'cancels the running build and shows retry button' do find('#ci-badge-deploy .ci-action-icon-container').click page.within('#ci-badge-deploy') do - expect(page).not_to have_css('.js-icon-retry') + expect(page).to have_css('.js-icon-retry') end end end @@ -133,11 +133,11 @@ describe 'Pipeline', :js do end end - it 'cancels the preparing build and does not show retry button' do + it 'cancels the preparing build and shows retry button' do find('#ci-badge-deploy .ci-action-icon-container').click page.within('#ci-badge-deploy') do - expect(page).not_to have_css('.js-icon-retry') + expect(page).to have_css('.js-icon-retry') end end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 025439f1b6e..b6231510b91 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -163,11 +163,11 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Canceled] + .to eq [Gitlab::Ci::Status::Build::Canceled, Gitlab::Ci::Status::Build::Retryable] end - it 'does not fabricate a retryable build status' do - expect(status).not_to be_a Gitlab::Ci::Status::Build::Retryable + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable end it 'fabricates status with correct details' do @@ -177,7 +177,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.illustration).to include(:image, :size, :title) expect(status.label).to eq 'canceled' expect(status).to have_details - expect(status).not_to have_action + expect(status).to have_action end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 89d18abee27..d98db024f73 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1294,7 +1294,7 @@ describe Ci::Build do build.cancel! end - it { is_expected.not_to be_retryable } + it { is_expected.to be_retryable } end end @@ -1824,7 +1824,7 @@ describe Ci::Build do context 'when build has been canceled' do subject { build_stubbed(:ci_build, :manual, status: :canceled) } - it { is_expected.not_to be_playable } + it { is_expected.to be_playable } end context 'when build is successful' do diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 7208cec357a..89ee6f896f9 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -864,7 +864,7 @@ describe API::Jobs do end describe 'POST /projects/:id/jobs/:job_id/retry' do - let(:job) { create(:ci_build, :failed, pipeline: pipeline) } + let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } before do post api("/projects/#{project.id}/jobs/#{job.id}/retry", api_user) @@ -874,7 +874,7 @@ describe API::Jobs do context 'user with :update_build permission' do it 'retries non-running job' do expect(response).to have_gitlab_http_status(201) - expect(project.builds.first.status).to eq('failed') + expect(project.builds.first.status).to eq('canceled') expect(json_response['status']).to eq('pending') end end -- cgit v1.2.1 From 5a89bcc4dae55c40bdd926797aba5e3f0ada05ae Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 19 Feb 2019 16:29:10 -0300 Subject: Avoid 500's when commit is not reachable --- spec/models/diff_note_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index fa19cb47a0d..d9e1fe4b165 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -321,6 +321,14 @@ describe DiffNote do end describe '#supports_suggestion?' do + context 'when noteable does not exist' do + it 'returns false' do + allow(subject).to receive(:noteable) { nil } + + expect(subject.supports_suggestion?).to be(false) + end + end + context 'when noteable does not support suggestions' do it 'returns false' do allow(subject.noteable).to receive(:supports_suggestion?) { false } -- cgit v1.2.1 From de21320db21e63db59cbba870ac23624a298214d Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Wed, 5 Jun 2019 20:06:37 +0800 Subject: Remove requirement for id for #markdown_cache_key It's not needed anymore as we require `#cache_key` instead. --- spec/lib/gitlab/markdown_cache/redis/extension_spec.rb | 4 ---- spec/lib/gitlab/markdown_cache/redis/store_spec.rb | 4 ---- spec/models/concerns/cache_markdown_field_spec.rb | 4 ---- 3 files changed, 12 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb index 24cfb399cc3..b6a781de426 100644 --- a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb +++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb @@ -15,10 +15,6 @@ describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do cache_markdown_field :title, pipeline: :single_line cache_markdown_field :description - def id - "test-markdown-cache" - end - def cache_key "cache-key" end diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb index e7701cf1306..95c68e7d491 100644 --- a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb +++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb @@ -13,10 +13,6 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do attr_accessor :field_1, :field_2, :field_1_html, :field_2_html, :cached_markdown_version - def id - 'test-redisbacked-store' - end - def cache_key "cache-key" end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 9e6b5d56805..0e5fb2b5153 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -27,10 +27,6 @@ describe CacheMarkdownField, :clean_gitlab_redis_cache do cache_markdown_field :title, pipeline: :single_line cache_markdown_field :description - def id - "test-markdown-cache" - end - def cache_key "cache-key" end -- cgit v1.2.1 From 89ea4a63a223468b3ac934f4ce4e3ef11ce26142 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 5 Jun 2019 12:44:29 +0000 Subject: Extract setListDetail() into issue boards store --- spec/javascripts/boards/boards_store_spec.js | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 68e66346bfd..b5559db8784 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -312,6 +312,17 @@ describe('Store', () => { }); }); + describe('setListDetail', () => { + it('sets the list detail', () => { + boardsStore.detail.list = 'not a list'; + + const dummyValue = 'new list'; + boardsStore.setListDetail(dummyValue); + + expect(boardsStore.detail.list).toEqual(dummyValue); + }); + }); + describe('clearDetailIssue', () => { it('resets issue details', () => { boardsStore.detail.issue = 'something'; -- cgit v1.2.1 From 40fd0b9e405ec8e4c37e8c90d44dde6c98e1176c Mon Sep 17 00:00:00 2001 From: Patrick Bajao Date: Mon, 27 May 2019 12:38:20 +0800 Subject: Add client code to call GetObjectDirectorySize RPC CE port of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/13460 --- spec/lib/gitlab/git/repository_spec.rb | 12 ++++++++++++ spec/lib/gitlab/gitaly_client/repository_service_spec.rb | 11 +++++++++++ 2 files changed, 23 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index cb4701e8edc..e72fb9c6fbc 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -186,6 +186,18 @@ describe Gitlab::Git::Repository, :seed_helper do it { is_expected.to be < 2 } end + describe '#object_directory_size' do + before do + allow(repository.gitaly_repository_client) + .to receive(:get_object_directory_size) + .and_return(2) + end + + subject { repository.object_directory_size } + + it { is_expected.to eq 2048 } + end + describe '#empty?' do it { expect(repository).not_to be_empty } end diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index 09de7ca6afd..a3808adb376 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -73,6 +73,17 @@ describe Gitlab::GitalyClient::RepositoryService do end end + describe '#get_object_directory_size' do + it 'sends a get_object_directory_size message' do + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:get_object_directory_size) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(size: 0) + + client.get_object_directory_size + end + end + describe '#apply_gitattributes' do let(:revision) { 'master' } -- cgit v1.2.1 From 690c4259a90e4b52c4107edabdeff60b4e05d264 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 5 Jun 2019 13:44:17 +0000 Subject: Prefer Time.zone.now to pair with Time.zone.name `Time.now` is local time, yet `Time.zone.now` is application configuration time. They can be different. --- spec/models/ci/pipeline_schedule_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 6382be73ea7..9a6471557ea 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -97,7 +97,7 @@ describe Ci::PipelineSchedule do let(:cron_worker_next_run_at) do Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) - .next_time_from(Time.now) + .next_time_from(Time.zone.now) end context 'when creates new pipeline schedule' do -- cgit v1.2.1 From be339468192c656bf9de0bb77d7e487f338902bf Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 21 May 2019 16:20:27 -0300 Subject: Delete unauthorized Todos when project is private Delete Todos for guest users when project visibility level is updated to private. --- spec/services/projects/update_service_spec.rb | 1 + .../destroy/confidential_issue_service_spec.rb | 58 +++++++++++++++------- .../confidential_issue_worker_spec.rb | 13 +++-- 3 files changed, 52 insertions(+), 20 deletions(-) (limited to 'spec') diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 5ad30b58511..1dcfb739eb6 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -45,6 +45,7 @@ describe Projects::UpdateService do it 'updates the project to private' do expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id) + expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, nil, project.id) result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE) diff --git a/spec/services/todos/destroy/confidential_issue_service_spec.rb b/spec/services/todos/destroy/confidential_issue_service_spec.rb index 78b6744b426..9f7e656f7d3 100644 --- a/spec/services/todos/destroy/confidential_issue_service_spec.rb +++ b/spec/services/todos/destroy/confidential_issue_service_spec.rb @@ -9,36 +9,60 @@ describe Todos::Destroy::ConfidentialIssueService do let(:assignee) { create(:user) } let(:guest) { create(:user) } let(:project_member) { create(:user) } - let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) } - - let!(:todo_issue_non_member) { create(:todo, user: user, target: issue, project: project) } - let!(:todo_issue_member) { create(:todo, user: project_member, target: issue, project: project) } - let!(:todo_issue_author) { create(:todo, user: author, target: issue, project: project) } - let!(:todo_issue_asignee) { create(:todo, user: assignee, target: issue, project: project) } - let!(:todo_issue_guest) { create(:todo, user: guest, target: issue, project: project) } - let!(:todo_another_non_member) { create(:todo, user: user, project: project) } + let(:issue_1) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) } describe '#execute' do before do project.add_developer(project_member) project.add_guest(guest) + + # todos not to be deleted + create(:todo, user: project_member, target: issue_1, project: project) + create(:todo, user: author, target: issue_1, project: project) + create(:todo, user: assignee, target: issue_1, project: project) + create(:todo, user: user, project: project) + # Todos to be deleted + create(:todo, user: guest, target: issue_1, project: project) + create(:todo, user: user, target: issue_1, project: project) end - subject { described_class.new(issue.id).execute } + subject { described_class.new(issue_id: issue_1.id).execute } - context 'when provided issue is confidential' do - before do - issue.update!(confidential: true) + context 'when issue_id parameter is present' do + context 'when provided issue is confidential' do + it 'removes issue todos for users who can not access the confidential issue' do + expect { subject }.to change { Todo.count }.from(6).to(4) + end end - it 'removes issue todos for users who can not access the confidential issue' do - expect { subject }.to change { Todo.count }.from(6).to(4) + context 'when provided issue is not confidential' do + it 'does not remove any todos' do + issue_1.update(confidential: false) + + expect { subject }.not_to change { Todo.count } + end end end - context 'when provided issue is not confidential' do - it 'does not remove any todos' do - expect { subject }.not_to change { Todo.count } + context 'when project_id parameter is present' do + subject { described_class.new(issue_id: nil, project_id: project.id).execute } + + it 'removes issues todos for users that cannot access confidential issues' do + issue_2 = create(:issue, :confidential, project: project) + issue_3 = create(:issue, :confidential, project: project, author: author, assignees: [assignee]) + issue_4 = create(:issue, project: project) + # Todos not to be deleted + create(:todo, user: guest, target: issue_1, project: project) + create(:todo, user: assignee, target: issue_1, project: project) + create(:todo, user: project_member, target: issue_2, project: project) + create(:todo, user: author, target: issue_3, project: project) + create(:todo, user: user, target: issue_4, project: project) + create(:todo, user: user, project: project) + # Todos to be deleted + create(:todo, user: user, target: issue_1, project: project) + create(:todo, user: guest, target: issue_2, project: project) + + expect { subject }.to change { Todo.count }.from(14).to(10) end end end diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb index 18876b71615..0907e2768ba 100644 --- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb +++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb @@ -3,12 +3,19 @@ require 'spec_helper' describe TodosDestroyer::ConfidentialIssueWorker do - it "calls the Todos::Destroy::ConfidentialIssueService with the params it was given" do - service = double + let(:service) { double } - expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(100).and_return(service) + it "calls the Todos::Destroy::ConfidentialIssueService with issue_id parameter" do + expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(issue_id: 100, project_id: nil).and_return(service) expect(service).to receive(:execute) described_class.new.perform(100) end + + it "calls the Todos::Destroy::ConfidentialIssueService with project_id parameter" do + expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(issue_id: nil, project_id: 100).and_return(service) + expect(service).to receive(:execute) + + described_class.new.perform(nil, 100) + end end -- cgit v1.2.1 From 74f78fa326a603be310375f9b37f8c8c9d921305 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Jun 2019 15:14:02 +0100 Subject: Adds hashed option to bs linked options Adds isOdd function --- spec/frontend/lib/utils/number_utility_spec.js | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'spec') diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js index 818404bad81..77d7478d317 100644 --- a/spec/frontend/lib/utils/number_utility_spec.js +++ b/spec/frontend/lib/utils/number_utility_spec.js @@ -5,6 +5,7 @@ import { bytesToGiB, numberToHumanSize, sum, + isOdd, } from '~/lib/utils/number_utils'; describe('Number Utils', () => { @@ -98,4 +99,14 @@ describe('Number Utils', () => { expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15); }); }); + + describe('isOdd', () => { + it('should return 0 with a even number', () => { + expect(isOdd(2)).toEqual(0); + }); + + it('should return 1 with a odd number', () => { + expect(isOdd(1)).toEqual(1); + }); + }); }); -- cgit v1.2.1 From 95b3fe286349176be7f117211dcc0ffa9dfcb8bb Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Tue, 4 Jun 2019 17:52:06 -0500 Subject: Use :complexity_multiplier only with connections This helps reduce complexity for non-connections --- spec/graphql/resolvers/base_resolver_spec.rb | 22 ++++++++-------- spec/graphql/resolvers/issues_resolver_spec.rb | 2 +- .../resolvers/namespace_projects_resolver_spec.rb | 2 +- spec/graphql/types/base_field_spec.rb | 29 +++++++++++++++------- 4 files changed, 34 insertions(+), 21 deletions(-) (limited to 'spec') diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb index 9982288e206..c162fdbbb47 100644 --- a/spec/graphql/resolvers/base_resolver_spec.rb +++ b/spec/graphql/resolvers/base_resolver_spec.rb @@ -29,18 +29,20 @@ describe Resolvers::BaseResolver do end end - it 'increases complexity based on arguments' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1) + context 'when field is a connection' do + it 'increases complexity based on arguments' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1) - expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 - expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7 - end + expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7 + end - it 'does not increase complexity when filtering by iids' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + it 'does not increase complexity when filtering by iids' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100) - expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 - expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 - expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 + expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 + end end end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index bffcdbfe915..798fe00de97 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -121,7 +121,7 @@ describe Resolvers::IssuesResolver do end it 'increases field complexity based on arguments' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100) expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4 expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8 diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb index 395e08081d3..20e197e9f73 100644 --- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -57,7 +57,7 @@ describe Resolvers::NamespaceProjectsResolver, :nested_groups do end it 'has an high complexity regardless of arguments' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100) expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24 expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb index a7fb156d9a8..0d3c3e37daf 100644 --- a/spec/graphql/types/base_field_spec.rb +++ b/spec/graphql/types/base_field_spec.rb @@ -28,18 +28,29 @@ describe Types::BaseField do expect(field.to_graphql.complexity).to eq 12 end - it 'sets complexity depending on arguments for resolvers' do - field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + context 'when field has a resolver proc' do + context 'and is a connection' do + let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) } - expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4 - expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3 - end + it 'sets complexity depending on arguments for resolvers' do + expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4 + expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3 + end - it 'sets complexity depending on number load limits for resolvers' do - field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + it 'sets complexity depending on number load limits for resolvers' do + expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2 + expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 + end + end - expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2 - expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 + context 'and is not a connection' do + it 'sets complexity as normal' do + field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + + expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2 + expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2 + end + end end end end -- cgit v1.2.1 From 0b0f39c5737265eb796040d13f6411f0c6b17708 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 5 Jun 2019 16:28:41 +0000 Subject: Add snowplow haml attr helper Helps build a hash of snowplow tracking data attrs. Updates import project pane to use new helper. --- spec/helpers/tracking_helper_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 spec/helpers/tracking_helper_spec.rb (limited to 'spec') diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb new file mode 100644 index 00000000000..71505e8ea69 --- /dev/null +++ b/spec/helpers/tracking_helper_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe TrackingHelper do + describe '#tracking_attrs' do + it 'returns an empty hash' do + expect(helper.tracking_attrs('a', 'b', 'c')).to eq({}) + end + end +end -- cgit v1.2.1 From 866f544c3e29c66ab47ddd9266898cb64d615967 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Tue, 4 Jun 2019 16:21:05 +0100 Subject: Avoid setting Gitlab::Session on sessionless requests --- spec/controllers/application_controller_spec.rb | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'spec') diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 5ecd1b6b7c8..40669ec5451 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -691,4 +691,38 @@ describe ApplicationController do end end end + + context 'Gitlab::Session' do + controller(described_class) do + prepend_before_action do + authenticate_sessionless_user!(:rss) + end + + def index + if Gitlab::Session.current + head :created + else + head :not_found + end + end + end + + it 'is set on web requests' do + sign_in(user) + + get :index + + expect(response).to have_gitlab_http_status(:created) + end + + context 'with sessionless user' do + it 'is not set' do + personal_access_token = create(:personal_access_token, user: user) + + get :index, format: :atom, params: { private_token: personal_access_token.token } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end -- cgit v1.2.1 From 13a8722464565b272bc00acd608761a3a83dcce0 Mon Sep 17 00:00:00 2001 From: Amit Rathi Date: Wed, 5 Jun 2019 19:50:29 +0000 Subject: Setup Git client for Jupyter --- spec/models/clusters/applications/jupyter_spec.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spec') diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index fc9ebed863e..43fa1010b2b 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -96,6 +96,8 @@ describe Clusters::Applications::Jupyter do expect(values).to match(/clientId: '?#{application.oauth_application.uid}/) expect(values).to match(/callbackUrl: '?#{application.callback_url}/) expect(values).to include("gitlabProjectIdWhitelist:\n - #{application.cluster.project.id}") + expect(values).to include("c.GitLabOAuthenticator.scope = ['api read_repository write_repository']") + expect(values).to match(/GITLAB_HOST: '?#{Gitlab.config.gitlab.host}/) end context 'when cluster belongs to a project' do -- cgit v1.2.1 From c0e743bf0cad24bad590328925a32e9eee9810b2 Mon Sep 17 00:00:00 2001 From: Martin Hanzel Date: Wed, 5 Jun 2019 21:28:40 +0000 Subject: Migrate old notes app test from Karma to Jest --- spec/frontend/helpers/jest_helpers.js | 24 + spec/frontend/helpers/timeout.js | 20 +- spec/frontend/notes/old_notes_spec.js | 1045 ++++++++++++++++++++++++++++++++ spec/javascripts/notes_spec.js | 1048 --------------------------------- 4 files changed, 1087 insertions(+), 1050 deletions(-) create mode 100644 spec/frontend/helpers/jest_helpers.js create mode 100644 spec/frontend/notes/old_notes_spec.js delete mode 100644 spec/javascripts/notes_spec.js (limited to 'spec') diff --git a/spec/frontend/helpers/jest_helpers.js b/spec/frontend/helpers/jest_helpers.js new file mode 100644 index 00000000000..4a150be9935 --- /dev/null +++ b/spec/frontend/helpers/jest_helpers.js @@ -0,0 +1,24 @@ +/* eslint-disable import/prefer-default-export */ + +/* +@module + +This method provides convenience functions to help migrating from Karma/Jasmine to Jest. + +Try not to use these in new tests - this module is provided primarily for convenience of migrating tests. + */ + +/** + * Creates a plain JS object pre-populated with Jest spy functions. Useful for making simple mocks classes. + * + * @see https://jasmine.github.io/2.0/introduction.html#section-Spies:_%3Ccode%3EcreateSpyObj%3C/code%3E + * @param {string} baseName Human-readable name of the object. This is used for reporting purposes. + * @param methods {string[]} List of method names that will be added to the spy object. + */ +export function createSpyObj(baseName, methods) { + const obj = {}; + methods.forEach(method => { + obj[method] = jest.fn().mockName(`${baseName}#${method}`); + }); + return obj; +} diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js index b30b7f1ce1e..e74598ae20a 100644 --- a/spec/frontend/helpers/timeout.js +++ b/spec/frontend/helpers/timeout.js @@ -1,5 +1,6 @@ const NS_PER_SEC = 1e9; const NS_PER_MS = 1e6; +const IS_DEBUGGING = process.execArgv.join(' ').includes('--inspect-brk'); let testTimeoutNS; @@ -8,6 +9,13 @@ export const setTestTimeout = newTimeoutMS => { jest.setTimeout(newTimeoutMS); }; +// Allows slow tests to set their own timeout. +// Useful for tests with jQuery, which is very slow in big DOMs. +let temporaryTimeoutNS = null; +export const setTestTimeoutOnce = newTimeoutMS => { + temporaryTimeoutNS = newTimeoutMS * NS_PER_MS; +}; + export const initializeTestTimeout = defaultTimeoutMS => { setTestTimeout(defaultTimeoutMS); @@ -19,12 +27,20 @@ export const initializeTestTimeout = defaultTimeoutMS => { }); afterEach(() => { + let timeoutNS = testTimeoutNS; + if (Number.isFinite(temporaryTimeoutNS)) { + timeoutNS = temporaryTimeoutNS; + temporaryTimeoutNS = null; + } + const [seconds, remainingNs] = process.hrtime(testStartTime); const elapsedNS = seconds * NS_PER_SEC + remainingNs; - if (elapsedNS > testTimeoutNS) { + // Disable the timeout error when debugging. It is meaningless because + // debugging always takes longer than the test timeout. + if (elapsedNS > timeoutNS && !IS_DEBUGGING) { throw new Error( - `Test took too long (${elapsedNS / NS_PER_MS}ms > ${testTimeoutNS / NS_PER_MS}ms)!`, + `Test took too long (${elapsedNS / NS_PER_MS}ms > ${timeoutNS / NS_PER_MS}ms)!`, ); } }); diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js new file mode 100644 index 00000000000..b57041cf4d1 --- /dev/null +++ b/spec/frontend/notes/old_notes_spec.js @@ -0,0 +1,1045 @@ +/* eslint-disable import/no-commonjs, no-new */ + +import $ from 'jquery'; +import _ from 'underscore'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import * as urlUtility from '~/lib/utils/url_utility'; +import '~/behaviors/markdown/render_gfm'; +import { createSpyObj } from 'helpers/jest_helpers'; +import { setTestTimeoutOnce } from 'helpers/timeout'; +import { TEST_HOST } from 'helpers/test_constants'; + +// These must be imported synchronously because they pull dependencies +// from the DOM. +window.jQuery = $; +require('autosize'); +require('~/commons'); +require('~/notes'); + +const { Notes } = window; +const FLASH_TYPE_ALERT = 'alert'; +const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; +const fixture = 'snippets/show.html'; +let mockAxios; + +window.project_uploads_path = `${TEST_HOST}/uploads`; +window.gon = window.gon || {}; +window.gl = window.gl || {}; +gl.utils = gl.utils || {}; +gl.utils.disableButtonIfEmptyField = () => {}; + +describe('Old Notes (~/notes.js)', () => { + beforeEach(() => { + jest.useFakeTimers(); + loadFixtures(fixture); + + // Re-declare this here so that test_setup.js#beforeEach() doesn't + // overwrite it. + mockAxios = new MockAdapter(axios); + + $.ajax = () => { + throw new Error('$.ajax should not be called through!'); + }; + + // These jQuery+DOM tests are super flaky so increase the timeout to avoid + // random failures. + // It seems that running tests in parallel increases failure rate. + jest.setTimeout(4000); + setTestTimeoutOnce(4000); + }); + + afterEach(done => { + // The Notes component sets a polling interval. Clear it after every run. + // Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers(). + jest.clearAllTimers(); + + setImmediate(() => { + // Wait for any requests to resolve, otherwise we get failures about + // unmocked requests. + mockAxios.restore(); + done(); + }); + }); + + it('loads the Notes class into the DOM', () => { + expect(Notes).toBeDefined(); + expect(Notes.name).toBe('Notes'); + }); + + describe('addBinding', () => { + it('calls postComment when comment button is clicked', () => { + jest.spyOn(Notes.prototype, 'postComment'); + + new window.Notes('', []); + $('.js-comment-button').click(); + expect(Notes.prototype.postComment).toHaveBeenCalled(); + }); + }); + + describe('task lists', () => { + beforeEach(() => { + mockAxios.onAny().reply(200, {}); + new Notes('', []); + }); + + it('modifies the Markdown field', () => { + const changeEvent = document.createEvent('HTMLEvents'); + changeEvent.initEvent('change', true, true); + $('input[type=checkbox]') + .attr('checked', true)[0] + .dispatchEvent(changeEvent); + + expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item'); + }); + + it('submits an ajax request on tasklist:changed', () => { + jest.spyOn(axios, 'patch'); + + const lineNumber = 8; + const lineSource = '- [ ] item 8'; + const index = 3; + const checked = true; + + $('.js-task-list-container').trigger({ + type: 'tasklist:changed', + detail: { lineNumber, lineSource, index, checked }, + }); + + expect(axios.patch).toHaveBeenCalledWith(undefined, { + note: { + note: '', + lock_version: undefined, + update_task: { index, checked, line_number: lineNumber, line_source: lineSource }, + }, + }); + }); + }); + + describe('comments', () => { + let notes; + let autosizeSpy; + let textarea; + + beforeEach(() => { + notes = new Notes('', []); + + textarea = $('.js-note-text'); + textarea.data('autosave', { + reset: () => {}, + }); + autosizeSpy = jest.fn(); + $(textarea).on('autosize:update', autosizeSpy); + + jest.spyOn(notes, 'renderNote'); + + $('.js-comment-button').on('click', e => { + const $form = $(this); + e.preventDefault(); + notes.addNote($form, {}); + notes.reenableTargetFormSubmitButton(e); + notes.resetMainTargetForm(e); + }); + }); + + it('autosizes after comment submission', () => { + textarea.text('This is an example comment note'); + expect(autosizeSpy).not.toHaveBeenCalled(); + $('.js-comment-button').click(); + expect(autosizeSpy).toHaveBeenCalled(); + }); + + it('should not place escaped text in the comment box in case of error', () => { + const deferred = $.Deferred(); + jest.spyOn($, 'ajax').mockReturnValueOnce(deferred); + $(textarea).text('A comment with `markup`.'); + + deferred.reject(); + $('.js-comment-button').click(); + + expect($(textarea).val()).toBe('A comment with `markup`.'); + + $.ajax.mockRestore(); + expect($.ajax.mock).toBeUndefined(); + }); + }); + + describe('updateNote', () => { + let notes; + let noteEntity; + let $notesContainer; + + beforeEach(() => { + notes = new Notes('', []); + window.gon.current_username = 'root'; + window.gon.current_user_fullname = 'Administrator'; + const sampleComment = 'foo'; + noteEntity = { + id: 1234, + html: `
    • +
      ${sampleComment}
      +
    • `, + note: sampleComment, + valid: true, + }; + + $notesContainer = $('ul.main-notes-list'); + const $form = $('form.js-main-target-form'); + $form.find('textarea.js-note-text').val(sampleComment); + + mockAxios.onPost(NOTES_POST_PATH).reply(200, noteEntity); + }); + + it('updates note and resets edit form', () => { + jest.spyOn(notes, 'revertNoteEditForm'); + jest.spyOn(notes, 'setupNewNote'); + + $('.js-comment-button').click(); + + const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`); + const updatedNote = Object.assign({}, noteEntity); + updatedNote.note = 'bar'; + notes.updateNote(updatedNote, $targetNote); + + expect(notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote); + expect(notes.setupNewNote).toHaveBeenCalled(); + }); + }); + + describe('updateNoteTargetSelector', () => { + const hash = 'note_foo'; + let $note; + + beforeEach(() => { + $note = $(`
      `); + jest.spyOn($note, 'filter'); + jest.spyOn($note, 'toggleClass'); + }); + + afterEach(() => { + expect(typeof urlUtility.getLocationHash.mock).toBe('object'); + urlUtility.getLocationHash.mockRestore(); + expect(urlUtility.getLocationHash.mock).toBeUndefined(); + expect(urlUtility.getLocationHash()).toBeNull(); + }); + + // urlUtility is a dependency of the notes module. Its getLocatinHash() method should be called internally. + + it('sets target when hash matches', () => { + jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(hash); + + Notes.updateNoteTargetSelector($note); + + expect(urlUtility.getLocationHash).toHaveBeenCalled(); + expect($note.filter).toHaveBeenCalledWith(`#${hash}`); + expect($note.toggleClass).toHaveBeenCalledWith('target', true); + }); + + it('unsets target when hash does not match', () => { + jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce('note_doesnotexist'); + + Notes.updateNoteTargetSelector($note); + + expect(urlUtility.getLocationHash).toHaveBeenCalled(); + expect($note.toggleClass).toHaveBeenCalledWith('target', false); + }); + + it('unsets target when there is not a hash fragment anymore', () => { + jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(null); + + Notes.updateNoteTargetSelector($note); + + expect(urlUtility.getLocationHash).toHaveBeenCalled(); + expect($note.toggleClass).toHaveBeenCalledWith('target', false); + }); + }); + + describe('renderNote', () => { + let notes; + let note; + let $notesList; + + beforeEach(() => { + note = { + id: 1, + valid: true, + note: 'heya', + html: '
      heya
      ', + }; + $notesList = createSpyObj('$notesList', ['find', 'append']); + + notes = createSpyObj('notes', [ + 'setupNewNote', + 'refresh', + 'collapseLongCommitList', + 'updateNotesCount', + 'putConflictEditWarningInPlace', + ]); + notes.taskList = createSpyObj('tasklist', ['init']); + notes.note_ids = []; + notes.updatedNotesTrackingMap = {}; + + jest.spyOn(Notes, 'isNewNote'); + jest.spyOn(Notes, 'isUpdatedNote'); + jest.spyOn(Notes, 'animateAppendNote'); + jest.spyOn(Notes, 'animateUpdateNote'); + }); + + describe('when adding note', () => { + it('should call .animateAppendNote', () => { + Notes.isNewNote.mockReturnValueOnce(true); + Notes.prototype.renderNote.call(notes, note, null, $notesList); + + expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList); + }); + }); + + describe('when note was edited', () => { + it('should call .animateUpdateNote', () => { + Notes.isNewNote.mockReturnValueOnce(false); + Notes.isUpdatedNote.mockReturnValueOnce(true); + const $note = $('
      '); + $notesList.find.mockReturnValueOnce($note); + const $newNote = $(note.html); + Notes.animateUpdateNote.mockReturnValueOnce($newNote); + + Notes.prototype.renderNote.call(notes, note, null, $notesList); + + expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note); + expect(notes.setupNewNote).toHaveBeenCalledWith($newNote); + }); + + describe('while editing', () => { + it('should update textarea if nothing has been touched', () => { + Notes.isNewNote.mockReturnValueOnce(false); + Notes.isUpdatedNote.mockReturnValueOnce(true); + const $note = $(`
      +
      initial
      + +
      `); + $notesList.find.mockReturnValueOnce($note); + Notes.prototype.renderNote.call(notes, note, null, $notesList); + + expect($note.find('.js-note-text').val()).toEqual(note.note); + }); + + it('should call .putConflictEditWarningInPlace', () => { + Notes.isNewNote.mockReturnValueOnce(false); + Notes.isUpdatedNote.mockReturnValueOnce(true); + const $note = $(`
      +
      initial
      + +
      `); + $notesList.find.mockReturnValueOnce($note); + Notes.prototype.renderNote.call(notes, note, null, $notesList); + + expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note); + }); + }); + }); + }); + + describe('isUpdatedNote', () => { + it('should consider same note text as the same', () => { + const result = Notes.isUpdatedNote( + { + note: 'initial', + }, + $(`
      +
      initial
      +
      `), + ); + + expect(result).toEqual(false); + }); + + it('should consider same note with trailing newline as the same', () => { + const result = Notes.isUpdatedNote( + { + note: 'initial\n', + }, + $(`
      +
      initial\n
      +
      `), + ); + + expect(result).toEqual(false); + }); + + it('should consider different notes as different', () => { + const result = Notes.isUpdatedNote( + { + note: 'foo', + }, + $(`
      +
      bar
      +
      `), + ); + + expect(result).toEqual(true); + }); + }); + + describe('renderDiscussionNote', () => { + let discussionContainer; + let note; + let notes; + let $form; + let row; + + beforeEach(() => { + note = { + html: '
    • ', + discussion_html: '
      ', + discussion_id: 1, + discussion_resolvable: false, + diff_discussion_html: false, + }; + $form = createSpyObj('$form', ['closest', 'find']); + $form.length = 1; + row = createSpyObj('row', ['prevAll', 'first', 'find']); + + notes = createSpyObj('notes', ['isParallelView', 'updateNotesCount']); + notes.note_ids = []; + + jest.spyOn(Notes, 'isNewNote'); + jest.spyOn(Notes, 'animateAppendNote').mockImplementation(); + Notes.isNewNote.mockReturnValue(true); + notes.isParallelView.mockReturnValue(false); + row.prevAll.mockReturnValue(row); + row.first.mockReturnValue(row); + row.find.mockReturnValue(row); + }); + + describe('Discussion root note', () => { + let body; + + beforeEach(() => { + body = createSpyObj('body', ['attr']); + discussionContainer = { length: 0 }; + + $form.closest.mockReturnValueOnce(row).mockReturnValue($form); + $form.find.mockReturnValue(discussionContainer); + body.attr.mockReturnValue(''); + }); + + it('should call Notes.animateAppendNote', () => { + Notes.prototype.renderDiscussionNote.call(notes, note, $form); + + expect(Notes.animateAppendNote).toHaveBeenCalledWith( + note.discussion_html, + $('.main-notes-list'), + ); + }); + + it('should append to row selected with line_code', () => { + $form.length = 0; + note.discussion_line_code = 'line_code'; + note.diff_discussion_html = ''; + + const line = document.createElement('div'); + line.id = note.discussion_line_code; + document.body.appendChild(line); + + // Override mocks for this single test + $form.closest.mockReset(); + $form.closest.mockReturnValue($form); + + Notes.prototype.renderDiscussionNote.call(notes, note, $form); + + expect(line.nextSibling.outerHTML).toEqual(note.diff_discussion_html); + }); + }); + + describe('Discussion sub note', () => { + beforeEach(() => { + discussionContainer = { length: 1 }; + + $form.closest.mockReturnValueOnce(row).mockReturnValueOnce($form); + $form.find.mockReturnValue(discussionContainer); + + Notes.prototype.renderDiscussionNote.call(notes, note, $form); + }); + + it('should call Notes.animateAppendNote', () => { + expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer); + }); + }); + }); + + describe('animateAppendNote', () => { + let noteHTML; + let $notesList; + let $resultantNote; + + beforeEach(() => { + noteHTML = '
      '; + $notesList = createSpyObj('$notesList', ['append']); + + $resultantNote = Notes.animateAppendNote(noteHTML, $notesList); + }); + + it('should have `fade-in-full` class', () => { + expect($resultantNote.hasClass('fade-in-full')).toEqual(true); + }); + + it('should append note to the notes list', () => { + expect($notesList.append).toHaveBeenCalledWith($resultantNote); + }); + }); + + describe('animateUpdateNote', () => { + let noteHTML; + let $note; + let $updatedNote; + + beforeEach(() => { + noteHTML = '
      '; + $note = createSpyObj('$note', ['replaceWith']); + + $updatedNote = Notes.animateUpdateNote(noteHTML, $note); + }); + + it('should have `fade-in` class', () => { + expect($updatedNote.hasClass('fade-in')).toEqual(true); + }); + + it('should call replaceWith on $note', () => { + expect($note.replaceWith).toHaveBeenCalledWith($updatedNote); + }); + }); + + describe('putEditFormInPlace', () => { + it('should call GLForm with GFM parameter passed through', () => { + const notes = new Notes('', []); + const $el = $(` +
      +
      +
      + `); + + notes.putEditFormInPlace($el); + + expect(notes.glForm.enableGFM).toBeTruthy(); + }); + }); + + describe('postComment & updateComment', () => { + const sampleComment = 'foo'; + const note = { + id: 1234, + html: `
    • +
      ${sampleComment}
      +
    • `, + note: sampleComment, + valid: true, + }; + let notes; + let $form; + let $notesContainer; + + function mockNotesPost() { + mockAxios.onPost(NOTES_POST_PATH).reply(200, note); + } + + function mockNotesPostError() { + mockAxios.onPost(NOTES_POST_PATH).networkError(); + } + + beforeEach(() => { + notes = new Notes('', []); + window.gon.current_username = 'root'; + window.gon.current_user_fullname = 'Administrator'; + $form = $('form.js-main-target-form'); + $notesContainer = $('ul.main-notes-list'); + $form.find('textarea.js-note-text').val(sampleComment); + }); + + it('should show placeholder note while new comment is being posted', () => { + mockNotesPost(); + + $('.js-comment-button').click(); + + expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0); + }); + + it('should remove placeholder note when new comment is done posting', done => { + mockNotesPost(); + + $('.js-comment-button').click(); + + setImmediate(() => { + expect($notesContainer.find('.note.being-posted').length).toEqual(0); + done(); + }); + }); + + describe('postComment', () => { + it('disables the submit button', done => { + const $submitButton = $form.find('.js-comment-submit-button'); + + expect($submitButton).not.toBeDisabled(); + const dummyEvent = { + preventDefault() {}, + target: $submitButton, + }; + mockAxios.onPost(NOTES_POST_PATH).replyOnce(() => { + expect($submitButton).toBeDisabled(); + return [200, note]; + }); + + notes + .postComment(dummyEvent) + .then(() => { + expect($submitButton).not.toBeDisabled(); + }) + .then(done) + .catch(done.fail); + }); + }); + + it('should show actual note element when new comment is done posting', done => { + mockNotesPost(); + + $('.js-comment-button').click(); + + setImmediate(() => { + expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0); + done(); + }); + }); + + it('should reset Form when new comment is done posting', done => { + mockNotesPost(); + + $('.js-comment-button').click(); + + setImmediate(() => { + expect($form.find('textarea.js-note-text').val()).toEqual(''); + done(); + }); + }); + + it('should show flash error message when new comment failed to be posted', done => { + mockNotesPostError(); + jest.spyOn(notes, 'addFlash'); + + $('.js-comment-button').click(); + + setImmediate(() => { + expect(notes.addFlash).toHaveBeenCalled(); + // JSDom doesn't support the :visible selector yet + expect(notes.flashContainer.style.display).not.toBe('none'); + done(); + }); + }); + + // This is a bad test carried over from the Karma -> Jest migration. + // The corresponding test in the Karma suite tests for + // elements and methods that don't actually exist, and gives a false + // positive pass. + /* + it('should show flash error message when comment failed to be updated', done => { + mockNotesPost(); + jest.spyOn(notes, 'addFlash').mockName('addFlash'); + + $('.js-comment-button').click(); + + deferredPromise() + .then(() => { + const $noteEl = $notesContainer.find(`#note_${note.id}`); + $noteEl.find('.js-note-edit').click(); + $noteEl.find('textarea.js-note-text').val(updatedComment); + + mockNotesPostError(); + + $noteEl.find('.js-comment-save-button').click(); + notes.updateComment({preventDefault: () => {}}); + }) + .then(() => deferredPromise()) + .then(() => { + const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`); + + expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals + expect( + $updatedNoteEl + .find('.note-text') + .text() + .trim(), + ).toEqual(sampleComment); // See if comment reverted back to original + + expect(notes.addFlash).toHaveBeenCalled(); + expect(notes.flashContainer.style.display).not.toBe('none'); + done(); + }) + .catch(done.fail); + }, 5000); + */ + }); + + describe('postComment with Slash commands', () => { + const sampleComment = '/assign @root\n/award :100:'; + const note = { + commands_changes: { + assignee_id: 1, + emoji_award: '100', + }, + errors: { + commands_only: ['Commands applied'], + }, + valid: false, + }; + let $form; + let $notesContainer; + + beforeEach(() => { + mockAxios.onPost(NOTES_POST_PATH).reply(200, note); + + new Notes('', []); + window.gon.current_username = 'root'; + window.gon.current_user_fullname = 'Administrator'; + gl.awardsHandler = { + addAwardToEmojiBar: () => {}, + scrollToAwards: () => {}, + }; + gl.GfmAutoComplete = { + dataSources: { + commands: '/root/test-project/autocomplete_sources/commands', + }, + }; + $form = $('form.js-main-target-form'); + $notesContainer = $('ul.main-notes-list'); + $form.find('textarea.js-note-text').val(sampleComment); + }); + + it('should remove slash command placeholder when comment with slash commands is done posting', done => { + jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar'); + $('.js-comment-button').click(); + + expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown + + setImmediate(() => { + expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed + done(); + }); + }); + }); + + describe('update comment with script tags', () => { + const sampleComment = ''; + const updatedComment = ''; + const note = { + id: 1234, + html: `
    • +
      ${sampleComment}
      +
    • `, + note: sampleComment, + valid: true, + }; + let $form; + let $notesContainer; + + beforeEach(() => { + mockAxios.onPost(NOTES_POST_PATH).reply(200, note); + + new Notes('', []); + window.gon.current_username = 'root'; + window.gon.current_user_fullname = 'Administrator'; + $form = $('form.js-main-target-form'); + $notesContainer = $('ul.main-notes-list'); + $form.find('textarea.js-note-text').html(sampleComment); + }); + + it('should not render a script tag', done => { + $('.js-comment-button').click(); + + setImmediate(() => { + const $noteEl = $notesContainer.find(`#note_${note.id}`); + $noteEl.find('.js-note-edit').click(); + $noteEl.find('textarea.js-note-text').html(updatedComment); + $noteEl.find('.js-comment-save-button').click(); + + const $updatedNoteEl = $notesContainer + .find(`#note_${note.id}`) + .find('.js-task-list-container'); + + expect( + $updatedNoteEl + .find('.note-text') + .text() + .trim(), + ).toEqual(''); + + done(); + }); + }); + }); + + describe('getFormData', () => { + let $form; + let sampleComment; + let notes; + + beforeEach(() => { + notes = new Notes('', []); + + $form = $('form'); + sampleComment = 'foobar'; + }); + + it('should return form metadata object from form reference', () => { + $form.find('textarea.js-note-text').val(sampleComment); + const { formData, formContent, formAction } = notes.getFormData($form); + + expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1); + expect(formContent).toEqual(sampleComment); + expect(formAction).toEqual($form.attr('action')); + }); + + it('should return form metadata with sanitized formContent from form reference', () => { + jest.spyOn(_, 'escape'); + + sampleComment = ''; + $form.find('textarea.js-note-text').val(sampleComment); + + const { formContent } = notes.getFormData($form); + + expect(_.escape).toHaveBeenCalledWith(sampleComment); + expect(formContent).toEqual('<script>alert("Boom!");</script>'); + }); + }); + + describe('hasQuickActions', () => { + let notes; + + beforeEach(() => { + notes = new Notes('', []); + }); + + it('should return true when comment begins with a quick action', () => { + const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; + const hasQuickActions = notes.hasQuickActions(sampleComment); + + expect(hasQuickActions).toBeTruthy(); + }); + + it('should return false when comment does NOT begin with a quick action', () => { + const sampleComment = 'Hey, /unassign Merging this'; + const hasQuickActions = notes.hasQuickActions(sampleComment); + + expect(hasQuickActions).toBeFalsy(); + }); + + it('should return false when comment does NOT have any quick actions', () => { + const sampleComment = 'Looking good, Awesome!'; + const hasQuickActions = notes.hasQuickActions(sampleComment); + + expect(hasQuickActions).toBeFalsy(); + }); + }); + + describe('stripQuickActions', () => { + it('should strip quick actions from the comment which begins with a quick action', () => { + const notes = new Notes(); + const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; + const stripedComment = notes.stripQuickActions(sampleComment); + + expect(stripedComment).toBe(''); + }); + + it('should strip quick actions from the comment but leaves plain comment if it is present', () => { + const notes = new Notes(); + const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this'; + const stripedComment = notes.stripQuickActions(sampleComment); + + expect(stripedComment).toBe('Merging this'); + }); + + it('should NOT strip string that has slashes within', () => { + const notes = new Notes(); + const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1'; + const stripedComment = notes.stripQuickActions(sampleComment); + + expect(stripedComment).toBe(sampleComment); + }); + }); + + describe('getQuickActionDescription', () => { + const availableQuickActions = [ + { name: 'close', description: 'Close this issue', params: [] }, + { name: 'title', description: 'Change title', params: [{}] }, + { name: 'estimate', description: 'Set time estimate', params: [{}] }, + ]; + let notes; + + beforeEach(() => { + notes = new Notes(); + }); + + it('should return executing quick action description when note has single quick action', () => { + const sampleComment = '/close'; + + expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe( + 'Applying command to close this issue', + ); + }); + + it('should return generic multiple quick action description when note has multiple quick actions', () => { + const sampleComment = '/close\n/title [Duplicate] Issue foobar'; + + expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe( + 'Applying multiple commands', + ); + }); + + it('should return generic quick action description when available quick actions list is not populated', () => { + const sampleComment = '/close\n/title [Duplicate] Issue foobar'; + + expect(notes.getQuickActionDescription(sampleComment)).toBe('Applying command'); + }); + }); + + describe('createPlaceholderNote', () => { + const sampleComment = 'foobar'; + const uniqueId = 'b1234-a4567'; + const currentUsername = 'root'; + const currentUserFullname = 'Administrator'; + const currentUserAvatar = 'avatar_url'; + let notes; + + beforeEach(() => { + notes = new Notes('', []); + }); + + it('should return constructed placeholder element for regular note based on form contents', () => { + const $tempNote = notes.createPlaceholderNote({ + formContent: sampleComment, + uniqueId, + isDiscussionNote: false, + currentUsername, + currentUserFullname, + currentUserAvatar, + }); + const $tempNoteHeader = $tempNote.find('.note-header'); + + expect($tempNote.prop('nodeName')).toEqual('LI'); + expect($tempNote.attr('id')).toEqual(uniqueId); + expect($tempNote.hasClass('being-posted')).toBeTruthy(); + expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); + $tempNote.find('.timeline-icon > a, .note-header-info > a').each((i, el) => { + expect(el.getAttribute('href')).toEqual(`/${currentUsername}`); + }); + + expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar); + expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy(); + expect( + $tempNoteHeader + .find('.d-none.d-sm-inline-block') + .text() + .trim(), + ).toEqual(currentUserFullname); + + expect( + $tempNoteHeader + .find('.note-headline-light') + .text() + .trim(), + ).toEqual(`@${currentUsername}`); + + expect( + $tempNote + .find('.note-body .note-text p') + .text() + .trim(), + ).toEqual(sampleComment); + }); + + it('should return constructed placeholder element for discussion note based on form contents', () => { + const $tempNote = notes.createPlaceholderNote({ + formContent: sampleComment, + uniqueId, + isDiscussionNote: true, + currentUsername, + currentUserFullname, + }); + + expect($tempNote.prop('nodeName')).toEqual('LI'); + expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy(); + }); + + it('should return a escaped user name', () => { + const currentUserFullnameXSS = 'Foo '; + const $tempNote = notes.createPlaceholderNote({ + formContent: sampleComment, + uniqueId, + isDiscussionNote: false, + currentUsername, + currentUserFullname: currentUserFullnameXSS, + currentUserAvatar, + }); + const $tempNoteHeader = $tempNote.find('.note-header'); + + expect( + $tempNoteHeader + .find('.d-none.d-sm-inline-block') + .text() + .trim(), + ).toEqual('Foo <script>alert("XSS")</script>'); + }); + }); + + describe('createPlaceholderSystemNote', () => { + const sampleCommandDescription = 'Applying command to close this issue'; + const uniqueId = 'b1234-a4567'; + let notes; + + beforeEach(() => { + notes = new Notes('', []); + jest.spyOn(_, 'escape'); + }); + + it('should return constructed placeholder element for system note based on form contents', () => { + const $tempNote = notes.createPlaceholderSystemNote({ + formContent: sampleCommandDescription, + uniqueId, + }); + + expect($tempNote.prop('nodeName')).toEqual('LI'); + expect($tempNote.attr('id')).toEqual(uniqueId); + expect($tempNote.hasClass('being-posted')).toBeTruthy(); + expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); + expect( + $tempNote + .find('.timeline-content i') + .text() + .trim(), + ).toEqual(sampleCommandDescription); + }); + }); + + describe('appendFlash', () => { + it('shows a flash message', () => { + const notes = new Notes('', []); + notes.addFlash('Error message', FLASH_TYPE_ALERT, notes.parentTimeline.get(0)); + + const flash = $('.flash-alert')[0]; + expect(document.contains(flash)).toBe(true); + expect(flash.parentNode.style.display).toBe('block'); + }); + }); + + describe('clearFlash', () => { + beforeEach(() => { + $(document).off('ajax:success'); + }); + + it('hides visible flash message', () => { + const notes = new Notes('', []); + notes.addFlash('Error message 1', FLASH_TYPE_ALERT, notes.parentTimeline.get(0)); + const flash = $('.flash-alert')[0]; + notes.clearFlash(); + expect(flash.parentNode.style.display).toBe('none'); + expect(notes.flashContainer).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js deleted file mode 100644 index 394e3343be6..00000000000 --- a/spec/javascripts/notes_spec.js +++ /dev/null @@ -1,1048 +0,0 @@ -/* eslint-disable no-unused-expressions, no-var, object-shorthand */ -import $ from 'jquery'; -import _ from 'underscore'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import 'autosize'; -import '~/gl_form'; -import '~/lib/utils/text_utility'; -import '~/behaviors/markdown/render_gfm'; -import Notes from '~/notes'; -import timeoutPromise from './helpers/set_timeout_promise_helper'; - -window.gon || (window.gon = {}); -window.gl = window.gl || {}; -gl.utils = gl.utils || {}; - -const htmlEscape = comment => { - const escapedString = comment.replace(/["&'<>]/g, a => { - const escapedToken = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '`': '`', - }[a]; - - return escapedToken; - }); - - return escapedString; -}; - -describe('Notes', function() { - const FLASH_TYPE_ALERT = 'alert'; - const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; - var fixture = 'snippets/show.html'; - preloadFixtures(fixture); - - beforeEach(function() { - loadFixtures(fixture); - gl.utils.disableButtonIfEmptyField = _.noop; - window.project_uploads_path = 'http://test.host/uploads'; - $('body').attr('data-page', 'projects:merge_requets:show'); - }); - - afterEach(() => { - // Undo what we did to the shared - $('body').removeAttr('data-page'); - }); - - describe('addBinding', () => { - it('calls postComment when comment button is clicked', () => { - spyOn(Notes.prototype, 'postComment'); - this.notes = new Notes('', []); - - $('.js-comment-button').click(); - - expect(Notes.prototype.postComment).toHaveBeenCalled(); - }); - }); - - describe('task lists', function() { - let mock; - - beforeEach(function() { - spyOn(axios, 'patch').and.callFake(() => new Promise(() => {})); - mock = new MockAdapter(axios); - mock.onAny().reply(200, {}); - - $('.js-comment-button').on('click', function(e) { - e.preventDefault(); - }); - this.notes = new Notes('', []); - }); - - afterEach(() => { - mock.restore(); - }); - - it('modifies the Markdown field', function() { - const changeEvent = document.createEvent('HTMLEvents'); - changeEvent.initEvent('change', true, true); - $('input[type=checkbox]') - .attr('checked', true)[0] - .dispatchEvent(changeEvent); - - expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item'); - }); - - it('submits an ajax request on tasklist:changed', function(done) { - const lineNumber = 8; - const lineSource = '- [ ] item 8'; - const index = 3; - const checked = true; - - $('.js-task-list-container').trigger({ - type: 'tasklist:changed', - detail: { lineNumber, lineSource, index, checked }, - }); - - setTimeout(() => { - expect(axios.patch).toHaveBeenCalledWith(undefined, { - note: { - note: '', - lock_version: undefined, - update_task: { index, checked, line_number: lineNumber, line_source: lineSource }, - }, - }); - - done(); - }); - }); - }); - - describe('comments', function() { - var textarea = '.js-note-text'; - - beforeEach(function() { - this.notes = new Notes('', []); - - this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update'); - spyOn(this.notes, 'renderNote').and.stub(); - - $(textarea).data('autosave', { - reset: function() {}, - }); - - $('.js-comment-button').on('click', e => { - const $form = $(this); - e.preventDefault(); - this.notes.addNote($form); - this.notes.reenableTargetFormSubmitButton(e); - this.notes.resetMainTargetForm(e); - }); - }); - - it('autosizes after comment submission', function() { - $(textarea).text('This is an example comment note'); - - expect(this.autoSizeSpy).not.toHaveBeenTriggered(); - - $('.js-comment-button').click(); - - expect(this.autoSizeSpy).toHaveBeenTriggered(); - }); - - it('should not place escaped text in the comment box in case of error', function() { - const deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred.promise()); - $(textarea).text('A comment with `markup`.'); - - deferred.reject(); - $('.js-comment-button').click(); - - expect($(textarea).val()).toEqual('A comment with `markup`.'); - }); - }); - - describe('updateNote', () => { - let sampleComment; - let noteEntity; - let $form; - let $notesContainer; - let mock; - - beforeEach(() => { - this.notes = new Notes('', []); - window.gon.current_username = 'root'; - window.gon.current_user_fullname = 'Administrator'; - sampleComment = 'foo'; - noteEntity = { - id: 1234, - html: `
    • -
      ${sampleComment}
      -
    • `, - note: sampleComment, - valid: true, - }; - $form = $('form.js-main-target-form'); - $notesContainer = $('ul.main-notes-list'); - $form.find('textarea.js-note-text').val(sampleComment); - - mock = new MockAdapter(axios); - mock.onPost(NOTES_POST_PATH).reply(200, noteEntity); - }); - - afterEach(() => { - mock.restore(); - }); - - it('updates note and resets edit form', done => { - spyOn(this.notes, 'revertNoteEditForm'); - spyOn(this.notes, 'setupNewNote'); - - $('.js-comment-button').click(); - - setTimeout(() => { - const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`); - const updatedNote = Object.assign({}, noteEntity); - updatedNote.note = 'bar'; - this.notes.updateNote(updatedNote, $targetNote); - - expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote); - expect(this.notes.setupNewNote).toHaveBeenCalled(); - - done(); - }); - }); - }); - - describe('updateNoteTargetSelector', () => { - const hash = 'note_foo'; - let $note; - - beforeEach(() => { - $note = $(`
      `); - spyOn($note, 'filter').and.callThrough(); - spyOn($note, 'toggleClass').and.callThrough(); - }); - - it('sets target when hash matches', () => { - spyOnDependency(Notes, 'getLocationHash').and.returnValue(hash); - - Notes.updateNoteTargetSelector($note); - - expect($note.filter).toHaveBeenCalledWith(`#${hash}`); - expect($note.toggleClass).toHaveBeenCalledWith('target', true); - }); - - it('unsets target when hash does not match', () => { - spyOnDependency(Notes, 'getLocationHash').and.returnValue('note_doesnotexist'); - - Notes.updateNoteTargetSelector($note); - - expect($note.toggleClass).toHaveBeenCalledWith('target', false); - }); - - it('unsets target when there is not a hash fragment anymore', () => { - spyOnDependency(Notes, 'getLocationHash').and.returnValue(null); - - Notes.updateNoteTargetSelector($note); - - expect($note.toggleClass).toHaveBeenCalledWith('target', false); - }); - }); - - describe('renderNote', () => { - let notes; - let note; - let $notesList; - - beforeEach(() => { - note = { - id: 1, - valid: true, - note: 'heya', - html: '
      heya
      ', - }; - $notesList = jasmine.createSpyObj('$notesList', ['find', 'append']); - - notes = jasmine.createSpyObj('notes', [ - 'setupNewNote', - 'refresh', - 'collapseLongCommitList', - 'updateNotesCount', - 'putConflictEditWarningInPlace', - ]); - notes.taskList = jasmine.createSpyObj('tasklist', ['init']); - notes.note_ids = []; - notes.updatedNotesTrackingMap = {}; - - spyOn(Notes, 'isNewNote').and.callThrough(); - spyOn(Notes, 'isUpdatedNote').and.callThrough(); - spyOn(Notes, 'animateAppendNote').and.callThrough(); - spyOn(Notes, 'animateUpdateNote').and.callThrough(); - }); - - describe('when adding note', () => { - it('should call .animateAppendNote', () => { - Notes.isNewNote.and.returnValue(true); - Notes.prototype.renderNote.call(notes, note, null, $notesList); - - expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList); - }); - }); - - describe('when note was edited', () => { - it('should call .animateUpdateNote', () => { - Notes.isNewNote.and.returnValue(false); - Notes.isUpdatedNote.and.returnValue(true); - const $note = $('
      '); - $notesList.find.and.returnValue($note); - const $newNote = $(note.html); - Notes.animateUpdateNote.and.returnValue($newNote); - - Notes.prototype.renderNote.call(notes, note, null, $notesList); - - expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note); - expect(notes.setupNewNote).toHaveBeenCalledWith($newNote); - }); - - describe('while editing', () => { - it('should update textarea if nothing has been touched', () => { - Notes.isNewNote.and.returnValue(false); - Notes.isUpdatedNote.and.returnValue(true); - const $note = $(`
      -
      initial
      - -
      `); - $notesList.find.and.returnValue($note); - Notes.prototype.renderNote.call(notes, note, null, $notesList); - - expect($note.find('.js-note-text').val()).toEqual(note.note); - }); - - it('should call .putConflictEditWarningInPlace', () => { - Notes.isNewNote.and.returnValue(false); - Notes.isUpdatedNote.and.returnValue(true); - const $note = $(`
      -
      initial
      - -
      `); - $notesList.find.and.returnValue($note); - Notes.prototype.renderNote.call(notes, note, null, $notesList); - - expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note); - }); - }); - }); - }); - - describe('isUpdatedNote', () => { - it('should consider same note text as the same', () => { - const result = Notes.isUpdatedNote( - { - note: 'initial', - }, - $(`
      -
      initial
      -
      `), - ); - - expect(result).toEqual(false); - }); - - it('should consider same note with trailing newline as the same', () => { - const result = Notes.isUpdatedNote( - { - note: 'initial\n', - }, - $(`
      -
      initial\n
      -
      `), - ); - - expect(result).toEqual(false); - }); - - it('should consider different notes as different', () => { - const result = Notes.isUpdatedNote( - { - note: 'foo', - }, - $(`
      -
      bar
      -
      `), - ); - - expect(result).toEqual(true); - }); - }); - - describe('renderDiscussionNote', () => { - let discussionContainer; - let note; - let notes; - let $form; - let row; - - beforeEach(() => { - note = { - html: '
    • ', - discussion_html: '
      ', - discussion_id: 1, - discussion_resolvable: false, - diff_discussion_html: false, - }; - $form = jasmine.createSpyObj('$form', ['closest', 'find']); - $form.length = 1; - row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']); - - notes = jasmine.createSpyObj('notes', ['isParallelView', 'updateNotesCount']); - notes.note_ids = []; - - spyOn(Notes, 'isNewNote'); - spyOn(Notes, 'animateAppendNote'); - Notes.isNewNote.and.returnValue(true); - notes.isParallelView.and.returnValue(false); - row.prevAll.and.returnValue(row); - row.first.and.returnValue(row); - row.find.and.returnValue(row); - }); - - describe('Discussion root note', () => { - let body; - - beforeEach(() => { - body = jasmine.createSpyObj('body', ['attr']); - discussionContainer = { length: 0 }; - - $form.closest.and.returnValues(row, $form); - $form.find.and.returnValues(discussionContainer); - body.attr.and.returnValue(''); - }); - - it('should call Notes.animateAppendNote', () => { - Notes.prototype.renderDiscussionNote.call(notes, note, $form); - - expect(Notes.animateAppendNote).toHaveBeenCalledWith( - note.discussion_html, - $('.main-notes-list'), - ); - }); - - it('should append to row selected with line_code', () => { - $form.length = 0; - note.discussion_line_code = 'line_code'; - note.diff_discussion_html = ''; - - const line = document.createElement('div'); - line.id = note.discussion_line_code; - document.body.appendChild(line); - - $form.closest.and.returnValues($form); - - Notes.prototype.renderDiscussionNote.call(notes, note, $form); - - expect(line.nextSibling.outerHTML).toEqual(note.diff_discussion_html); - }); - }); - - describe('Discussion sub note', () => { - beforeEach(() => { - discussionContainer = { length: 1 }; - - $form.closest.and.returnValues(row, $form); - $form.find.and.returnValues(discussionContainer); - - Notes.prototype.renderDiscussionNote.call(notes, note, $form); - }); - - it('should call Notes.animateAppendNote', () => { - expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer); - }); - }); - }); - - describe('animateAppendNote', () => { - let noteHTML; - let $notesList; - let $resultantNote; - - beforeEach(() => { - noteHTML = '
      '; - $notesList = jasmine.createSpyObj('$notesList', ['append']); - - $resultantNote = Notes.animateAppendNote(noteHTML, $notesList); - }); - - it('should have `fade-in-full` class', () => { - expect($resultantNote.hasClass('fade-in-full')).toEqual(true); - }); - - it('should append note to the notes list', () => { - expect($notesList.append).toHaveBeenCalledWith($resultantNote); - }); - }); - - describe('animateUpdateNote', () => { - let noteHTML; - let $note; - let $updatedNote; - - beforeEach(() => { - noteHTML = '
      '; - $note = jasmine.createSpyObj('$note', ['replaceWith']); - - $updatedNote = Notes.animateUpdateNote(noteHTML, $note); - }); - - it('should have `fade-in` class', () => { - expect($updatedNote.hasClass('fade-in')).toEqual(true); - }); - - it('should call replaceWith on $note', () => { - expect($note.replaceWith).toHaveBeenCalledWith($updatedNote); - }); - }); - - describe('putEditFormInPlace', () => { - it('should call GLForm with GFM parameter passed through', () => { - const notes = new Notes('', []); - const $el = $(` -
      -
      -
      - `); - - notes.putEditFormInPlace($el); - - expect(notes.glForm.enableGFM).toBeTruthy(); - }); - }); - - describe('postComment & updateComment', () => { - const sampleComment = 'foo'; - const updatedComment = 'bar'; - const note = { - id: 1234, - html: `
    • -
      ${sampleComment}
      -
    • `, - note: sampleComment, - valid: true, - }; - let $form; - let $notesContainer; - let mock; - - function mockNotesPost() { - mock.onPost(NOTES_POST_PATH).reply(200, note); - } - - function mockNotesPostError() { - mock.onPost(NOTES_POST_PATH).networkError(); - } - - beforeEach(() => { - mock = new MockAdapter(axios); - - this.notes = new Notes('', []); - window.gon.current_username = 'root'; - window.gon.current_user_fullname = 'Administrator'; - $form = $('form.js-main-target-form'); - $notesContainer = $('ul.main-notes-list'); - $form.find('textarea.js-note-text').val(sampleComment); - }); - - afterEach(() => { - mock.restore(); - }); - - it('should show placeholder note while new comment is being posted', () => { - mockNotesPost(); - - $('.js-comment-button').click(); - - expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0); - }); - - it('should remove placeholder note when new comment is done posting', done => { - mockNotesPost(); - - $('.js-comment-button').click(); - - setTimeout(() => { - expect($notesContainer.find('.note.being-posted').length).toEqual(0); - - done(); - }); - }); - - describe('postComment', () => { - it('disables the submit button', done => { - const $submitButton = $form.find('.js-comment-submit-button'); - - expect($submitButton).not.toBeDisabled(); - const dummyEvent = { - preventDefault() {}, - target: $submitButton, - }; - mock.onPost(NOTES_POST_PATH).replyOnce(() => { - expect($submitButton).toBeDisabled(); - return [200, note]; - }); - - this.notes - .postComment(dummyEvent) - .then(() => { - expect($submitButton).not.toBeDisabled(); - }) - .then(done) - .catch(done.fail); - }); - }); - - it('should show actual note element when new comment is done posting', done => { - mockNotesPost(); - - $('.js-comment-button').click(); - - setTimeout(() => { - expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0); - - done(); - }); - }); - - it('should reset Form when new comment is done posting', done => { - mockNotesPost(); - - $('.js-comment-button').click(); - - setTimeout(() => { - expect($form.find('textarea.js-note-text').val()).toEqual(''); - - done(); - }); - }); - - it('should show flash error message when new comment failed to be posted', done => { - mockNotesPostError(); - - $('.js-comment-button').click(); - - setTimeout(() => { - expect( - $notesContainer - .parent() - .find('.flash-container .flash-text') - .is(':visible'), - ).toEqual(true); - - done(); - }); - }); - - it('should show flash error message when comment failed to be updated', done => { - mockNotesPost(); - - $('.js-comment-button').click(); - - timeoutPromise() - .then(() => { - const $noteEl = $notesContainer.find(`#note_${note.id}`); - $noteEl.find('.js-note-edit').click(); - $noteEl.find('textarea.js-note-text').val(updatedComment); - - mock.restore(); - - mockNotesPostError(); - - $noteEl.find('.js-comment-save-button').click(); - }) - .then(timeoutPromise) - .then(() => { - const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`); - - expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals - expect( - $updatedNoteEl - .find('.note-text') - .text() - .trim(), - ).toEqual(sampleComment); // See if comment reverted back to original - - expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown - - done(); - }) - .catch(done.fail); - }, 2000); - }); - - describe('postComment with Slash commands', () => { - const sampleComment = '/assign @root\n/award :100:'; - const note = { - commands_changes: { - assignee_id: 1, - emoji_award: '100', - }, - errors: { - commands_only: ['Commands applied'], - }, - valid: false, - }; - let $form; - let $notesContainer; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onPost(NOTES_POST_PATH).reply(200, note); - - this.notes = new Notes('', []); - window.gon.current_username = 'root'; - window.gon.current_user_fullname = 'Administrator'; - gl.awardsHandler = { - addAwardToEmojiBar: () => {}, - scrollToAwards: () => {}, - }; - gl.GfmAutoComplete = { - dataSources: { - commands: '/root/test-project/autocomplete_sources/commands', - }, - }; - $form = $('form.js-main-target-form'); - $notesContainer = $('ul.main-notes-list'); - $form.find('textarea.js-note-text').val(sampleComment); - }); - - afterEach(() => { - mock.restore(); - }); - - it('should remove slash command placeholder when comment with slash commands is done posting', done => { - spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough(); - $('.js-comment-button').click(); - - expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown - - setTimeout(() => { - expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed - done(); - }); - }); - }); - - describe('update comment with script tags', () => { - const sampleComment = ''; - const updatedComment = ''; - const note = { - id: 1234, - html: `
    • -
      ${sampleComment}
      -
    • `, - note: sampleComment, - valid: true, - }; - let $form; - let $notesContainer; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onPost(NOTES_POST_PATH).reply(200, note); - - this.notes = new Notes('', []); - window.gon.current_username = 'root'; - window.gon.current_user_fullname = 'Administrator'; - $form = $('form.js-main-target-form'); - $notesContainer = $('ul.main-notes-list'); - $form.find('textarea.js-note-text').html(sampleComment); - }); - - afterEach(() => { - mock.restore(); - }); - - it('should not render a script tag', done => { - $('.js-comment-button').click(); - - setTimeout(() => { - const $noteEl = $notesContainer.find(`#note_${note.id}`); - $noteEl.find('.js-note-edit').click(); - $noteEl.find('textarea.js-note-text').html(updatedComment); - $noteEl.find('.js-comment-save-button').click(); - - const $updatedNoteEl = $notesContainer - .find(`#note_${note.id}`) - .find('.js-task-list-container'); - - expect( - $updatedNoteEl - .find('.note-text') - .text() - .trim(), - ).toEqual(''); - - done(); - }); - }); - }); - - describe('getFormData', () => { - let $form; - let sampleComment; - - beforeEach(() => { - this.notes = new Notes('', []); - - $form = $('form'); - sampleComment = 'foobar'; - }); - - it('should return form metadata object from form reference', () => { - $form.find('textarea.js-note-text').val(sampleComment); - const { formData, formContent, formAction } = this.notes.getFormData($form); - - expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1); - expect(formContent).toEqual(sampleComment); - expect(formAction).toEqual($form.attr('action')); - }); - - it('should return form metadata with sanitized formContent from form reference', () => { - spyOn(_, 'escape').and.callFake(htmlEscape); - - sampleComment = ''; - $form.find('textarea.js-note-text').val(sampleComment); - - const { formContent } = this.notes.getFormData($form); - - expect(_.escape).toHaveBeenCalledWith(sampleComment); - expect(formContent).toEqual('<script>alert("Boom!");</script>'); - }); - }); - - describe('hasQuickActions', () => { - beforeEach(() => { - this.notes = new Notes('', []); - }); - - it('should return true when comment begins with a quick action', () => { - const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; - const hasQuickActions = this.notes.hasQuickActions(sampleComment); - - expect(hasQuickActions).toBeTruthy(); - }); - - it('should return false when comment does NOT begin with a quick action', () => { - const sampleComment = 'Hey, /unassign Merging this'; - const hasQuickActions = this.notes.hasQuickActions(sampleComment); - - expect(hasQuickActions).toBeFalsy(); - }); - - it('should return false when comment does NOT have any quick actions', () => { - const sampleComment = 'Looking good, Awesome!'; - const hasQuickActions = this.notes.hasQuickActions(sampleComment); - - expect(hasQuickActions).toBeFalsy(); - }); - }); - - describe('stripQuickActions', () => { - it('should strip quick actions from the comment which begins with a quick action', () => { - this.notes = new Notes(); - const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this'; - const stripedComment = this.notes.stripQuickActions(sampleComment); - - expect(stripedComment).toBe(''); - }); - - it('should strip quick actions from the comment but leaves plain comment if it is present', () => { - this.notes = new Notes(); - const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this'; - const stripedComment = this.notes.stripQuickActions(sampleComment); - - expect(stripedComment).toBe('Merging this'); - }); - - it('should NOT strip string that has slashes within', () => { - this.notes = new Notes(); - const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1'; - const stripedComment = this.notes.stripQuickActions(sampleComment); - - expect(stripedComment).toBe(sampleComment); - }); - }); - - describe('getQuickActionDescription', () => { - const availableQuickActions = [ - { name: 'close', description: 'Close this issue', params: [] }, - { name: 'title', description: 'Change title', params: [{}] }, - { name: 'estimate', description: 'Set time estimate', params: [{}] }, - ]; - - beforeEach(() => { - this.notes = new Notes(); - }); - - it('should return executing quick action description when note has single quick action', () => { - const sampleComment = '/close'; - - expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe( - 'Applying command to close this issue', - ); - }); - - it('should return generic multiple quick action description when note has multiple quick actions', () => { - const sampleComment = '/close\n/title [Duplicate] Issue foobar'; - - expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe( - 'Applying multiple commands', - ); - }); - - it('should return generic quick action description when available quick actions list is not populated', () => { - const sampleComment = '/close\n/title [Duplicate] Issue foobar'; - - expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command'); - }); - }); - - describe('createPlaceholderNote', () => { - const sampleComment = 'foobar'; - const uniqueId = 'b1234-a4567'; - const currentUsername = 'root'; - const currentUserFullname = 'Administrator'; - const currentUserAvatar = 'avatar_url'; - - beforeEach(() => { - this.notes = new Notes('', []); - }); - - it('should return constructed placeholder element for regular note based on form contents', () => { - const $tempNote = this.notes.createPlaceholderNote({ - formContent: sampleComment, - uniqueId, - isDiscussionNote: false, - currentUsername, - currentUserFullname, - currentUserAvatar, - }); - const $tempNoteHeader = $tempNote.find('.note-header'); - - expect($tempNote.prop('nodeName')).toEqual('LI'); - expect($tempNote.attr('id')).toEqual(uniqueId); - expect($tempNote.hasClass('being-posted')).toBeTruthy(); - expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); - $tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() { - expect($(this).attr('href')).toEqual(`/${currentUsername}`); - }); - - expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar); - expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy(); - expect( - $tempNoteHeader - .find('.d-none.d-sm-inline-block') - .text() - .trim(), - ).toEqual(currentUserFullname); - - expect( - $tempNoteHeader - .find('.note-headline-light') - .text() - .trim(), - ).toEqual(`@${currentUsername}`); - - expect( - $tempNote - .find('.note-body .note-text p') - .text() - .trim(), - ).toEqual(sampleComment); - }); - - it('should return constructed placeholder element for discussion note based on form contents', () => { - const $tempNote = this.notes.createPlaceholderNote({ - formContent: sampleComment, - uniqueId, - isDiscussionNote: true, - currentUsername, - currentUserFullname, - }); - - expect($tempNote.prop('nodeName')).toEqual('LI'); - expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy(); - }); - - it('should return a escaped user name', () => { - const currentUserFullnameXSS = 'Foo '; - const $tempNote = this.notes.createPlaceholderNote({ - formContent: sampleComment, - uniqueId, - isDiscussionNote: false, - currentUsername, - currentUserFullname: currentUserFullnameXSS, - currentUserAvatar, - }); - const $tempNoteHeader = $tempNote.find('.note-header'); - - expect( - $tempNoteHeader - .find('.d-none.d-sm-inline-block') - .text() - .trim(), - ).toEqual('Foo <script>alert("XSS")</script>'); - }); - }); - - describe('createPlaceholderSystemNote', () => { - const sampleCommandDescription = 'Applying command to close this issue'; - const uniqueId = 'b1234-a4567'; - - beforeEach(() => { - this.notes = new Notes('', []); - spyOn(_, 'escape').and.callFake(htmlEscape); - }); - - it('should return constructed placeholder element for system note based on form contents', () => { - const $tempNote = this.notes.createPlaceholderSystemNote({ - formContent: sampleCommandDescription, - uniqueId, - }); - - expect($tempNote.prop('nodeName')).toEqual('LI'); - expect($tempNote.attr('id')).toEqual(uniqueId); - expect($tempNote.hasClass('being-posted')).toBeTruthy(); - expect($tempNote.hasClass('fade-in-half')).toBeTruthy(); - expect( - $tempNote - .find('.timeline-content i') - .text() - .trim(), - ).toEqual(sampleCommandDescription); - }); - }); - - describe('appendFlash', () => { - beforeEach(() => { - this.notes = new Notes(); - }); - - it('shows a flash message', () => { - this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); - - expect($('.flash-alert').is(':visible')).toBeTruthy(); - }); - }); - - describe('clearFlash', () => { - beforeEach(() => { - $(document).off('ajax:success'); - this.notes = new Notes(); - }); - - it('hides visible flash message', () => { - this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); - - this.notes.clearFlash(); - - expect($('.flash-alert').is(':visible')).toBeFalsy(); - }); - }); -}); -- cgit v1.2.1 From c763d98ed76a2a23c05b9816dee3245779313ecf Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Mon, 3 Jun 2019 17:35:39 -0400 Subject: Backport of EE Displaying Blocking MRs This MR is a backport of an EE merge request --- .../reports/components/report_section_spec.js | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'spec') diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js index 3b609484b9e..d4a3073374a 100644 --- a/spec/frontend/reports/components/report_section_spec.js +++ b/spec/frontend/reports/components/report_section_spec.js @@ -197,4 +197,44 @@ describe('Report section', () => { expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); }); }); + + describe('Success and Error slots', () => { + const createComponent = status => { + vm = mountComponentWithSlots(ReportSection, { + props: { + status, + hasIssues: true, + }, + slots: { + success: ['This is a success'], + loading: ['This is loading'], + error: ['This is an error'], + }, + }); + }; + + it('only renders success slot when status is "SUCCESS"', () => { + createComponent('SUCCESS'); + + expect(vm.$el.textContent.trim()).toContain('This is a success'); + expect(vm.$el.textContent.trim()).not.toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is loading'); + }); + + it('only renders error slot when status is "ERROR"', () => { + createComponent('ERROR'); + + expect(vm.$el.textContent.trim()).toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is a success'); + expect(vm.$el.textContent.trim()).not.toContain('This is loading'); + }); + + it('only renders loading slot when status is "LOADING"', () => { + createComponent('LOADING'); + + expect(vm.$el.textContent.trim()).toContain('This is loading'); + expect(vm.$el.textContent.trim()).not.toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is a success'); + }); + }); }); -- cgit v1.2.1 From bd624741a170a30c965ff1a2bcf9a0ae1bc763e3 Mon Sep 17 00:00:00 2001 From: Kerri Miller Date: Tue, 4 Jun 2019 07:50:41 -0700 Subject: Remove 2nd stub expectation of #last_diff_file It looks to be a stub/mock rather than strictly an expectation of the system, so dropping this to only a single invocation expected, as we've removed one of the two places #last_diff_file would be invoked. --- spec/services/suggestions/create_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb index ccd44e615a8..d95f9e3349b 100644 --- a/spec/services/suggestions/create_service_spec.rb +++ b/spec/services/suggestions/create_service_spec.rb @@ -96,7 +96,7 @@ describe Suggestions::CreateService do it 'creates no suggestion when diff file is not found' do expect_next_instance_of(DiffNote) do |diff_note| - expect(diff_note).to receive(:latest_diff_file).twice { nil } + expect(diff_note).to receive(:latest_diff_file).once { nil } end expect { subject.execute }.not_to change(Suggestion, :count) -- cgit v1.2.1 From 732a89ce239a2bb267aec4af222cf54d201d8e27 Mon Sep 17 00:00:00 2001 From: Kerri Miller Date: Tue, 4 Jun 2019 08:43:29 -0700 Subject: Update spec param expectations --- spec/services/preview_markdown_service_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index f7261cd7125..d25e9958831 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -56,7 +56,9 @@ describe PreviewMarkdownService do expect(Gitlab::Diff::SuggestionsParser) .to receive(:parse) - .with(text, position: position, project: merge_request.project) + .with(text, position: position, + project: merge_request.project, + supports_suggestion: true) .and_call_original result = service.execute -- cgit v1.2.1 From f8d93b24822937d803da4c9fcc89d50c85de89c7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 25 May 2019 22:42:42 -0700 Subject: Set project default visibility to max allowed If the default, instance-wide visibility were set to internal but a group were private, the radio button would be set on internal even though that setting were not allowed. We now consider the allowed visibility setting for the group in determining what the default should be. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/62298 --- spec/features/projects/new_project_spec.rb | 30 +++++++++++++++++++ spec/helpers/visibility_level_helper_spec.rb | 45 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) (limited to 'spec') diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index b54ea929978..033e1afe866 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -63,6 +63,36 @@ describe 'New project' do end end end + + context 'when group visibility is private but default is internal' do + before do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) + end + + it 'has private selected' do + group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE) + visit new_project_path(namespace_id: group.id) + + page.within('#blank-project-pane') do + expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked + end + end + end + + context 'when group visibility is public but user requests private' do + before do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) + end + + it 'has private selected' do + group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) + + page.within('#blank-project-pane') do + expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked + end + end + end end context 'Readme selector' do diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index e565ac8c530..25a2fcf5a81 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -162,4 +162,49 @@ describe VisibilityLevelHelper do end end end + + describe "selected_visibility_level" do + let(:group) { create(:group, :public) } + let!(:project) { create(:project, :internal, group: group) } + let!(:forked_project) { fork_project(project) } + + using RSpec::Parameterized::TableSyntax + + PUBLIC = Gitlab::VisibilityLevel::PUBLIC + INTERNAL = Gitlab::VisibilityLevel::INTERNAL + PRIVATE = Gitlab::VisibilityLevel::PRIVATE + + # This is a subset of all the permutations + where(:requested_level, :max_allowed, :global_default_level, :restricted_levels, :expected) do + PUBLIC | PUBLIC | PUBLIC | [] | PUBLIC + PUBLIC | PUBLIC | PUBLIC | [PUBLIC] | INTERNAL + INTERNAL | PUBLIC | PUBLIC | [] | INTERNAL + INTERNAL | PRIVATE | PRIVATE | [] | PRIVATE + PRIVATE | PUBLIC | PUBLIC | [] | PRIVATE + PUBLIC | PRIVATE | INTERNAL | [] | PRIVATE + PUBLIC | INTERNAL | PUBLIC | [] | INTERNAL + PUBLIC | PRIVATE | PUBLIC | [] | PRIVATE + PUBLIC | INTERNAL | INTERNAL | [] | INTERNAL + PUBLIC | PUBLIC | INTERNAL | [] | PUBLIC + end + + before do + stub_application_setting(restricted_visibility_levels: restricted_levels, + default_project_visibility: global_default_level) + end + + with_them do + it "provides correct visibility level for forked project" do + project.update(visibility_level: max_allowed) + + expect(selected_visibility_level(forked_project, requested_level)).to eq(expected) + end + + it "provides correct visibiility level for project in group" do + project.group.update(visibility_level: max_allowed) + + expect(selected_visibility_level(project, requested_level)).to eq(expected) + end + end + end end -- cgit v1.2.1 From c7dcbc03bb71b954ab781a51736db29d687eac7b Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Tue, 4 Jun 2019 11:40:07 +1200 Subject: CE backport for changes in EE MR 13389 This backports to ce a refactor of the shared example 'a controller that can serve LFS files'. The ee MR that contains the original refactor is https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/13389 The reason for the refactor was mostly the removal of the outer context as it didn't need to be there, and prevented let(:lfs_oid) from being overwritten. The shared example was also renamed to be more descriptive. --- .../projects/avatars_controller_spec.rb | 2 +- spec/controllers/projects/raw_controller_spec.rb | 2 +- .../repository_lfs_file_load_examples.rb | 116 ++++++++++----------- 3 files changed, 60 insertions(+), 60 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb index de1b9dc0bf3..d463619ad0b 100644 --- a/spec/controllers/projects/avatars_controller_spec.rb +++ b/spec/controllers/projects/avatars_controller_spec.rb @@ -39,7 +39,7 @@ describe Projects::AvatarsController do end context 'when the avatar is stored in lfs' do - it_behaves_like 'repository lfs file load' do + it_behaves_like 'a controller that can serve LFS files' do let(:filename) { 'lfs_object.iso' } let(:filepath) { "files/lfs/#{filename}" } end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 3a89d8ce032..97acd47b4da 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -42,7 +42,7 @@ describe Projects::RawController do end end - it_behaves_like 'repository lfs file load' do + it_behaves_like 'a controller that can serve LFS files' do let(:filename) { 'lfs_object.iso' } let(:filepath) { "be93687/files/lfs/#{filename}" } end diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb index 982e0317f7f..b7080c68270 100644 --- a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb +++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb @@ -9,87 +9,87 @@ # - `filepath`: path of the file (contains filename) # - `subject`: the request to be made to the controller. Example: # subject { get :show, namespace_id: project.namespace, project_id: project } -shared_examples 'repository lfs file load' do - context 'when file is stored in lfs' do - let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' } - let(:lfs_size) { '1575078' } - let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) } +shared_examples 'a controller that can serve LFS files' do + let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' } + let(:lfs_size) { '1575078' } + let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) } + + context 'when lfs is enabled' do + before do + allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true) + end - context 'when lfs is enabled' do + context 'when project has access' do before do - allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true) + project.lfs_objects << lfs_object + allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true) + allow(controller).to receive(:send_file) { controller.head :ok } end - context 'when project has access' do - before do - project.lfs_objects << lfs_object - allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true) - allow(controller).to receive(:send_file) { controller.head :ok } - end + it 'serves the file' do + lfs_uploader = LfsObjectUploader.new(lfs_object) - it 'serves the file' do - # Notice the filename= is omitted from the disposition; this is because - # Rails 5 will append this header in send_file - expect(controller).to receive(:send_file) - .with( - "#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", - filename: filename, - disposition: %Q(attachment; filename*=UTF-8''#{filename})) + # Notice the filename= is omitted from the disposition; this is because + # Rails 5 will append this header in send_file + expect(controller).to receive(:send_file) + .with( + File.join(lfs_uploader.root, lfs_uploader.store_dir, lfs_uploader.filename), + filename: filename, + disposition: %Q(attachment; filename*=UTF-8''#{filename})) - subject + subject - expect(response).to have_gitlab_http_status(200) - end + expect(response).to have_gitlab_http_status(200) + end - context 'and lfs uses object storage' do - let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) } + context 'and lfs uses object storage' do + let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) } - before do - stub_lfs_object_storage - lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) - end + before do + stub_lfs_object_storage + lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) + end - it 'responds with redirect to file' do - subject + it 'responds with redirect to file' do + subject - expect(response).to have_gitlab_http_status(302) - expect(response.location).to include(lfs_object.reload.file.path) - end + expect(response).to have_gitlab_http_status(302) + expect(response.location).to include(lfs_object.reload.file.path) + end - it 'sets content disposition' do - subject + it 'sets content disposition' do + subject - file_uri = URI.parse(response.location) - params = CGI.parse(file_uri.query) + file_uri = URI.parse(response.location) + params = CGI.parse(file_uri.query) - expect(params["response-content-disposition"].first).to eq(%q(attachment; filename="lfs_object.iso"; filename*=UTF-8''lfs_object.iso)) - end + expect(params["response-content-disposition"].first).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename})) end end + end - context 'when project does not have access' do - it 'does not serve the file' do - subject + context 'when project does not have access' do + it 'does not serve the file' do + subject - expect(response).to have_gitlab_http_status(404) - end + expect(response).to have_gitlab_http_status(404) end end + end - context 'when lfs is not enabled' do - before do - allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false) - end + context 'when lfs is not enabled' do + before do + allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false) + end - it 'delivers ASCII file' do - subject + it 'delivers ASCII file' do + subject - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') - expect(response.header['Content-Disposition']) - .to eq('inline') - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') - end + expect(response).to have_gitlab_http_status(200) + expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') + expect(response.header['Content-Disposition']) + .to eq('inline') + expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') end end end -- cgit v1.2.1 From 31afb0e8addf73f6cbcf4213ad2c386488c95715 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 27 May 2019 19:26:39 +0700 Subject: Introduce service for merge request pipeline creation We don't have a way to create MR pipeline today, this is one of the headaches for users. This change is a preliminary work for the capability. --- .../merge_requests/create_pipeline_service_spec.rb | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 spec/services/merge_requests/create_pipeline_service_spec.rb (limited to 'spec') diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb new file mode 100644 index 00000000000..9479439bde8 --- /dev/null +++ b/spec/services/merge_requests/create_pipeline_service_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::CreatePipelineService do + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + let(:service) { described_class.new(project, user, params) } + let(:params) { {} } + + before do + project.add_developer(user) + end + + describe '#execute' do + subject { service.execute(merge_request) } + + before do + stub_ci_pipeline_yaml_file(YAML.dump(config)) + end + + let(:config) do + { rspec: { script: 'echo', only: ['merge_requests'] } } + end + + let(:merge_request) do + create(:merge_request, + source_branch: 'feature', + source_project: project, + target_branch: 'master', + target_project: project) + end + + it 'creates a detached merge request pipeline' do + expect { subject }.to change { Ci::Pipeline.count }.by(1) + + expect(subject).to be_persisted + expect(subject).to be_detached_merge_request_pipeline + end + + context 'when service is called multiple times' do + it 'creates a pipeline once' do + expect do + service.execute(merge_request) + service.execute(merge_request) + end.to change { Ci::Pipeline.count }.by(1) + end + + context 'when allow_duplicate option is true' do + let(:params) { { allow_duplicate: true } } + + it 'creates pipelines multiple times' do + expect do + service.execute(merge_request) + service.execute(merge_request) + end.to change { Ci::Pipeline.count }.by(2) + end + end + end + + context 'when .gitlab-ci.yml does not have only: [merge_requests] keyword' do + let(:config) do + { rspec: { script: 'echo' } } + end + + it 'does not create a pipeline' do + expect { subject }.not_to change { Ci::Pipeline.count } + end + end + end +end -- cgit v1.2.1 From 2a01b33e6ac27e84d842f3040d7adf799f7cf258 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Jun 2019 14:11:03 +0700 Subject: Create BaseService for Auto Merge architecture It abstracts some codes for common methods in AutoMerge::*Services. --- spec/models/merge_request_spec.rb | 16 --- spec/services/auto_merge/base_service_spec.rb | 144 ++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 16 deletions(-) create mode 100644 spec/services/auto_merge/base_service_spec.rb (limited to 'spec') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 956c5675f38..fc28c216b21 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1088,22 +1088,6 @@ describe MergeRequest do end end - describe "#reset_auto_merge" do - let(:merge_if_green) do - create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user), - merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" } - end - - it "sets the item to false" do - merge_if_green.reset_auto_merge - merge_if_green.reload - - expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey - expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil - expect(merge_if_green.merge_params["commit_message"]).to be_nil - end - end - describe '#committers' do it 'returns all the committers of every commit in the merge request' do users = subject.commits.without_merge_commits.map(&:committer_email).uniq.map do |email| diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb new file mode 100644 index 00000000000..197fa16961d --- /dev/null +++ b/spec/services/auto_merge/base_service_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AutoMerge::BaseService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { described_class.new(project, user, params) } + let(:merge_request) { create(:merge_request) } + let(:params) { {} } + + describe '#execute' do + subject { service.execute(merge_request) } + + it 'sets properies to the merge request' do + subject + + merge_request.reload + expect(merge_request).to be_auto_merge_enabled + expect(merge_request.merge_user).to eq(user) + expect(merge_request.auto_merge_strategy).to eq('base') + end + + it 'yields block' do + expect { |b| service.execute(merge_request, &b) }.to yield_control.once + end + + it 'returns activated strategy name' do + is_expected.to eq(:base) + end + + context 'when merge parameters are given' do + let(:params) do + { + 'commit_message' => "Merge branch 'patch-12' into 'master'", + 'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18", + 'should_remove_source_branch' => false, + 'squash' => false, + 'squash_commit_message' => "Update README.md" + } + end + + it 'sets merge parameters' do + subject + + merge_request.reload + expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'") + expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18') + expect(merge_request.merge_params['should_remove_source_branch']).to eq(false) + expect(merge_request.merge_params['squash']).to eq(false) + expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md') + end + end + + context 'when strategy is merge when pipeline succeeds' do + let(:service) { AutoMerge::MergeWhenPipelineSucceedsService.new(project, user) } + + it 'sets the auto merge strategy' do + subject + + merge_request.reload + expect(merge_request.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS) + end + + it 'returns activated strategy name' do + is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS.to_sym) + end + end + + context 'when failed to save' do + before do + allow(merge_request).to receive(:save) { false } + end + + it 'does not yield block' do + expect { |b| service.execute(merge_request, &b) }.not_to yield_control + end + + it 'returns failed' do + is_expected.to eq(:failed) + end + end + end + + describe '#cancel' do + subject { service.cancel(merge_request) } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it 'removes properies from the merge request' do + subject + + merge_request.reload + expect(merge_request).not_to be_auto_merge_enabled + expect(merge_request.merge_user).to be_nil + expect(merge_request.auto_merge_strategy).to be_nil + end + + it 'yields block' do + expect { |b| service.cancel(merge_request, &b) }.to yield_control.once + end + + it 'returns success status' do + expect(subject[:status]).to eq(:success) + end + + context 'when merge params are set' do + before do + merge_request.update!(merge_params: + { + 'should_remove_source_branch' => false, + 'commit_message' => "Merge branch 'patch-12' into 'master'", + 'squash_commit_message' => "Update README.md", + 'auto_merge_strategy' => 'merge_when_pipeline_succeeds' + }) + end + + it 'removes merge parameters' do + subject + + merge_request.reload + expect(merge_request.merge_params['should_remove_source_branch']).to be_nil + expect(merge_request.merge_params['commit_message']).to be_nil + expect(merge_request.merge_params['squash_commit_message']).to be_nil + expect(merge_request.merge_params['auto_merge_strategy']).to be_nil + end + end + + context 'when failed to save' do + before do + allow(merge_request).to receive(:save) { false } + end + + it 'does not yield block' do + expect { |b| service.execute(merge_request, &b) }.not_to yield_control + end + + it 'returns error status' do + expect(subject[:status]).to eq(:error) + expect(subject[:message]).to eq("Can't cancel the automatic merge") + end + end + end +end -- cgit v1.2.1 From bf8ab1243356e5732ce1a07ea5fb3ea98549635d Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Mon, 3 Jun 2019 17:00:31 -0400 Subject: Default MR checkbox to true in most cases - Pull the new MR option out into it's own component - Default MR checkbox to true when creating a new MR and committing to a branch that does not have an MR - Still change the MR checkbox to false when a user is on a branch that already has an MR - Hide MR option when on a branch that already has an MR and committing to current branch - Don't default to true when committing directly to master --- .../ide/stores/modules/commit/mutations_spec.js | 15 ++ .../ide/components/commit_sidebar/actions_spec.js | 43 ------ .../new_merge_request_option_spec.js | 73 ++++++++++ spec/javascripts/ide/mock_data.js | 1 + spec/javascripts/ide/stores/getters_spec.js | 32 +++++ .../ide/stores/modules/commit/actions_spec.js | 157 ++++++++++++++++++++- .../ide/stores/modules/commit/getters_spec.js | 29 ---- 7 files changed, 276 insertions(+), 74 deletions(-) create mode 100644 spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js (limited to 'spec') diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js index 40d47aaad03..246500a2f34 100644 --- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js +++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js @@ -54,5 +54,20 @@ describe('IDE commit module mutations', () => { expect(state.shouldCreateMR).toBe(false); }); + + it('sets shouldCreateMR to given value when passed in', () => { + state.shouldCreateMR = false; + mutations.TOGGLE_SHOULD_CREATE_MR(state, false); + + expect(state.shouldCreateMR).toBe(false); + }); + }); + + describe('INTERACT_WITH_NEW_MR', () => { + it('sets interactedWithNewMR to true', () => { + mutations.INTERACT_WITH_NEW_MR(state); + + expect(state.interactedWithNewMR).toBe(true); + }); }); }); diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js index 23e6a055518..b903abe63fc 100644 --- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js @@ -73,47 +73,4 @@ describe('IDE commit sidebar actions', () => { expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc); }); }); - - describe('create new MR checkbox', () => { - it('disables `createMR` button when an MR already exists and committing to current branch', () => { - createComponent({ hasMR: true, commitAction: consts.COMMIT_TO_CURRENT_BRANCH }); - - expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(true); - }); - - it('does not disable checkbox if MR does not exist', () => { - createComponent({ hasMR: false }); - - expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false); - }); - - it('does not disable checkbox when creating a new branch', () => { - createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH }); - - expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false); - }); - - it('toggles off new MR when switching back to commit to current branch and MR exists', () => { - createComponent({ - commitAction: consts.COMMIT_TO_NEW_BRANCH, - shouldCreateMR: true, - }); - const currentBranchRadio = vm.$el.querySelector( - `input[value="${consts.COMMIT_TO_CURRENT_BRANCH}"`, - ); - currentBranchRadio.click(); - - vm.$nextTick(() => { - expect(vm.$store.state.commit.shouldCreateMR).toBe(false); - }); - }); - - it('toggles `shouldCreateMR` when clicking checkbox', () => { - createComponent(); - const el = vm.$el.querySelector('input[type="checkbox"]'); - el.dispatchEvent(new Event('change')); - - expect(vm.$store.state.commit.shouldCreateMR).toBe(true); - }); - }); }); diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js new file mode 100644 index 00000000000..7017bfcd6a6 --- /dev/null +++ b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js @@ -0,0 +1,73 @@ +import Vue from 'vue'; +import store from '~/ide/stores'; +import consts from '~/ide/stores/modules/commit/constants'; +import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { projectData } from 'spec/ide/mock_data'; +import { resetStore } from 'spec/ide/helpers'; + +describe('create new MR checkbox', () => { + let vm; + const createComponent = ({ + hasMR = false, + commitAction = consts.COMMIT_TO_NEW_BRANCH, + currentBranchId = 'master', + } = {}) => { + const Component = Vue.extend(NewMergeRequestOption); + + vm = createComponentWithStore(Component, store); + + vm.$store.state.currentBranchId = currentBranchId; + vm.$store.state.currentProjectId = 'abcproject'; + vm.$store.state.commit.commitAction = commitAction; + Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData }); + + if (hasMR) { + vm.$store.state.currentMergeRequestId = '1'; + vm.$store.state.projects[store.state.currentProjectId].mergeRequests[ + store.state.currentMergeRequestId + ] = { foo: 'bar' }; + } + + return vm.$mount(); + }; + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + it('is hidden when an MR already exists and committing to current branch', () => { + createComponent({ + hasMR: true, + commitAction: consts.COMMIT_TO_CURRENT_BRANCH, + currentBranchId: 'feature', + }); + + expect(vm.$el.textContent).toBe(''); + }); + + it('does not hide checkbox if MR does not exist', () => { + createComponent({ hasMR: false }); + + expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false); + }); + + it('does not hide checkbox when creating a new branch', () => { + createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH }); + + expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false); + }); + + it('dispatches toggleShouldCreateMR when clicking checkbox', () => { + createComponent(); + const el = vm.$el.querySelector('input[type="checkbox"]'); + spyOn(vm.$store, 'dispatch'); + el.dispatchEvent(new Event('change')); + + expect(vm.$store.dispatch.calls.allArgs()).toEqual( + jasmine.arrayContaining([['commit/toggleShouldCreateMR', jasmine.any(Object)]]), + ); + }); +}); diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index 4fe826943b2..570a396c5e3 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -16,6 +16,7 @@ export const projectData = { }, mergeRequests: {}, merge_requests_enabled: true, + default_branch: 'master', }; export const pipelines = [ diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js index 9c135661997..735bbd47f55 100644 --- a/spec/javascripts/ide/stores/getters_spec.js +++ b/spec/javascripts/ide/stores/getters_spec.js @@ -180,6 +180,38 @@ describe('IDE store getters', () => { }); }); + describe('isOnDefaultBranch', () => { + it('returns false when no project exists', () => { + const localGetters = { + currentProject: undefined, + }; + + expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy(); + }); + + it("returns true when project's default branch matches current branch", () => { + const localGetters = { + currentProject: { + default_branch: 'master', + }, + branchName: 'master', + }; + + expect(getters.isOnDefaultBranch({}, localGetters)).toBeTruthy(); + }); + + it("returns false when project's default branch doesn't match current branch", () => { + const localGetters = { + currentProject: { + default_branch: 'master', + }, + branchName: 'feature', + }; + + expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy(); + }); + }); + describe('packageJson', () => { it('returns package.json entry', () => { localState.entries['package.json'] = { name: 'package.json' }; diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index 4413a12fac4..5f7272311c8 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -1,9 +1,12 @@ -import actions from '~/ide/stores/actions'; +import rootActions from '~/ide/stores/actions'; import store from '~/ide/stores'; import service from '~/ide/services'; import router from '~/ide/ide_router'; import eventHub from '~/ide/eventhub'; import consts from '~/ide/stores/modules/commit/constants'; +import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types'; +import * as actions from '~/ide/stores/modules/commit/actions'; +import testAction from '../../../../helpers/vuex_action_helper'; import { commitActionTypes } from '~/ide/constants'; import { resetStore, file } from 'spec/ide/helpers'; @@ -225,7 +228,7 @@ describe('IDE commit module actions', () => { let visitUrl; beforeEach(() => { - visitUrl = spyOnDependency(actions, 'visitUrl'); + visitUrl = spyOnDependency(rootActions, 'visitUrl'); document.body.innerHTML += '
      '; @@ -523,4 +526,154 @@ describe('IDE commit module actions', () => { }); }); }); + + describe('toggleShouldCreateMR', () => { + it('commits both toggle and interacting with MR checkbox actions', done => { + testAction( + actions.toggleShouldCreateMR, + {}, + store.state, + [ + { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR }, + { type: mutationTypes.INTERACT_WITH_NEW_MR }, + ], + [], + done, + ); + }); + }); + + describe('setShouldCreateMR', () => { + beforeEach(() => { + store.state.projects = { + project: { + default_branch: 'master', + branches: { + master: { + name: 'master', + }, + feature: { + name: 'feature', + }, + }, + }, + }; + + store.state.currentProjectId = 'project'; + }); + + it('sets to false when the current branch already has an MR', done => { + store.state.commit.currentMergeRequestId = 1; + store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + store.state.currentMergeRequestId = '1'; + store.state.currentBranchId = 'feature'; + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/setShouldCreateMR') + .then(() => { + expect(store.commit.calls.allArgs()[0]).toEqual( + jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]), + ); + done(); + }) + .catch(done.fail); + }); + + it('changes to false when current branch is the default branch and user has not interacted', done => { + store.state.commit.interactedWithNewMR = false; + store.state.currentBranchId = 'master'; + store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/setShouldCreateMR') + .then(() => { + expect(store.commit.calls.allArgs()[0]).toEqual( + jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]), + ); + done(); + }) + .catch(done.fail); + }); + + it('changes to true when "create new branch" is selected and user has not interacted', done => { + store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; + store.state.commit.interactedWithNewMR = false; + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/setShouldCreateMR') + .then(() => { + expect(store.commit.calls.allArgs()[0]).toEqual( + jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, true]), + ); + done(); + }) + .catch(done.fail); + }); + + it('does not change anything if user has interacted and comitting to new branch', done => { + store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; + store.state.commit.interactedWithNewMR = true; + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/setShouldCreateMR') + .then(() => { + expect(store.commit).not.toHaveBeenCalled(); + done(); + }) + .catch(done.fail); + }); + + it('does not change anything if user has interacted and comitting to branch without MR', done => { + store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + store.state.commit.currentMergeRequestId = null; + store.state.commit.interactedWithNewMR = true; + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/setShouldCreateMR') + .then(() => { + expect(store.commit).not.toHaveBeenCalled(); + done(); + }) + .catch(done.fail); + }); + + it('still changes to false if hiding the checkbox', done => { + store.state.currentBranchId = 'feature'; + store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + store.state.currentMergeRequestId = '1'; + store.state.commit.interactedWithNewMR = true; + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/setShouldCreateMR') + .then(() => { + expect(store.commit.calls.allArgs()[0]).toEqual( + jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]), + ); + done(); + }) + .catch(done.fail); + }); + + it('does not change to false when on master and user has interacted even if MR exists', done => { + store.state.currentBranchId = 'master'; + store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + store.state.currentMergeRequestId = '1'; + store.state.commit.interactedWithNewMR = true; + spyOn(store, 'commit').and.callThrough(); + + store + .dispatch('commit/setShouldCreateMR') + .then(() => { + expect(store.commit).not.toHaveBeenCalled(); + done(); + }) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js index e00fd7199d7..6e71a790deb 100644 --- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js @@ -144,33 +144,4 @@ describe('IDE commit module getters', () => { }); }); }); - - describe('shouldDisableNewMrOption', () => { - it('returns false if commitAction `COMMIT_TO_NEW_BRANCH`', () => { - state.commitAction = consts.COMMIT_TO_NEW_BRANCH; - const rootState = { - currentMergeRequest: { foo: 'bar' }, - }; - - expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy(); - }); - - it('returns false if there is no current merge request', () => { - state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; - const rootState = { - currentMergeRequest: null, - }; - - expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy(); - }); - - it('returns true an MR exists and commit action is `COMMIT_TO_CURRENT_BRANCH`', () => { - state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; - const rootState = { - currentMergeRequest: { foo: 'bar' }, - }; - - expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeTruthy(); - }); - }); }); -- cgit v1.2.1 From 6f7b6ba072462adb079f90eea9498317e258cb0d Mon Sep 17 00:00:00 2001 From: Sarah Yasonik Date: Thu, 6 Jun 2019 07:30:04 +0000 Subject: Use the selected time window for metrics dashboard --- spec/javascripts/monitoring/dashboard_spec.js | 70 +++++++++++++++++++-------- 1 file changed, 51 insertions(+), 19 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index cea8cb18918..80b9b740b94 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -38,6 +38,7 @@ describe('Dashboard', () => { let DashboardComponent; let mock; let store; + let component; beforeEach(() => { setFixtures(` @@ -59,12 +60,13 @@ describe('Dashboard', () => { }); afterEach(() => { + component.$destroy(); mock.restore(); }); describe('no metrics are available yet', () => { it('shows a getting started empty state when no metrics are present', () => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, showTimeWindowDropdown: false }, store, @@ -81,7 +83,7 @@ describe('Dashboard', () => { }); it('shows up a loading state', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false }, store, @@ -94,7 +96,7 @@ describe('Dashboard', () => { }); it('hides the legend when showLegend is false', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -114,7 +116,7 @@ describe('Dashboard', () => { }); it('hides the group panels when showPanels is false', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -134,7 +136,7 @@ describe('Dashboard', () => { }); it('renders the environments dropdown with a number of environments', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -165,7 +167,7 @@ describe('Dashboard', () => { }); it('hides the environments dropdown list when there is no environments', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -195,7 +197,7 @@ describe('Dashboard', () => { }); it('renders the environments dropdown with a single active element', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -228,7 +230,7 @@ describe('Dashboard', () => { }); it('hides the dropdown', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -249,7 +251,7 @@ describe('Dashboard', () => { }); it('does not show the time window dropdown when the feature flag is not set', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -270,7 +272,7 @@ describe('Dashboard', () => { }); it('renders the time window dropdown with a set of options', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -295,10 +297,46 @@ describe('Dashboard', () => { }); }); + it('fetches the metrics data with proper time window', done => { + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: true, + }, + store, + }); + + spyOn(component.$store, 'dispatch').and.stub(); + const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff'); + + component.$store.commit( + `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`, + '/environments', + ); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + + component.$mount(); + + Vue.nextTick() + .then(() => { + expect(component.$store.dispatch).toHaveBeenCalled(); + expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow); + + done(); + }) + .catch(done.fail); + }); + it('shows a specific time window selected from the url params', done => { spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']); - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, store, @@ -319,7 +357,7 @@ describe('Dashboard', () => { '', ]); - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, store, @@ -344,7 +382,7 @@ describe('Dashboard', () => { }); it('sets elWidth to page width when the sidebar is resized', done => { - const component = new DashboardComponent({ + component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, @@ -374,16 +412,10 @@ describe('Dashboard', () => { }); describe('external dashboard link', () => { - let component; - beforeEach(() => { mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); }); - afterEach(() => { - component.$destroy(); - }); - describe('with feature flag enabled', () => { beforeEach(() => { component = new DashboardComponent({ -- cgit v1.2.1 From 39a27b7c467c5e72484463e0a7b4e3423268def4 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 6 Jun 2019 07:36:45 +0000 Subject: CE Backport: Show tree within Epic containing child Epics and Issues --- .../vue_shared/droplab_dropdown_button_spec.js | 136 +++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 spec/frontend/vue_shared/droplab_dropdown_button_spec.js (limited to 'spec') diff --git a/spec/frontend/vue_shared/droplab_dropdown_button_spec.js b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js new file mode 100644 index 00000000000..22295721328 --- /dev/null +++ b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js @@ -0,0 +1,136 @@ +import { mount, createLocalVue } from '@vue/test-utils'; + +import DroplabDropdownButton from '~/vue_shared/components/droplab_dropdown_button.vue'; + +const mockActions = [ + { + title: 'Foo', + description: 'Some foo action', + }, + { + title: 'Bar', + description: 'Some bar action', + }, +]; + +const createComponent = ({ + size = '', + dropdownClass = '', + actions = mockActions, + defaultAction = 0, +}) => { + const localVue = createLocalVue(); + + return mount(DroplabDropdownButton, { + localVue, + propsData: { + size, + dropdownClass, + actions, + defaultAction, + }, + }); +}; + +describe('DroplabDropdownButton', () => { + let wrapper; + + beforeEach(() => { + wrapper = createComponent({}); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('data', () => { + it('contains `selectedAction` representing value of `defaultAction` prop', () => { + expect(wrapper.vm.selectedAction).toBe(0); + }); + }); + + describe('computed', () => { + describe('selectedActionTitle', () => { + it('returns string containing title of selected action', () => { + wrapper.setData({ selectedAction: 0 }); + + expect(wrapper.vm.selectedActionTitle).toBe(mockActions[0].title); + + wrapper.setData({ selectedAction: 1 }); + + expect(wrapper.vm.selectedActionTitle).toBe(mockActions[1].title); + }); + }); + + describe('buttonSizeClass', () => { + it('returns string containing button sizing class based on `size` prop', done => { + const wrapperWithSize = createComponent({ + size: 'sm', + }); + + wrapperWithSize.vm.$nextTick(() => { + expect(wrapperWithSize.vm.buttonSizeClass).toBe('btn-sm'); + + done(); + wrapperWithSize.destroy(); + }); + }); + }); + }); + + describe('methods', () => { + describe('handlePrimaryActionClick', () => { + it('emits `onActionClick` event on component with selectedAction object as param', () => { + jest.spyOn(wrapper.vm, '$emit'); + + wrapper.setData({ selectedAction: 0 }); + wrapper.vm.handlePrimaryActionClick(); + + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionClick', mockActions[0]); + }); + }); + + describe('handleActionClick', () => { + it('emits `onActionSelect` event on component with selectedAction index as param', () => { + jest.spyOn(wrapper.vm, '$emit'); + + wrapper.vm.handleActionClick(1); + + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionSelect', 1); + }); + }); + }); + + describe('template', () => { + it('renders default action button', () => { + const defaultButton = wrapper.findAll('.btn').at(0); + + expect(defaultButton.text()).toBe(mockActions[0].title); + }); + + it('renders dropdown button', () => { + const dropdownButton = wrapper.findAll('.dropdown-toggle').at(0); + + expect(dropdownButton.isVisible()).toBe(true); + }); + + it('renders dropdown actions', () => { + const dropdownActions = wrapper.findAll('.dropdown-menu li button'); + + Array(dropdownActions.length) + .fill() + .forEach((_, index) => { + const actionContent = dropdownActions.at(index).find('.description'); + + expect(actionContent.find('strong').text()).toBe(mockActions[index].title); + expect(actionContent.find('p').text()).toBe(mockActions[index].description); + }); + }); + + it('renders divider between dropdown actions', () => { + const dropdownDivider = wrapper.find('.dropdown-menu .divider'); + + expect(dropdownDivider.isVisible()).toBe(true); + }); + }); +}); -- cgit v1.2.1 From c38ea7e8843660ae9993ad8f515866113bbcc4f5 Mon Sep 17 00:00:00 2001 From: Jiaan <3468028-jiaan@users.noreply.gitlab.com> Date: Thu, 6 Jun 2019 07:39:59 +0000 Subject: Resolve "Inline validation for user's name and username length" --- spec/features/users/signup_spec.rb | 44 +++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 957c3cfc583..1a9caf0ffbb 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -25,6 +25,13 @@ describe 'Signup' do expect(find('.username')).not_to have_css '.gl-field-error-outline' end + it 'does not show an error border if the username length is not longer than 255 characters' do + fill_in 'new_user_username', with: 'u' * 255 + wait_for_requests + + expect(find('.username')).not_to have_css '.gl-field-error-outline' + end + it 'shows an error border if the username already exists' do existing_user = create(:user) @@ -41,6 +48,20 @@ describe 'Signup' do expect(find('.username')).to have_css '.gl-field-error-outline' end + it 'shows an error border if the username is longer than 255 characters' do + fill_in 'new_user_username', with: 'u' * 256 + wait_for_requests + + expect(find('.username')).to have_css '.gl-field-error-outline' + end + + it 'shows an error message if the username is longer than 255 characters' do + fill_in 'new_user_username', with: 'u' * 256 + wait_for_requests + + expect(page).to have_content("Username is too long (maximum is 255 characters).") + end + it 'shows an error message on submit if the username contains special characters' do fill_in 'new_user_username', with: 'new$user!username' wait_for_requests @@ -67,14 +88,35 @@ describe 'Signup' do before do visit root_path click_link 'Register' - simulate_input('#new_user_name', 'Ehsan 🦋') + end + + it 'does not show an error border if the user\'s fullname length is not longer than 128 characters' do + fill_in 'new_user_name', with: 'u' * 128 + + expect(find('.name')).not_to have_css '.gl-field-error-outline' end it 'shows an error border if the user\'s fullname contains an emoji' do + simulate_input('#new_user_name', 'Ehsan 🦋') + + expect(find('.name')).to have_css '.gl-field-error-outline' + end + + it 'shows an error border if the user\'s fullname is longer than 128 characters' do + fill_in 'new_user_name', with: 'n' * 129 + expect(find('.name')).to have_css '.gl-field-error-outline' end + it 'shows an error message if the user\'s fullname is longer than 128 characters' do + fill_in 'new_user_name', with: 'n' * 129 + + expect(page).to have_content("Name is too long (maximum is 128 characters).") + end + it 'shows an error message if the username contains emojis' do + simulate_input('#new_user_name', 'Ehsan 🦋') + expect(page).to have_content("Invalid input, please avoid emojis") end end -- cgit v1.2.1 From 765f30b80baaed9ace1b5b07570e65e7a7b7ee35 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 6 Jun 2019 08:18:47 +0000 Subject: Quarantine this flaky test See https://gitlab.com/gitlab-org/gitlab-ce/issues/62760 --- spec/models/ci/pipeline_schedule_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 9a6471557ea..227870eb27f 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -128,7 +128,7 @@ describe Ci::PipelineSchedule do context 'when pipeline schedule runs every minute' do let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) } - it "updates next_run_at to the sidekiq worker's execution time" do + it "updates next_run_at to the sidekiq worker's execution time", :quarantine do expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at) end end -- cgit v1.2.1 From ad9ae16d8a44dd2523bd6e6109db9fe2da45d3a5 Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Thu, 30 May 2019 18:40:53 +1200 Subject: Add project level git depth setting Introduce default_git_depth in project's CI/CD settings and set it to 50. Use it if there is no GIT_DEPTH variable specified. Apply this default only to newly created projects and keep it nil for old ones in order to not break pipelines that rely on non-shallow clones. default_git_depth can be updated from CI/CD Settings in the UI, must be either nil or integer between 0 and 1000 (incl). Inherit default_git_depth from the origin project when forking projects. MR pipelines are run on a MR ref (refs/merge-requests/:iid/merge) and it contains unique commit (i.e. merge commit) which doesn't exist in the other branch/tags refs. We need to add it cause otherwise it may break pipelines for old projects that have already enabled Pipelines for merge results and have git depth 0. Document new default_git_depth project CI/CD setting --- .../projects/settings/ci_cd_controller_spec.rb | 15 ++++++ .../background_migration/delete_diff_files_spec.rb | 6 +++ .../populate_external_pipeline_source_spec.rb | 3 ++ .../migrations/remove_orphaned_label_links_spec.rb | 6 +++ spec/models/project_ci_cd_setting_spec.rb | 22 +++++++++ spec/presenters/ci/build_runner_presenter_spec.rb | 54 +++++++++++++--------- spec/requests/api/runner_spec.rb | 16 +++++-- spec/services/projects/fork_service_spec.rb | 8 ++++ 8 files changed, 104 insertions(+), 26 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index b91a4df40a5..117b9cf7915 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -200,6 +200,21 @@ describe Projects::Settings::CiCdController do expect(response).to redirect_to(namespace_project_settings_ci_cd_path) end end + + context 'when default_git_depth is not specified' do + let(:params) { { ci_cd_settings_attributes: { default_git_depth: 10 } } } + + before do + project.ci_cd_settings.update!(default_git_depth: nil) + end + + it 'set specified git depth' do + subject + + project.reload + expect(project.default_git_depth).to eq(10) + end + end end end end diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb index 27281333348..22ec09d44ac 100644 --- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb @@ -3,6 +3,12 @@ require 'spec_helper' # rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do describe '#perform' do + before do + # This migration was created before we introduced ProjectCiCdSetting#default_git_depth + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth?).and_return(true) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + end + context 'when diff files can be deleted' do let(:merge_request) { create(:merge_request, :merged) } let!(:merge_request_diff) do diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb index 3e009fed0f1..317745bcf83 100644 --- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb @@ -9,6 +9,9 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration before do # This migration was created before we introduced metadata configs stub_feature_flags(ci_build_metadata_config: false) + # This migration was created before we introduced ProjectCiCdSetting#default_git_depth + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth?).and_return(true) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) end let!(:internal_pipeline) { create(:ci_pipeline, source: :web) } diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb index 13b8919343e..4c8135cd08c 100644 --- a/spec/migrations/remove_orphaned_label_links_spec.rb +++ b/spec/migrations/remove_orphaned_label_links_spec.rb @@ -10,6 +10,12 @@ describe RemoveOrphanedLabelLinks, :migration do let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs let(:label) { create_label } + before do + # This migration was created before we introduced ProjectCiCdSetting#default_git_depth + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth?).and_return(true) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + end + context 'add foreign key on label_id' do let!(:label_link_with_label) { create_label_link(label_id: label.id) } let!(:label_link_without_label) { create_label_link(label_id: nil) } diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb index 4aa62028169..80f75f45e43 100644 --- a/spec/models/project_ci_cd_setting_spec.rb +++ b/spec/models/project_ci_cd_setting_spec.rb @@ -21,4 +21,26 @@ describe ProjectCiCdSetting do 2.times { described_class.available? } end end + + describe 'validations' do + it { is_expected.to validate_numericality_of(:default_git_depth).only_integer.is_greater_than_or_equal_to(0).is_less_than_or_equal_to(1000).allow_nil } + end + + describe '#default_git_depth' do + let(:default_value) { described_class::DEFAULT_GIT_DEPTH } + + it 'sets default value for new records' do + project = create(:project) + + expect(project.ci_cd_settings.default_git_depth).to eq(default_value) + end + + it 'does not set default value if present' do + project = build(:project) + project.build_ci_cd_settings(default_git_depth: 42) + project.save! + + expect(project.reload.ci_cd_settings.default_git_depth).to eq(42) + end + end end diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index 3430111ca9e..9ed8e3a4e0a 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -119,23 +119,23 @@ describe Ci::BuildRunnerPresenter do end describe '#git_depth' do - subject { presenter.git_depth } - let(:build) { create(:ci_build) } - it 'returns the correct git depth' do - is_expected.to eq(0) - end + subject(:git_depth) { presenter.git_depth } context 'when GIT_DEPTH variable is specified' do before do create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline) end - it 'returns the correct git depth' do - is_expected.to eq(1) + it 'returns its value' do + expect(git_depth).to eq(1) end end + + it 'defaults to git depth setting for the project' do + expect(git_depth).to eq(build.project.default_git_depth) + end end describe '#refspecs' do @@ -144,24 +144,24 @@ describe Ci::BuildRunnerPresenter do let(:build) { create(:ci_build) } it 'returns the correct refspecs' do - is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', - '+refs/heads/*:refs/remotes/origin/*') + is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") end - context 'when GIT_DEPTH variable is specified' do - before do - create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline) - end + context 'when ref is tag' do + let(:build) { create(:ci_build, :tag) } it 'returns the correct refspecs' do - is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") + is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}") end - context 'when ref is tag' do - let(:build) { create(:ci_build, :tag) } + context 'when GIT_DEPTH is zero' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline) + end it 'returns the correct refspecs' do - is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}") + is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', + '+refs/heads/*:refs/remotes/origin/*') end end end @@ -173,17 +173,27 @@ describe Ci::BuildRunnerPresenter do it 'returns the correct refspecs' do is_expected - .to contain_exactly('+refs/heads/*:refs/remotes/origin/*', - '+refs/tags/*:refs/tags/*', - '+refs/merge-requests/1/head:refs/merge-requests/1/head') + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') + end + + context 'when GIT_DEPTH is zero' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline) + end + + it 'returns the correct refspecs' do + is_expected + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head', + '+refs/heads/*:refs/remotes/origin/*', + '+refs/tags/*:refs/tags/*') + end end context 'when pipeline is legacy detached merge request pipeline' do let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } it 'returns the correct refspecs' do - is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', - '+refs/heads/*:refs/remotes/origin/*') + is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") end end end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 3202050ac20..038c958b5cc 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -444,8 +444,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do 'sha' => job.sha, 'before_sha' => job.before_sha, 'ref_type' => 'branch', - 'refspecs' => %w[+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*], - 'depth' => 0 } + 'refspecs' => ["+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"], + 'depth' => project.default_git_depth } end let(:expected_steps) do @@ -531,7 +531,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end end - context 'when GIT_DEPTH is not specified' do + context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do + before do + project.update!(default_git_depth: nil) + end + it 'specifies refspecs' do request_job @@ -587,7 +591,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end end - context 'when GIT_DEPTH is not specified' do + context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do + before do + project.update!(default_git_depth: nil) + end + it 'specifies refspecs' do request_job diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index ec3f1782e8f..e8a38287a63 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -145,6 +145,14 @@ describe Projects::ForkService do end end + context "CI/CD settings" do + it "inherits default_git_depth from the origin project" do + @from_project.update(default_git_depth: 42) + @to_project = fork_project(@from_project, @to_user) + expect(@to_project.default_git_depth).to eq(42) + end + end + context "when project has restricted visibility level" do context "and only one visibility level is restricted" do before do -- cgit v1.2.1 From 8af83bbfec20bb387436bbc46f6cfdac6aa05b71 Mon Sep 17 00:00:00 2001 From: Fabio Pitino Date: Thu, 6 Jun 2019 09:29:05 +0000 Subject: Always show "Pipelines must succeed" checkbox Before: the checkbox was hidden if builds were disabled. The checkbox should not be linked to the builds feature because they are 2 separate functionality. --- .../projects/settings/user_manages_merge_requests_settings_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb index 28d52f25f56..0739726f52c 100644 --- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb @@ -51,7 +51,7 @@ describe 'Projects > Settings > User manages merge request settings' do end it 'shows the Merge Requests settings that do not depend on Builds feature' do - expect(page).not_to have_content 'Pipelines must succeed' + expect(page).to have_content 'Pipelines must succeed' expect(page).to have_content 'All discussions must be resolved' within('.sharing-permissions-form') do -- cgit v1.2.1 From 8bea9eeddfa6e2e602f32c7130e39a8eb5f191ae Mon Sep 17 00:00:00 2001 From: Andrew Fontaine Date: Thu, 6 Jun 2019 09:36:17 +0000 Subject: Add a New Copy Button That Works in Modals This copy button manages a local instance of the Clipboard plugin specific to it, which means it is created/destroyed on the creation/destruction of the component. This allows it to work well in gitlab-ui modals, as the event listeners are bound on creation of the button. It also allows for bindings to the `container` option of the Clipboard plugin, which allows it to work within the focus trap set by bootstrap's modals. --- .../components/modal_copy_button_spec.js | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 spec/frontend/vue_shared/components/modal_copy_button_spec.js (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/modal_copy_button_spec.js b/spec/frontend/vue_shared/components/modal_copy_button_spec.js new file mode 100644 index 00000000000..f1943861523 --- /dev/null +++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js @@ -0,0 +1,40 @@ +import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import modalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; + +describe('modal copy button', () => { + const Component = Vue.extend(modalCopyButton); + let wrapper; + + afterEach(() => { + wrapper.destroy(); + }); + + beforeEach(() => { + wrapper = shallowMount(Component, { + propsData: { + text: 'copy me', + title: 'Copy this value into Clipboard!', + }, + }); + }); + + describe('clipboard', () => { + it('should fire a `success` event on click', () => { + document.execCommand = jest.fn(() => true); + window.getSelection = jest.fn(() => ({ + toString: jest.fn(() => 'test'), + removeAllRanges: jest.fn(), + })); + wrapper.trigger('click'); + expect(wrapper.emitted().success).not.toBeEmpty(); + expect(document.execCommand).toHaveBeenCalledWith('copy'); + }); + it("should propagate the clipboard error event if execCommand doesn't work", () => { + document.execCommand = jest.fn(() => false); + wrapper.trigger('click'); + expect(wrapper.emitted().error).not.toBeEmpty(); + expect(document.execCommand).toHaveBeenCalledWith('copy'); + }); + }); +}); -- cgit v1.2.1 From 982463e944e0af140dc31c8e64a995b476294e82 Mon Sep 17 00:00:00 2001 From: Marc Schwede Date: Thu, 6 Jun 2019 10:05:42 +0000 Subject: Change "Report abuse to GitLab" to more generic wording. --- spec/javascripts/notes/components/note_actions_spec.js | 2 +- spec/support/features/reportable_note_shared_examples.rb | 4 ++-- .../views/projects/notes/_more_actions_dropdown.html.haml_spec.rb | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index 2159e4ddf16..1f2c07385a7 100644 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -66,7 +66,7 @@ describe('noteActions', () => { expect(wrapper.find('.js-note-edit').exists()).toBe(true); }); - it('should be possible to report abuse to GitLab', () => { + it('should be possible to report abuse to admin', () => { expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true); }); diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb index 89dfbf931d2..5d5a0a7b5d2 100644 --- a/spec/support/features/reportable_note_shared_examples.rb +++ b/spec/support/features/reportable_note_shared_examples.rb @@ -20,7 +20,7 @@ shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - expect(dropdown).to have_link('Report abuse to GitLab', href: abuse_report_path) + expect(dropdown).to have_link('Report abuse to admin', href: abuse_report_path) if type == 'issue' || type == 'merge_request' expect(dropdown).to have_button('Delete comment') @@ -33,7 +33,7 @@ shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - dropdown.click_link('Report abuse to GitLab') + dropdown.click_link('Report abuse to admin') expect(find('#user_name')['value']).to match(note.author.username) expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note)) diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb index 8a9ab02eaca..ae47f364296 100644 --- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb +++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb @@ -12,10 +12,10 @@ describe 'projects/notes/_more_actions_dropdown' do assign(:project, project) end - it 'shows Report abuse to GitLab button if not editable and not current users comment' do + it 'shows Report abuse to admin button if not editable and not current users comment' do render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: false, note: note - expect(rendered).to have_link('Report abuse to GitLab') + expect(rendered).to have_link('Report abuse to admin') end it 'does not show the More actions button if not editable and current users comment' do @@ -24,10 +24,10 @@ describe 'projects/notes/_more_actions_dropdown' do expect(rendered).not_to have_selector('.dropdown.more-actions') end - it 'shows Report abuse to GitLab and Delete buttons if editable and not current users comment' do + it 'shows Report abuse to admin and Delete buttons if editable and not current users comment' do render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: true, note: note - expect(rendered).to have_link('Report abuse to GitLab') + expect(rendered).to have_link('Report abuse to admin') expect(rendered).to have_link('Delete comment') end -- cgit v1.2.1 From 076e16cb1c6533275d46a4542054947b2c3b2042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 19 Apr 2019 15:44:05 +0200 Subject: Ensure that template is used once This adds additional test that make sure that each template name is used exactly once across all folders exposed --- spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb index fe46c67a920..5f0a7e925ca 100644 --- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb +++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb @@ -15,6 +15,13 @@ describe Gitlab::Template::GitlabCiYmlTemplate do expect(all).to include('Docker') expect(all).to include('Ruby') end + + it 'ensure that the template name is used exactly once' do + all = subject.all.group_by(&:name) + duplicates = all.select { |_, templates| templates.length > 1 } + + expect(duplicates).to be_empty + end end describe '.find' do -- cgit v1.2.1 From fab72e2ad992475a7bdfb12e7ca4eba626e4acba Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Thu, 6 Jun 2019 10:37:02 +0000 Subject: Add doc links for confidential and locked issues With this commit, we add docs link for confidential and locked issues to note compose and edit forms --- .../vue_shared/components/issue/issue_warning_spec.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js index 4a8de5fc4f1..63880b85625 100644 --- a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js +++ b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js @@ -15,31 +15,37 @@ function formatWarning(string) { describe('Issue Warning Component', () => { describe('isLocked', () => { it('should render locked issue warning information', () => { - const vm = mountComponent(IssueWarning, { + const props = { isLocked: true, - }); + lockedIssueDocsPath: 'docs/issues/locked', + }; + const vm = mountComponent(IssueWarning, props); expect( vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), ).toMatch(/lock$/); expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - 'This issue is locked. Only project members can comment.', + 'This issue is locked. Only project members can comment. Learn more', ); + expect(vm.$el.querySelector('a').href).toContain(props.lockedIssueDocsPath); }); }); describe('isConfidential', () => { it('should render confidential issue warning information', () => { - const vm = mountComponent(IssueWarning, { + const props = { isConfidential: true, - }); + confidentialIssueDocsPath: '/docs/issues/confidential', + }; + const vm = mountComponent(IssueWarning, props); expect( vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), ).toMatch(/eye-slash$/); expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - 'This is a confidential issue. Your comment will not be visible to the public.', + 'This is a confidential issue. People without permission will never get a notification. Learn more', ); + expect(vm.$el.querySelector('a').href).toContain(props.confidentialIssueDocsPath); }); }); -- cgit v1.2.1 From 52673a916df0f759262afa1aaa230f7d62efd554 Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Thu, 6 Jun 2019 23:08:01 +1200 Subject: Forks get default_git_depth 0 if the origin is nil If the origin project has no default_git_depth set (i.e. nil) set the fork's default_git_depth to 0 --- spec/models/project_ci_cd_setting_spec.rb | 4 ++-- spec/services/projects/fork_service_spec.rb | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'spec') diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb index 80f75f45e43..c5a4c242b70 100644 --- a/spec/models/project_ci_cd_setting_spec.rb +++ b/spec/models/project_ci_cd_setting_spec.rb @@ -37,10 +37,10 @@ describe ProjectCiCdSetting do it 'does not set default value if present' do project = build(:project) - project.build_ci_cd_settings(default_git_depth: 42) + project.build_ci_cd_settings(default_git_depth: 0) project.save! - expect(project.reload.ci_cd_settings.default_git_depth).to eq(42) + expect(project.reload.ci_cd_settings.default_git_depth).to eq(0) end end end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index e8a38287a63..3211a6e1310 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -146,10 +146,26 @@ describe Projects::ForkService do end context "CI/CD settings" do - it "inherits default_git_depth from the origin project" do - @from_project.update(default_git_depth: 42) - @to_project = fork_project(@from_project, @to_user) - expect(@to_project.default_git_depth).to eq(42) + let(:to_project) { fork_project(@from_project, @to_user) } + + context "when origin has git depth specified" do + before do + @from_project.update(default_git_depth: 42) + end + + it "inherits default_git_depth from the origin project" do + expect(to_project.default_git_depth).to eq(42) + end + end + + context "when origin does not define git depth" do + before do + @from_project.update!(default_git_depth: nil) + end + + it "the fork has git depth set to 0" do + expect(to_project.default_git_depth).to eq(0) + end end end -- cgit v1.2.1 From 280ac894d203a040c49cc191038fa4c51eb4e174 Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Thu, 6 Jun 2019 23:20:26 +1200 Subject: Reformat ProjectCiCdSetting validations specs --- spec/models/project_ci_cd_setting_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'spec') diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb index c5a4c242b70..f596cee81dc 100644 --- a/spec/models/project_ci_cd_setting_spec.rb +++ b/spec/models/project_ci_cd_setting_spec.rb @@ -23,7 +23,13 @@ describe ProjectCiCdSetting do end describe 'validations' do - it { is_expected.to validate_numericality_of(:default_git_depth).only_integer.is_greater_than_or_equal_to(0).is_less_than_or_equal_to(1000).allow_nil } + it 'validates default_git_depth is between 0 and 1000 or nil' do + expect(subject).to validate_numericality_of(:default_git_depth) + .only_integer + .is_greater_than_or_equal_to(0) + .is_less_than_or_equal_to(1000) + .allow_nil + end end describe '#default_git_depth' do -- cgit v1.2.1 From c0a812ecb74f1fd519d096d8be36f0a2436adc94 Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Fri, 7 Jun 2019 00:04:56 +1200 Subject: Fix migration specs using factories We need to stub default_git_depth and default_git_depth= because some old migrations specs try to create a record using schema before that column was introduced. --- spec/lib/gitlab/background_migration/delete_diff_files_spec.rb | 2 +- .../background_migration/populate_external_pipeline_source_spec.rb | 2 +- spec/migrations/remove_orphaned_label_links_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb index 22ec09d44ac..0a5b99d27e7 100644 --- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch describe '#perform' do before do # This migration was created before we introduced ProjectCiCdSetting#default_git_depth - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth?).and_return(true) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) end diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb index 317745bcf83..c6bc3db88a3 100644 --- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb @@ -10,8 +10,8 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration # This migration was created before we introduced metadata configs stub_feature_flags(ci_build_metadata_config: false) # This migration was created before we introduced ProjectCiCdSetting#default_git_depth - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth?).and_return(true) allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) end let!(:internal_pipeline) { create(:ci_pipeline, source: :web) } diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb index 4c8135cd08c..e8c44c141c3 100644 --- a/spec/migrations/remove_orphaned_label_links_spec.rb +++ b/spec/migrations/remove_orphaned_label_links_spec.rb @@ -12,8 +12,8 @@ describe RemoveOrphanedLabelLinks, :migration do before do # This migration was created before we introduced ProjectCiCdSetting#default_git_depth - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth?).and_return(true) allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) end context 'add foreign key on label_id' do -- cgit v1.2.1 From a1088c7a3143b4b65991ce70100baf039ecb5eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 6 Jun 2019 14:15:26 +0200 Subject: Include the port in the URLs of the API Link headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/lib/api/helpers/pagination_spec.rb | 34 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) (limited to 'spec') diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index 6e215ea1561..c788da55cd2 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -2,8 +2,12 @@ require 'spec_helper' describe API::Helpers::Pagination do let(:resource) { Project.all } - let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:8080/api/v4/projects" } - let(:canonical_api_projects_url) { "#{Gitlab.config.gitlab.url}/api/v4/projects" } + let(:custom_port) { 8080 } + let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" } + + before do + stub_config_setting(port: custom_port) + end subject do Class.new.include(described_class).new @@ -48,7 +52,7 @@ describe API::Helpers::Pagination do it 'adds appropriate headers' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}") + expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') @@ -71,7 +75,7 @@ describe API::Helpers::Pagination do it 'adds appropriate headers' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}") + expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') @@ -171,7 +175,7 @@ describe API::Helpers::Pagination do it 'returns the right link to the next page' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}") + expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') end @@ -224,9 +228,9 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="prev"') end @@ -290,8 +294,8 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="last"') expect(val).not_to include('rel="prev"') end @@ -318,9 +322,9 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '1') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) expect(val).not_to include('rel="next"') end @@ -367,8 +371,8 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) expect(val).not_to include('rel="prev"') expect(val).not_to include('rel="next"') expect(val).not_to include('page=0') -- cgit v1.2.1 From 74399a90989e5caade1de1833a7f65cfbc070bcd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 24 May 2019 02:07:59 +0800 Subject: Show Test Automation Engineer for spec/feature as reviewers. --- spec/features/admin/admin_appearance_spec.rb | 2 + spec/features/atom/dashboard_issues_spec.rb | 2 + spec/features/groups/merge_requests_spec.rb | 2 + .../conversational_development_index_spec.rb | 2 + spec/features/issues_spec.rb | 2 + .../user_merges_merge_request_spec.rb | 2 + spec/features/project_variables_spec.rb | 2 + spec/features/projects/clusters_spec.rb | 2 + spec/features/projects/pipelines/pipeline_spec.rb | 2 + spec/features/projects_spec.rb | 2 + spec/features/security/profile_access_spec.rb | 2 + spec/lib/gitlab/danger/teammate_spec.rb | 47 +++++++++++++++++----- 12 files changed, 59 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 83cd686818c..f6c498f7a4c 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Admin Appearance' do diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index dfa1c92ea49..d523e2992db 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe "Dashboard Issues Feed" do diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index e1bc4eca619..59230d6891a 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Group merge requests page' do diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/conversational_development_index_spec.rb index d8be554d734..713cd944f8c 100644 --- a/spec/features/instance_statistics/conversational_development_index_spec.rb +++ b/spec/features/instance_statistics/conversational_development_index_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Conversational Development Index' do diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index bc0ec58bd24..5ee9425c491 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Issues' do diff --git a/spec/features/merge_request/user_merges_merge_request_spec.rb b/spec/features/merge_request/user_merges_merge_request_spec.rb index 6539e6e9208..da15a4bda4b 100644 --- a/spec/features/merge_request/user_merges_merge_request_spec.rb +++ b/spec/features/merge_request/user_merges_merge_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe "User merges a merge request", :js do diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 76abc640077..95685a3c7ff 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Project variables', :js do diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index a85e7333ba8..ce382c19fc1 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Clusters', :js do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 25b3ac00604..1de153db41c 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Pipeline', :js do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 27f6ed56283..b5112758475 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Project' do diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index a198e65046f..044a47567be 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe "Profile access" do diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index 753c74ff814..6a6cf1429c8 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -5,39 +5,66 @@ require 'fast_spec_helper' require 'gitlab/danger/teammate' describe Gitlab::Danger::Teammate do - subject { described_class.new({ 'projects' => projects }) } + subject { described_class.new(options) } + let(:options) { { 'projects' => projects, 'role' => role } } let(:projects) { { project => capabilities } } + let(:role) { 'Engineer, Manage' } + let(:labels) { [] } let(:project) { double } - describe 'multiple roles project project' do - let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer database'] } + context 'when having multiple capabilities' do + let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer qa'] } it '#reviewer? supports multiple roles per project' do - expect(subject.reviewer?(project, :backend)).to be_truthy + expect(subject.reviewer?(project, :backend, labels)).to be_truthy end it '#traintainer? supports multiple roles per project' do - expect(subject.traintainer?(project, :database)).to be_truthy + expect(subject.traintainer?(project, :qa, labels)).to be_truthy end it '#maintainer? supports multiple roles per project' do - expect(subject.maintainer?(project, :frontend)).to be_truthy + expect(subject.maintainer?(project, :frontend, labels)).to be_truthy + end + + context 'when labels contain Create and the category is test' do + let(:labels) { ['Create'] } + + context 'when role is Test Automation Engineer, Create' do + let(:role) { 'Test Automation Engineer, Create' } + + it '#reviewer? returns true' do + expect(subject.reviewer?(project, :test, labels)).to be_truthy + end + + it '#maintainer? returns false' do + expect(subject.maintainer?(project, :test, labels)).to be_falsey + end + end + + context 'when role is Test Automation Engineer, Manage' do + let(:role) { 'Test Automation Engineer, Manage' } + + it '#reviewer? returns false' do + expect(subject.reviewer?(project, :test, labels)).to be_falsey + end + end end end - describe 'one role project project' do + context 'when having single capability' do let(:capabilities) { 'reviewer backend' } it '#reviewer? supports one role per project' do - expect(subject.reviewer?(project, :backend)).to be_truthy + expect(subject.reviewer?(project, :backend, labels)).to be_truthy end it '#traintainer? supports one role per project' do - expect(subject.traintainer?(project, :database)).to be_falsey + expect(subject.traintainer?(project, :database, labels)).to be_falsey end it '#maintainer? supports one role per project' do - expect(subject.maintainer?(project, :frontend)).to be_falsey + expect(subject.maintainer?(project, :frontend, labels)).to be_falsey end end end -- cgit v1.2.1 From 3aab750d076774b2adef9493171f29ea3b4523d6 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Thu, 6 Jun 2019 13:20:15 +0000 Subject: Add certificate valid time to pages domain table Save certificate validity time for pages domains on save Fill validity time for existing pages domains in background migration --- .../enqueue_verify_pages_domain_workers_spec.rb | 6 ++- ...alid_time_for_pages_domain_certificates_spec.rb | 46 ++++++++++++++++++++++ spec/models/pages_domain_spec.rb | 11 ++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb (limited to 'spec') diff --git a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb index afcaefa0591..abf39317188 100644 --- a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb +++ b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb @@ -8,9 +8,13 @@ describe EnqueueVerifyPagesDomainWorkers, :sidekiq, :migration do end end + let(:domains_table) { table(:pages_domains) } + describe '#up' do it 'enqueues a verification worker for every domain' do - domains = 1.upto(3).map { |i| PagesDomain.create!(domain: "my#{i}.domain.com") } + domains = Array.new(3) do |i| + domains_table.create!(domain: "my#{i}.domain.com", verification_code: "123#{i}") + end expect { migrate! }.to change(PagesDomainVerificationWorker.jobs, :size).by(3) diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb new file mode 100644 index 00000000000..54f3e264df0 --- /dev/null +++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb') + +describe ScheduleFillValidTimeForPagesDomainCertificates, :migration, :sidekiq do + let(:migration_class) { described_class::MIGRATION } + let(:migration_name) { migration_class.to_s.demodulize } + + let(:domains_table) { table(:pages_domains) } + + let(:certificate) do + File.read('spec/fixtures/passphrase_x509_certificate.crt') + end + + before do + domains_table.create!(domain: "domain1.example.com", verification_code: "123") + domains_table.create!(domain: "domain2.example.com", verification_code: "123", certificate: '') + domains_table.create!(domain: "domain3.example.com", verification_code: "123", certificate: certificate) + domains_table.create!(domain: "domain4.example.com", verification_code: "123", certificate: certificate) + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + first_id = domains_table.find_by_domain("domain3.example.com").id + last_id = domains_table.find_by_domain("domain4.example.com").id + + expect(migration_name).to be_scheduled_delayed_migration(5.minutes, first_id, last_id) + expect(BackgroundMigrationWorker.jobs.size).to eq(1) + end + end + end + + it 'sets certificate valid_not_before/not_after' do + perform_enqueued_jobs do + migrate! + + domain = domains_table.find_by_domain("domain3.example.com") + expect(domain.certificate_valid_not_before) + .to eq(Time.parse("2018-03-23 14:02:08 UTC")) + expect(domain.certificate_valid_not_after) + .to eq(Time.parse("2019-03-23 14:02:08 UTC")) + end + end +end diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index ec4d4517f82..fdc81359d34 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -81,6 +81,17 @@ describe PagesDomain do end end + describe 'when certificate is specified' do + let(:domain) { build(:pages_domain) } + + it 'saves validity time' do + domain.save + + expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC")) + expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC")) + end + end + describe 'validate certificate' do subject { domain } -- cgit v1.2.1 From 3335918bff211f543ec0f304fbfaf8278daa91d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Wed, 23 Jan 2019 19:44:12 +0100 Subject: Migrate legacy uploads to the project location Uploads coming from AttachmentUploader need to be moved to the currently supported location (FileUploader) --- spec/factories/uploads.rb | 5 +- .../migrate_legacy_uploads_spec.rb | 237 +++++++++++++++++++++ 2 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb (limited to 'spec') diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index 426abdc2a6c..52f6962f16b 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -54,10 +54,7 @@ FactoryBot.define do end trait :attachment_upload do - transient do - mount_point :attachment - end - + mount_point :attachment model { build(:note) } uploader "AttachmentUploader" end diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb new file mode 100644 index 00000000000..802c8fb8c97 --- /dev/null +++ b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb @@ -0,0 +1,237 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::BackgroundMigration::MigrateLegacyUploads, :migration, schema: 20190103140724 do + let(:test_dir) { FileUploader.options['storage_path'] } + + # rubocop: disable RSpec/FactoriesInMigrationSpecs + let!(:namespace) { create(:namespace) } + let!(:project) { create(:project, :legacy_storage, namespace: namespace) } + let!(:issue) { create(:issue, project: project) } + + let!(:note1) { create(:note, note: 'some note text awesome', project: project, noteable: issue) } + let!(:note2) { create(:note, note: 'some note', project: project, noteable: issue) } + + let!(:hashed_project) { create(:project, namespace: namespace) } + let!(:issue_hashed_project) { create(:issue, project: hashed_project) } + let!(:note_hashed_project) { create(:note, note: 'some note', project: hashed_project, attachment: 'text.pdf', noteable: issue_hashed_project) } + + let!(:standard_upload) do + create(:upload, + path: "secretabcde/image.png", + model_id: create(:project).id, model_type: 'Project', uploader: 'FileUploader') + end + + def create_remote_upload(model, filename) + create(:upload, :attachment_upload, + path: "note/attachment/#{model.id}/#{filename}", secret: nil, + store: ObjectStorage::Store::REMOTE, model: model) + end + + def create_upload(model, filename, with_file = true) + params = { + path: "uploads/-/system/note/attachment/#{model.id}/#{filename}", + model: model, + store: ObjectStorage::Store::LOCAL + } + + upload = if with_file + create(:upload, :with_file, :attachment_upload, params) + else + create(:upload, :attachment_upload, params) + end + + model.update(attachment: upload.build_uploader) + model.attachment.upload + end + + let(:start_id) { 1 } + let(:end_id) { 10000 } + + def new_upload_legacy + Upload.find_by(model_id: project.id, model_type: 'Project') + end + + def new_upload_hashed + Upload.find_by(model_id: hashed_project.id, model_type: 'Project') + end + + shared_examples 'migrates files correctly' do + before do + described_class.new.perform(start_id, end_id) + end + + it 'removes all the legacy upload records' do + expect(Upload.where(uploader: 'AttachmentUploader')).to be_empty + + expect(standard_upload.reload).to eq(standard_upload) + end + + it 'creates new upload records correctly' do + expect(new_upload_legacy.secret).not_to be_nil + expect(new_upload_legacy.path).to end_with("#{new_upload_legacy.secret}/image.png") + expect(new_upload_legacy.model_id).to eq(project.id) + expect(new_upload_legacy.model_type).to eq('Project') + expect(new_upload_legacy.uploader).to eq('FileUploader') + + expect(new_upload_hashed.secret).not_to be_nil + expect(new_upload_hashed.path).to end_with("#{new_upload_hashed.secret}/text.pdf") + expect(new_upload_hashed.model_id).to eq(hashed_project.id) + expect(new_upload_hashed.model_type).to eq('Project') + expect(new_upload_hashed.uploader).to eq('FileUploader') + end + + it 'updates the legacy upload notes so that they include the file references in the markdown' do + expected_path = File.join('/uploads', new_upload_legacy.secret, 'image.png') + expected_markdown = "some note text awesome \n ![image](#{expected_path})" + expect(note1.reload.note).to eq(expected_markdown) + + expected_path = File.join('/uploads', new_upload_hashed.secret, 'text.pdf') + expected_markdown = "some note \n [text.pdf](#{expected_path})" + expect(note_hashed_project.reload.note).to eq(expected_markdown) + end + + it 'removed the attachments from the note model' do + expect(note1.reload.attachment.file).to be_nil + expect(note2.reload.attachment.file).to be_nil + expect(note_hashed_project.reload.attachment.file).to be_nil + end + end + + context 'when legacy uploads are stored in local storage' do + let!(:legacy_upload1) { create_upload(note1, 'image.png') } + let!(:legacy_upload_not_found) { create_upload(note2, 'image.png', false) } + let!(:legacy_upload_hashed) { create_upload(note_hashed_project, 'text.pdf', with_file: true) } + + shared_examples 'removes legacy local files' do + it 'removes all the legacy upload records' do + expect(File.exist?(legacy_upload1.absolute_path)).to be_truthy + expect(File.exist?(legacy_upload_hashed.absolute_path)).to be_truthy + + described_class.new.perform(start_id, end_id) + + expect(File.exist?(legacy_upload1.absolute_path)).to be_falsey + expect(File.exist?(legacy_upload_hashed.absolute_path)).to be_falsey + end + end + + context 'when object storage is disabled for FileUploader' do + it_behaves_like 'migrates files correctly' + it_behaves_like 'removes legacy local files' + + it 'moves legacy uploads to the correct location' do + described_class.new.perform(start_id, end_id) + + expected_path1 = File.join(test_dir, 'uploads', namespace.path, project.path, new_upload_legacy.secret, 'image.png') + expected_path2 = File.join(test_dir, 'uploads', hashed_project.disk_path, new_upload_hashed.secret, 'text.pdf') + + expect(File.exist?(expected_path1)).to be_truthy + expect(File.exist?(expected_path2)).to be_truthy + end + + context 'when the upload move fails' do + it 'does not remove old uploads' do + expect(FileUploader).to receive(:copy_to).twice.and_raise('failed') + + described_class.new.perform(start_id, end_id) + + expect(legacy_upload1.reload).to eq(legacy_upload1) + expect(legacy_upload_hashed.reload).to eq(legacy_upload_hashed) + expect(standard_upload.reload).to eq(standard_upload) + end + end + end + + context 'when object storage is enabled for FileUploader' do + before do + stub_uploads_object_storage(FileUploader) + end + + it_behaves_like 'migrates files correctly' + it_behaves_like 'removes legacy local files' + + # The process of migrating to object storage is a manual one, + # so it would go against expectations to automatically migrate these files + # to object storage during this migration. + # After this migration, these files should be able to successfully migrate to object storage. + it 'stores files locally' do + described_class.new.perform(start_id, end_id) + + expected_path1 = File.join(test_dir, 'uploads', namespace.path, project.path, new_upload_legacy.secret, 'image.png') + expected_path2 = File.join(test_dir, 'uploads', hashed_project.disk_path, new_upload_hashed.secret, 'text.pdf') + + expect(File.exist?(expected_path1)).to be_truthy + expect(File.exist?(expected_path2)).to be_truthy + end + end + + context 'with legacy_diff_note upload' do + let!(:merge_request) { create(:merge_request, source_project: project) } + let!(:legacy_diff_note) { create(:legacy_diff_note_on_merge_request, note: 'some note', project: project, noteable: merge_request) } + let!(:legacy_upload_diff_note) do + create(:upload, :with_file, :attachment_upload, + path: "uploads/-/system/note/attachment/#{legacy_diff_note.id}/some_legacy.pdf", model: legacy_diff_note) + end + + before do + described_class.new.perform(start_id, end_id) + end + + it 'does not remove legacy diff note file' do + expect(File.exist?(legacy_upload_diff_note.absolute_path)).to be_truthy + end + + it 'removes all the legacy upload records except for the one with legacy_diff_note' do + expect(Upload.where(uploader: 'AttachmentUploader')).to eq([legacy_upload_diff_note]) + end + + it 'adds link to the troubleshooting documentation to the note' do + help_doc_link = 'https://docs.gitlab.com/ee/administration/troubleshooting/migrations.html#legacy-upload-migration' + + expect(legacy_diff_note.reload.note).to include(help_doc_link) + end + end + end + + context 'when legacy uploads are stored in object storage' do + let!(:legacy_upload1) { create_remote_upload(note1, 'image.png') } + let!(:legacy_upload_not_found) { create_remote_upload(note2, 'non-existing.pdf') } + let!(:legacy_upload_hashed) { create_remote_upload(note_hashed_project, 'text.pdf') } + let(:remote_files) do + [ + { key: "#{legacy_upload1.path}" }, + { key: "#{legacy_upload_hashed.path}" } + ] + end + let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) } + let(:bucket) { connection.directories.create(key: 'uploads') } + + def create_remote_files + remote_files.each { |file| bucket.files.create(file) } + end + + before do + stub_uploads_object_storage(FileUploader) + create_remote_files + end + + it_behaves_like 'migrates files correctly' + + it 'moves legacy uploads to the correct remote location' do + described_class.new.perform(start_id, end_id) + + connection = ::Fog::Storage.new(FileUploader.object_store_credentials) + expect(connection.get_object('uploads', new_upload_legacy.path)[:status]).to eq(200) + expect(connection.get_object('uploads', new_upload_hashed.path)[:status]).to eq(200) + end + + it 'removes all the legacy upload records' do + described_class.new.perform(start_id, end_id) + + remote_files.each do |remote_file| + expect(bucket.files.get(remote_file[:key])).to be_nil + end + end + end + # rubocop: enable RSpec/FactoriesInMigrationSpecs +end -- cgit v1.2.1 From 10dcfac1f17cffbbc542d47bdf27b682ab453dd2 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Jun 2019 13:57:50 +0000 Subject: Revert "Merge branch 'pages-domain_ssl-valid-period' into 'master'" This reverts merge request !28743 --- .../enqueue_verify_pages_domain_workers_spec.rb | 6 +-- ...alid_time_for_pages_domain_certificates_spec.rb | 46 ---------------------- spec/models/pages_domain_spec.rb | 11 ------ 3 files changed, 1 insertion(+), 62 deletions(-) delete mode 100644 spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb (limited to 'spec') diff --git a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb index abf39317188..afcaefa0591 100644 --- a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb +++ b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb @@ -8,13 +8,9 @@ describe EnqueueVerifyPagesDomainWorkers, :sidekiq, :migration do end end - let(:domains_table) { table(:pages_domains) } - describe '#up' do it 'enqueues a verification worker for every domain' do - domains = Array.new(3) do |i| - domains_table.create!(domain: "my#{i}.domain.com", verification_code: "123#{i}") - end + domains = 1.upto(3).map { |i| PagesDomain.create!(domain: "my#{i}.domain.com") } expect { migrate! }.to change(PagesDomainVerificationWorker.jobs, :size).by(3) diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb deleted file mode 100644 index 54f3e264df0..00000000000 --- a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb') - -describe ScheduleFillValidTimeForPagesDomainCertificates, :migration, :sidekiq do - let(:migration_class) { described_class::MIGRATION } - let(:migration_name) { migration_class.to_s.demodulize } - - let(:domains_table) { table(:pages_domains) } - - let(:certificate) do - File.read('spec/fixtures/passphrase_x509_certificate.crt') - end - - before do - domains_table.create!(domain: "domain1.example.com", verification_code: "123") - domains_table.create!(domain: "domain2.example.com", verification_code: "123", certificate: '') - domains_table.create!(domain: "domain3.example.com", verification_code: "123", certificate: certificate) - domains_table.create!(domain: "domain4.example.com", verification_code: "123", certificate: certificate) - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - first_id = domains_table.find_by_domain("domain3.example.com").id - last_id = domains_table.find_by_domain("domain4.example.com").id - - expect(migration_name).to be_scheduled_delayed_migration(5.minutes, first_id, last_id) - expect(BackgroundMigrationWorker.jobs.size).to eq(1) - end - end - end - - it 'sets certificate valid_not_before/not_after' do - perform_enqueued_jobs do - migrate! - - domain = domains_table.find_by_domain("domain3.example.com") - expect(domain.certificate_valid_not_before) - .to eq(Time.parse("2018-03-23 14:02:08 UTC")) - expect(domain.certificate_valid_not_after) - .to eq(Time.parse("2019-03-23 14:02:08 UTC")) - end - end -end diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index fdc81359d34..ec4d4517f82 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -81,17 +81,6 @@ describe PagesDomain do end end - describe 'when certificate is specified' do - let(:domain) { build(:pages_domain) } - - it 'saves validity time' do - domain.save - - expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC")) - expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC")) - end - end - describe 'validate certificate' do subject { domain } -- cgit v1.2.1 From 940c812d5e3788d86603a87723198842af86efaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Alc=C3=A1ntara?= Date: Thu, 6 Jun 2019 14:15:39 +0000 Subject: Replace upgrade with update Use update term instead of update in the clusters managed app implementation in order to make the implementation and UI language more consistent. --- spec/frontend/clusters/clusters_bundle_spec.js | 33 +++++++++- .../clusters/components/application_row_spec.js | 76 +++++++++++----------- .../services/application_state_machine_spec.js | 2 +- .../clusters/stores/clusters_store_spec.js | 3 +- 4 files changed, 71 insertions(+), 43 deletions(-) (limited to 'spec') diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index 66b22fa2681..6de06a9e2d5 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -1,5 +1,10 @@ import Clusters from '~/clusters/clusters_bundle'; -import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, APPLICATIONS } from '~/clusters/constants'; +import { + APPLICATION_STATUS, + INGRESS_DOMAIN_SUFFIX, + APPLICATIONS, + RUNNER, +} from '~/clusters/constants'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import { loadHTMLFixture } from 'helpers/fixtures'; @@ -353,4 +358,30 @@ describe('Clusters', () => { }); }); }); + + describe('updateApplication', () => { + const params = { version: '1.0.0' }; + let storeUpdateApplication; + let installApplication; + + beforeEach(() => { + storeUpdateApplication = jest.spyOn(cluster.store, 'updateApplication'); + installApplication = jest.spyOn(cluster.service, 'installApplication'); + + cluster.updateApplication({ id: RUNNER, params }); + }); + + afterEach(() => { + storeUpdateApplication.mockRestore(); + installApplication.mockRestore(); + }); + + it('calls store updateApplication method', () => { + expect(storeUpdateApplication).toHaveBeenCalledWith(RUNNER); + }); + + it('sends installApplication request', () => { + expect(installApplication).toHaveBeenCalledWith(RUNNER, params); + }); + }); }); diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js index 7c781b72355..9f127ccb690 100644 --- a/spec/frontend/clusters/components/application_row_spec.js +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -245,26 +245,26 @@ describe('Application Row', () => { }); }); - describe('Upgrade button', () => { + describe('Update button', () => { it('has indeterminate state on page load', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: null, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).toBe(null); + expect(updateBtn).toBe(null); }); - it('has enabled "Upgrade" when "upgradeAvailable" is true', () => { + it('has enabled "Update" when "updateAvailable" is true', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - upgradeAvailable: true, + updateAvailable: true, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).not.toBe(null); - expect(upgradeBtn.innerHTML).toContain('Upgrade'); + expect(updateBtn).not.toBe(null); + expect(updateBtn.innerHTML).toContain('Update'); }); it('has enabled "Retry update" when update process fails', () => { @@ -273,10 +273,10 @@ describe('Application Row', () => { status: APPLICATION_STATUS.INSTALLED, updateFailed: true, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).not.toBe(null); - expect(upgradeBtn.innerHTML).toContain('Retry update'); + expect(updateBtn).not.toBe(null); + expect(updateBtn.innerHTML).toContain('Retry update'); }); it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => { @@ -284,53 +284,51 @@ describe('Application Row', () => { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATING, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).not.toBe(null); - expect(vm.isUpgrading).toBe(true); - expect(upgradeBtn.innerHTML).toContain('Updating'); + expect(updateBtn).not.toBe(null); + expect(vm.isUpdating).toBe(true); + expect(updateBtn.innerHTML).toContain('Updating'); }); - it('clicking upgrade button emits event', () => { + it('clicking update button emits event', () => { jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.INSTALLED, - upgradeAvailable: true, + updateAvailable: true, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - upgradeBtn.click(); + updateBtn.click(); - expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', { + expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', { id: DEFAULT_APPLICATION_STATE.id, params: {}, }); }); - it('clicking disabled upgrade button emits nothing', () => { + it('clicking disabled update button emits nothing', () => { jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATING, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - upgradeBtn.click(); + updateBtn.click(); expect(eventHub.$emit).not.toHaveBeenCalled(); }); - it('displays an error message if application upgrade failed', () => { + it('displays an error message if application update failed', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, title: 'GitLab Runner', status: APPLICATION_STATUS.INSTALLED, updateFailed: true, }); - const failureMessage = vm.$el.querySelector( - '.js-cluster-application-upgrade-failure-message', - ); + const failureMessage = vm.$el.querySelector('.js-cluster-application-update-details'); expect(failureMessage).not.toBe(null); expect(failureMessage.innerHTML).toContain( @@ -338,7 +336,7 @@ describe('Application Row', () => { ); }); - it('displays a success toast message if application upgrade was successful', () => { + it('displays a success toast message if application update was successful', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, title: 'GitLab Runner', @@ -349,13 +347,13 @@ describe('Application Row', () => { vm.updateSuccessful = true; return vm.$nextTick(() => { - expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.'); + expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner updated successfully.'); }); }); }); describe('Version', () => { - it('displays a version number if application has been upgraded', () => { + it('displays a version number if application has been updated', () => { const version = '0.1.45'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, @@ -363,15 +361,15 @@ describe('Application Row', () => { updateSuccessful: true, version, }); - const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-update-version'); - expect(upgradeDetails.innerHTML).toContain('Upgraded'); + expect(updateDetails.innerHTML).toContain('Updated'); expect(versionEl).not.toBe(null); expect(versionEl.innerHTML).toContain(version); }); - it('contains a link to the chart repo if application has been upgraded', () => { + it('contains a link to the chart repo if application has been updated', () => { const version = '0.1.45'; const chartRepo = 'https://gitlab.com/charts/gitlab-runner'; vm = mountComponent(ApplicationRow, { @@ -381,13 +379,13 @@ describe('Application Row', () => { chartRepo, version, }); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + const versionEl = vm.$el.querySelector('.js-cluster-application-update-version'); expect(versionEl.href).toEqual(chartRepo); expect(versionEl.target).toEqual('_blank'); }); - it('does not display a version number if application upgrade failed', () => { + it('does not display a version number if application update failed', () => { const version = '0.1.45'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, @@ -395,10 +393,10 @@ describe('Application Row', () => { updateFailed: true, version, }); - const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-update-version'); - expect(upgradeDetails.innerHTML).toContain('failed'); + expect(updateDetails.innerHTML).toContain('failed'); expect(versionEl).toBe(null); }); }); diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js index e057e2ac955..c146ef79be7 100644 --- a/spec/frontend/clusters/services/application_state_machine_spec.js +++ b/spec/frontend/clusters/services/application_state_machine_spec.js @@ -127,7 +127,7 @@ describe('applicationStateMachine', () => { describe(`current state is ${UPDATING}`, () => { it.each` expectedState | event | effects - ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true, updateAcknowledged: false }} + ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true }} ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} `(`transitions to $expectedState on $event event and applies $effects`, data => { const { expectedState, event, effects } = data; diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index 0d129349799..f2cc413512d 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -85,11 +85,10 @@ describe('Clusters Store', () => { statusReason: mockResponseData.applications[2].status_reason, requestReason: null, version: mockResponseData.applications[2].version, - upgradeAvailable: mockResponseData.applications[2].update_available, + updateAvailable: mockResponseData.applications[2].update_available, chartRepo: 'https://gitlab.com/charts/gitlab-runner', installed: false, installFailed: false, - updateAcknowledged: true, updateFailed: false, updateSuccessful: false, uninstallable: false, -- cgit v1.2.1 From 964b1f65d09ef3dd9d7d9a2be33d846c7fbda21c Mon Sep 17 00:00:00 2001 From: mfluharty Date: Wed, 5 Jun 2019 14:29:29 +0100 Subject: Add frontend specs Test that a regex is provided Test that it is the expected regex Test length of variable Test special character set --- .../ci_variable_list/ajax_variable_list_spec.js | 8 +++ .../ci_variable_list/ci_variable_list_spec.js | 59 ++++++++++++++++++++++ 2 files changed, 67 insertions(+) (limited to 'spec') diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 2839922fbd3..e6a969bd855 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -32,6 +32,7 @@ describe('AjaxFormVariableList', () => { saveButton, errorBox, saveEndpoint: container.dataset.saveEndpoint, + maskableRegex: container.dataset.maskableRegex, }); spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough(); @@ -220,4 +221,11 @@ describe('AjaxFormVariableList', () => { expect(row.dataset.isPersisted).toEqual('true'); }); }); + + describe('maskableRegex', () => { + it('takes in the regex provided by the data attribute', () => { + expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex); + }); + }); }); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 394e60fc22c..1b2b186234e 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -150,6 +150,65 @@ describe('VariableList', () => { .then(done) .catch(done.fail); }); + + describe('validateMaskability', () => { + let $row; + + const maskingErrorElement = '.js-row:nth-child(2) .masking-validation-error'; + + beforeEach(() => { + $row = $wrapper.find('.js-row:last-child'); + $row.find('.js-ci-variable-input-key').val('variable-key'); + }); + + it('has a regex provided via a data attribute', () => { + expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + }); + + it('allows values that are 8 characters long', done => { + $row.find('.js-ci-variable-input-value').val('looooong'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toHaveClass('hide'); + }) + .then(done) + .catch(done.fail); + }); + + it('rejects values that are shorter than 8 characters', done => { + $row.find('.js-ci-variable-input-value').val('short'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toBeVisible(); + }) + .then(done) + .catch(done.fail); + }); + + it('allows values with base 64 characters', done => { + $row.find('.js-ci-variable-input-value').val('abcABC123_+=/-'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toHaveClass('hide'); + }) + .then(done) + .catch(done.fail); + }); + + it('rejects values with other special characters', done => { + $row.find('.js-ci-variable-input-value').val('1234567$'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toBeVisible(); + }) + .then(done) + .catch(done.fail); + }); + }); }); describe('toggleEnableRow method', () => { -- cgit v1.2.1 From 6a90249b6a9c14391bf1ada9bb4726e8bf1e4ffd Mon Sep 17 00:00:00 2001 From: mfluharty Date: Thu, 6 Jun 2019 17:00:25 +0100 Subject: Add masked toggle click to frontend spec Need to set masked to true before testing now that the default is false --- spec/javascripts/ci_variable_list/ci_variable_list_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 1b2b186234e..064113e879a 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -154,11 +154,11 @@ describe('VariableList', () => { describe('validateMaskability', () => { let $row; - const maskingErrorElement = '.js-row:nth-child(2) .masking-validation-error'; + const maskingErrorElement = '.js-row:last-child .masking-validation-error'; beforeEach(() => { $row = $wrapper.find('.js-row:last-child'); - $row.find('.js-ci-variable-input-key').val('variable-key'); + $row.find('.ci-variable-masked-item .js-project-feature-toggle').click(); }); it('has a regex provided via a data attribute', () => { -- cgit v1.2.1 From 7100c6b5815b31ce5ca61494336b2aa07078e7e0 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 6 Jun 2019 16:52:20 +0000 Subject: Increase Jest timeout on CI to 5 seconds --- spec/frontend/helpers/timeout.js | 16 ++++++++++++++-- spec/frontend/test_setup.js | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js index e74598ae20a..702ef0be5aa 100644 --- a/spec/frontend/helpers/timeout.js +++ b/spec/frontend/helpers/timeout.js @@ -5,7 +5,13 @@ const IS_DEBUGGING = process.execArgv.join(' ').includes('--inspect-brk'); let testTimeoutNS; export const setTestTimeout = newTimeoutMS => { - testTimeoutNS = newTimeoutMS * NS_PER_MS; + const newTimeoutNS = newTimeoutMS * NS_PER_MS; + // never accept a smaller timeout than the default + if (newTimeoutNS < testTimeoutNS) { + return; + } + + testTimeoutNS = newTimeoutNS; jest.setTimeout(newTimeoutMS); }; @@ -13,7 +19,13 @@ export const setTestTimeout = newTimeoutMS => { // Useful for tests with jQuery, which is very slow in big DOMs. let temporaryTimeoutNS = null; export const setTestTimeoutOnce = newTimeoutMS => { - temporaryTimeoutNS = newTimeoutMS * NS_PER_MS; + const newTimeoutNS = newTimeoutMS * NS_PER_MS; + // never accept a smaller timeout than the default + if (newTimeoutNS < testTimeoutNS) { + return; + } + + temporaryTimeoutNS = newTimeoutNS; }; export const initializeTestTimeout = defaultTimeoutMS => { diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index c24f0bc4776..7e7cc1488b8 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -15,7 +15,7 @@ afterEach(() => }), ); -initializeTestTimeout(500); +initializeTestTimeout(process.env.CI ? 5000 : 500); // fail tests for unmocked requests beforeEach(done => { -- cgit v1.2.1 From c3338c920d6123174000ea11243cb7dc285cee03 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Thu, 6 Jun 2019 18:55:31 +0000 Subject: Add pages domains acme orders Extract acme double to helper Create ACME challanges for pages domains * Create order & challange through API * save them to database * request challenge validation We're saving order and challenge as one entity, that wouldn't be correct if we would order certificates for several domains simultaneously, but we always order certificate per domain Add controller for processing acme challenges redirected from pages Don't save acme challenge url - we don't use it Validate acme challenge attributes Encrypt private_key in acme orders --- .../controllers/acme_challenges_controller_spec.rb | 44 +++++++ spec/factories/pages_domain_acme_orders.rb | 17 +++ spec/lib/gitlab/lets_encrypt/challenge_spec.rb | 18 +-- spec/lib/gitlab/lets_encrypt/order_spec.rb | 38 +++--- spec/models/pages_domain_acme_order_spec.rb | 49 +++++++ .../create_acme_order_service_spec.rb | 63 +++++++++ ...obtain_lets_encrypt_certificate_service_spec.rb | 146 +++++++++++++++++++++ spec/support/helpers/lets_encrypt_helpers.rb | 40 ++++++ 8 files changed, 382 insertions(+), 33 deletions(-) create mode 100644 spec/controllers/acme_challenges_controller_spec.rb create mode 100644 spec/factories/pages_domain_acme_orders.rb create mode 100644 spec/models/pages_domain_acme_order_spec.rb create mode 100644 spec/services/pages_domains/create_acme_order_service_spec.rb create mode 100644 spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb (limited to 'spec') diff --git a/spec/controllers/acme_challenges_controller_spec.rb b/spec/controllers/acme_challenges_controller_spec.rb new file mode 100644 index 00000000000..cee06bed27b --- /dev/null +++ b/spec/controllers/acme_challenges_controller_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AcmeChallengesController do + describe '#show' do + let!(:acme_order) { create(:pages_domain_acme_order) } + + def make_request(domain, token) + get(:show, params: { domain: domain, token: token }) + end + + before do + make_request(domain, token) + end + + context 'with right domain and token' do + let(:domain) { acme_order.pages_domain.domain } + let(:token) { acme_order.challenge_token } + + it 'renders acme challenge file content' do + expect(response.body).to eq(acme_order.challenge_file_content) + end + end + + context 'when domain is invalid' do + let(:domain) { 'somewrongdomain.com' } + let(:token) { acme_order.challenge_token } + + it 'renders not found' do + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when token is invalid' do + let(:domain) { acme_order.pages_domain.domain } + let(:token) { 'wrongtoken' } + + it 'renders not found' do + expect(response).to have_gitlab_http_status(404) + end + end + end +end diff --git a/spec/factories/pages_domain_acme_orders.rb b/spec/factories/pages_domain_acme_orders.rb new file mode 100644 index 00000000000..7f9ee1c8f9c --- /dev/null +++ b/spec/factories/pages_domain_acme_orders.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :pages_domain_acme_order do + pages_domain + url { 'https://example.com/' } + expires_at { 1.day.from_now } + challenge_token { 'challenge_token' } + challenge_file_content { 'filecontent' } + + private_key { OpenSSL::PKey::RSA.new(4096).to_pem } + + trait :expired do + expires_at { 1.day.ago } + end + end +end diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb index 74622f356de..fcd92586362 100644 --- a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb @@ -3,23 +3,11 @@ require 'spec_helper' describe ::Gitlab::LetsEncrypt::Challenge do - delegated_methods = { - url: 'https://example.com/', - status: 'pending', - token: 'tokenvalue', - file_content: 'hereisfilecontent', - request_validation: true - } + include LetsEncryptHelpers - let(:acme_challenge) do - acme_challenge = instance_double('Acme::Client::Resources::Challenge') - allow(acme_challenge).to receive_messages(delegated_methods) - acme_challenge - end - - let(:challenge) { described_class.new(acme_challenge) } + let(:challenge) { described_class.new(acme_challenge_double) } - delegated_methods.each do |method, value| + LetsEncryptHelpers::ACME_CHALLENGE_METHODS.each do |method, value| describe "##{method}" do it 'delegates to Acme::Client::Resources::Challenge' do expect(challenge.public_send(method)).to eq(value) diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb index ee7058baf8d..1a759103c44 100644 --- a/spec/lib/gitlab/lets_encrypt/order_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb @@ -3,20 +3,13 @@ require 'spec_helper' describe ::Gitlab::LetsEncrypt::Order do - delegated_methods = { - url: 'https://example.com/', - status: 'valid' - } - - let(:acme_order) do - acme_order = instance_double('Acme::Client::Resources::Order') - allow(acme_order).to receive_messages(delegated_methods) - acme_order - end + include LetsEncryptHelpers + + let(:acme_order) { acme_order_double } let(:order) { described_class.new(acme_order) } - delegated_methods.each do |method, value| + LetsEncryptHelpers::ACME_ORDER_METHODS.each do |method, value| describe "##{method}" do it 'delegates to Acme::Client::Resources::Order' do expect(order.public_send(method)).to eq(value) @@ -25,15 +18,24 @@ describe ::Gitlab::LetsEncrypt::Order do end describe '#new_challenge' do - before do - challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01') - authorization = instance_double('Acme::Client::Resources::Authorization') - allow(authorization).to receive(:http).and_return(challenge) - allow(acme_order).to receive(:authorizations).and_return([authorization]) - end - it 'returns challenge' do expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge) end end + + describe '#request_certificate' do + let(:private_key) do + OpenSSL::PKey::RSA.new(4096).to_pem + end + + it 'generates csr and finalizes order' do + expect(acme_order).to receive(:finalize) do |csr:| + expect do + csr.csr # it's being evaluated lazily + end.not_to raise_error + end + + order.request_certificate(domain: 'example.com', private_key: private_key) + end + end end diff --git a/spec/models/pages_domain_acme_order_spec.rb b/spec/models/pages_domain_acme_order_spec.rb new file mode 100644 index 00000000000..4ffb4fc7389 --- /dev/null +++ b/spec/models/pages_domain_acme_order_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomainAcmeOrder do + using RSpec::Parameterized::TableSyntax + + describe '.expired' do + let!(:not_expired_order) { create(:pages_domain_acme_order) } + let!(:expired_order) { create(:pages_domain_acme_order, :expired) } + + it 'returns only expired orders' do + expect(described_class.count).to eq(2) + expect(described_class.expired).to eq([expired_order]) + end + end + + describe '.find_by_domain_and_token' do + let!(:domain) { create(:pages_domain, domain: 'test.com') } + let!(:acme_order) { create(:pages_domain_acme_order, challenge_token: 'righttoken', pages_domain: domain) } + + where(:domain_name, :challenge_token, :present) do + 'test.com' | 'righttoken' | true + 'test.com' | 'wrongtoken' | false + 'test.org' | 'righttoken' | false + end + + with_them do + subject { described_class.find_by_domain_and_token(domain_name, challenge_token).present? } + + it { is_expected.to eq(present) } + end + end + + subject { create(:pages_domain_acme_order) } + + describe 'associations' do + it { is_expected.to belong_to(:pages_domain) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:pages_domain) } + it { is_expected.to validate_presence_of(:expires_at) } + it { is_expected.to validate_presence_of(:url) } + it { is_expected.to validate_presence_of(:challenge_token) } + it { is_expected.to validate_presence_of(:challenge_file_content) } + it { is_expected.to validate_presence_of(:private_key) } + end +end diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb new file mode 100644 index 00000000000..d59aa9b979e --- /dev/null +++ b/spec/services/pages_domains/create_acme_order_service_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomains::CreateAcmeOrderService do + include LetsEncryptHelpers + + let(:pages_domain) { create(:pages_domain) } + + let(:challenge) { ::Gitlab::LetsEncrypt::Challenge.new(acme_challenge_double) } + + let(:order_double) do + Gitlab::LetsEncrypt::Order.new(acme_order_double).tap do |order| + allow(order).to receive(:new_challenge).and_return(challenge) + end + end + + let(:lets_encrypt_client) do + instance_double('Gitlab::LetsEncrypt::Client').tap do |client| + allow(client).to receive(:new_order).with(pages_domain.domain) + .and_return(order_double) + end + end + + let(:service) { described_class.new(pages_domain) } + + before do + allow(::Gitlab::LetsEncrypt::Client).to receive(:new).and_return(lets_encrypt_client) + end + + it 'saves order to database before requesting validation' do + allow(pages_domain.acme_orders).to receive(:create!).and_call_original + allow(challenge).to receive(:request_validation).and_call_original + + service.execute + + expect(pages_domain.acme_orders).to have_received(:create!).ordered + expect(challenge).to have_received(:request_validation).ordered + end + + it 'generates and saves private key' do + service.execute + + saved_order = PagesDomainAcmeOrder.last + expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error + end + + it 'properly saves order attributes' do + service.execute + + saved_order = PagesDomainAcmeOrder.last + expect(saved_order.url).to eq(order_double.url) + expect(saved_order.expires_at).to be_like_time(order_double.expires) + end + + it 'properly saves challenge attributes' do + service.execute + + saved_order = PagesDomainAcmeOrder.last + expect(saved_order.challenge_token).to eq(challenge.token) + expect(saved_order.challenge_file_content).to eq(challenge.file_content) + end +end diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb new file mode 100644 index 00000000000..6d7be27939c --- /dev/null +++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomains::ObtainLetsEncryptCertificateService do + include LetsEncryptHelpers + + let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) } + let(:service) { described_class.new(pages_domain) } + + before do + stub_lets_encrypt_settings + end + + def expect_to_create_acme_challenge + expect(::PagesDomains::CreateAcmeOrderService).to receive(:new).with(pages_domain) + .and_wrap_original do |m, *args| + create_service = m.call(*args) + + expect(create_service).to receive(:execute) + + create_service + end + end + + def stub_lets_encrypt_order(url, status) + order = ::Gitlab::LetsEncrypt::Order.new(acme_order_double(status: status)) + + allow_any_instance_of(::Gitlab::LetsEncrypt::Client).to( + receive(:load_order).with(url).and_return(order) + ) + + order + end + + context 'when there is no acme order' do + it 'creates acme order' do + expect_to_create_acme_challenge + + service.execute + end + end + + context 'when there is expired acme order' do + let!(:existing_order) do + create(:pages_domain_acme_order, :expired, pages_domain: pages_domain) + end + + it 'removes acme order and creates new one' do + expect_to_create_acme_challenge + + service.execute + + expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil + end + end + + %w(pending processing).each do |status| + context "there is an order in '#{status}' status" do + let(:existing_order) do + create(:pages_domain_acme_order, pages_domain: pages_domain) + end + + before do + stub_lets_encrypt_order(existing_order.url, status) + end + + it 'does not raise errors' do + expect do + service.execute + end.not_to raise_error + end + end + end + + context 'when order is ready' do + let(:existing_order) do + create(:pages_domain_acme_order, pages_domain: pages_domain) + end + + let!(:api_order) do + stub_lets_encrypt_order(existing_order.url, 'ready') + end + + it 'request certificate' do + expect(api_order).to receive(:request_certificate).and_call_original + + service.execute + end + end + + context 'when order is valid' do + let(:existing_order) do + create(:pages_domain_acme_order, pages_domain: pages_domain) + end + + let!(:api_order) do + stub_lets_encrypt_order(existing_order.url, 'valid') + end + + let(:certificate) do + key = OpenSSL::PKey.read(existing_order.private_key) + + subject = "/C=BE/O=Test/OU=Test/CN=#{pages_domain.domain}" + + cert = OpenSSL::X509::Certificate.new + cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject) + cert.not_before = Time.now + cert.not_after = 1.year.from_now + cert.public_key = key.public_key + cert.serial = 0x0 + cert.version = 2 + + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = cert + cert.extensions = [ + ef.create_extension("basicConstraints", "CA:TRUE", true), + ef.create_extension("subjectKeyIdentifier", "hash") + ] + cert.add_extension ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + + cert.sign key, OpenSSL::Digest::SHA1.new + + cert.to_pem + end + + before do + expect(api_order).to receive(:certificate) { certificate } + end + + it 'saves private_key and certificate for domain' do + service.execute + + expect(pages_domain.key).to be_present + expect(pages_domain.certificate).to eq(certificate) + end + + it 'removes order from database' do + service.execute + + expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil + end + end +end diff --git a/spec/support/helpers/lets_encrypt_helpers.rb b/spec/support/helpers/lets_encrypt_helpers.rb index 7f0886b451c..2857416ad95 100644 --- a/spec/support/helpers/lets_encrypt_helpers.rb +++ b/spec/support/helpers/lets_encrypt_helpers.rb @@ -1,6 +1,26 @@ # frozen_string_literal: true module LetsEncryptHelpers + ACME_ORDER_METHODS = { + url: 'https://example.com/', + status: 'valid', + expires: 2.days.from_now + }.freeze + + ACME_CHALLENGE_METHODS = { + status: 'pending', + token: 'tokenvalue', + file_content: 'hereisfilecontent', + request_validation: true + }.freeze + + def stub_lets_encrypt_settings + stub_application_setting( + lets_encrypt_notification_email: 'myemail@test.example.com', + lets_encrypt_terms_of_service_accepted: true + ) + end + def stub_lets_encrypt_client client = instance_double('Acme::Client') @@ -16,4 +36,24 @@ module LetsEncryptHelpers client end + + def acme_challenge_double + challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01') + allow(challenge).to receive_messages(ACME_CHALLENGE_METHODS) + challenge + end + + def acme_authorization_double + authorization = instance_double('Acme::Client::Resources::Authorization') + allow(authorization).to receive(:http).and_return(acme_challenge_double) + authorization + end + + def acme_order_double(attributes = {}) + acme_order = instance_double('Acme::Client::Resources::Order') + allow(acme_order).to receive_messages(ACME_ORDER_METHODS.merge(attributes)) + allow(acme_order).to receive(:authorizations).and_return([acme_authorization_double]) + allow(acme_order).to receive(:finalize) + acme_order + end end -- cgit v1.2.1 From d1d05ae4f34aab350db86e03859c97e90f515520 Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Thu, 6 Jun 2019 19:14:09 +0000 Subject: Add certificate valid time to pages domain table Save certificate validity time for pages domains on save Fill validity time for existing pages domains in background migration --- .../enqueue_verify_pages_domain_workers_spec.rb | 6 ++- ...alid_time_for_pages_domain_certificates_spec.rb | 46 ++++++++++++++++++++++ spec/models/pages_domain_spec.rb | 11 ++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb (limited to 'spec') diff --git a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb index afcaefa0591..abf39317188 100644 --- a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb +++ b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb @@ -8,9 +8,13 @@ describe EnqueueVerifyPagesDomainWorkers, :sidekiq, :migration do end end + let(:domains_table) { table(:pages_domains) } + describe '#up' do it 'enqueues a verification worker for every domain' do - domains = 1.upto(3).map { |i| PagesDomain.create!(domain: "my#{i}.domain.com") } + domains = Array.new(3) do |i| + domains_table.create!(domain: "my#{i}.domain.com", verification_code: "123#{i}") + end expect { migrate! }.to change(PagesDomainVerificationWorker.jobs, :size).by(3) diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb new file mode 100644 index 00000000000..54f3e264df0 --- /dev/null +++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb') + +describe ScheduleFillValidTimeForPagesDomainCertificates, :migration, :sidekiq do + let(:migration_class) { described_class::MIGRATION } + let(:migration_name) { migration_class.to_s.demodulize } + + let(:domains_table) { table(:pages_domains) } + + let(:certificate) do + File.read('spec/fixtures/passphrase_x509_certificate.crt') + end + + before do + domains_table.create!(domain: "domain1.example.com", verification_code: "123") + domains_table.create!(domain: "domain2.example.com", verification_code: "123", certificate: '') + domains_table.create!(domain: "domain3.example.com", verification_code: "123", certificate: certificate) + domains_table.create!(domain: "domain4.example.com", verification_code: "123", certificate: certificate) + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + first_id = domains_table.find_by_domain("domain3.example.com").id + last_id = domains_table.find_by_domain("domain4.example.com").id + + expect(migration_name).to be_scheduled_delayed_migration(5.minutes, first_id, last_id) + expect(BackgroundMigrationWorker.jobs.size).to eq(1) + end + end + end + + it 'sets certificate valid_not_before/not_after' do + perform_enqueued_jobs do + migrate! + + domain = domains_table.find_by_domain("domain3.example.com") + expect(domain.certificate_valid_not_before) + .to eq(Time.parse("2018-03-23 14:02:08 UTC")) + expect(domain.certificate_valid_not_after) + .to eq(Time.parse("2019-03-23 14:02:08 UTC")) + end + end +end diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index ec4d4517f82..fdc81359d34 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -81,6 +81,17 @@ describe PagesDomain do end end + describe 'when certificate is specified' do + let(:domain) { build(:pages_domain) } + + it 'saves validity time' do + domain.save + + expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC")) + expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC")) + end + end + describe 'validate certificate' do subject { domain } -- cgit v1.2.1 From 59f9c5a380aa679b9dccd093f76d7eb7473f872d Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Thu, 6 Jun 2019 16:23:14 -0500 Subject: Removes duplicated shared_context folder Most of the shared context are located under spec/support/shared_contexts, we only have one file under spec/support/shared_context, this MR moves it to spec/support/shared_contexts instead --- .../policies/project_policy_shared_context.rb | 88 ---------------------- .../policies/project_policy_shared_context.rb | 88 ++++++++++++++++++++++ 2 files changed, 88 insertions(+), 88 deletions(-) delete mode 100644 spec/support/shared_context/policies/project_policy_shared_context.rb create mode 100644 spec/support/shared_contexts/policies/project_policy_shared_context.rb (limited to 'spec') diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb deleted file mode 100644 index 54d9f5b15f2..00000000000 --- a/spec/support/shared_context/policies/project_policy_shared_context.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_context 'ProjectPolicy context' do - set(:guest) { create(:user) } - set(:reporter) { create(:user) } - set(:developer) { create(:user) } - set(:maintainer) { create(:user) } - set(:owner) { create(:user) } - set(:admin) { create(:admin) } - let(:project) { create(:project, :public, namespace: owner.namespace) } - - let(:base_guest_permissions) do - %i[ - read_project read_board read_list read_wiki read_issue - read_project_for_iids read_issue_iid read_label - read_milestone read_project_snippet read_project_member read_note - create_project create_issue create_note upload_file create_merge_request_in - award_emoji - ] - end - - let(:base_reporter_permissions) do - %i[ - download_code fork_project create_project_snippet update_issue - admin_issue admin_label admin_list read_commit_status read_build - read_container_image read_pipeline read_environment read_deployment - read_merge_request download_wiki_code read_sentry_issue read_prometheus - ] - end - - let(:team_member_reporter_permissions) do - %i[build_download_code build_read_container_image] - end - - let(:developer_permissions) do - %i[ - admin_milestone admin_merge_request update_merge_request create_commit_status - update_commit_status create_build update_build create_pipeline - update_pipeline create_merge_request_from create_wiki push_code - resolve_note create_container_image update_container_image - create_environment create_deployment create_release update_release - ] - end - - let(:base_maintainer_permissions) do - %i[ - push_to_delete_protected_branch update_project_snippet update_environment - update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project - admin_commit_status admin_build admin_container_image - admin_pipeline admin_environment admin_deployment destroy_release add_cluster - daily_statistics - ] - end - - let(:public_permissions) do - %i[ - download_code fork_project read_commit_status read_pipeline - read_container_image build_download_code build_read_container_image - download_wiki_code read_release - ] - end - - let(:base_owner_permissions) do - %i[ - change_namespace change_visibility_level rename_project remove_project - archive_project remove_fork_project destroy_merge_request destroy_issue - set_issue_iid set_issue_created_at set_note_created_at - ] - end - - # Used in EE specs - let(:additional_guest_permissions) { [] } - let(:additional_reporter_permissions) { [] } - let(:additional_maintainer_permissions) { [] } - let(:additional_owner_permissions) { [] } - - let(:guest_permissions) { base_guest_permissions + additional_guest_permissions } - let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions } - let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions } - let(:owner_permissions) { base_owner_permissions + additional_owner_permissions } - - before do - project.add_guest(guest) - project.add_maintainer(maintainer) - project.add_developer(developer) - project.add_reporter(reporter) - end -end diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb new file mode 100644 index 00000000000..54d9f5b15f2 --- /dev/null +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +RSpec.shared_context 'ProjectPolicy context' do + set(:guest) { create(:user) } + set(:reporter) { create(:user) } + set(:developer) { create(:user) } + set(:maintainer) { create(:user) } + set(:owner) { create(:user) } + set(:admin) { create(:admin) } + let(:project) { create(:project, :public, namespace: owner.namespace) } + + let(:base_guest_permissions) do + %i[ + read_project read_board read_list read_wiki read_issue + read_project_for_iids read_issue_iid read_label + read_milestone read_project_snippet read_project_member read_note + create_project create_issue create_note upload_file create_merge_request_in + award_emoji + ] + end + + let(:base_reporter_permissions) do + %i[ + download_code fork_project create_project_snippet update_issue + admin_issue admin_label admin_list read_commit_status read_build + read_container_image read_pipeline read_environment read_deployment + read_merge_request download_wiki_code read_sentry_issue read_prometheus + ] + end + + let(:team_member_reporter_permissions) do + %i[build_download_code build_read_container_image] + end + + let(:developer_permissions) do + %i[ + admin_milestone admin_merge_request update_merge_request create_commit_status + update_commit_status create_build update_build create_pipeline + update_pipeline create_merge_request_from create_wiki push_code + resolve_note create_container_image update_container_image + create_environment create_deployment create_release update_release + ] + end + + let(:base_maintainer_permissions) do + %i[ + push_to_delete_protected_branch update_project_snippet update_environment + update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project + admin_commit_status admin_build admin_container_image + admin_pipeline admin_environment admin_deployment destroy_release add_cluster + daily_statistics + ] + end + + let(:public_permissions) do + %i[ + download_code fork_project read_commit_status read_pipeline + read_container_image build_download_code build_read_container_image + download_wiki_code read_release + ] + end + + let(:base_owner_permissions) do + %i[ + change_namespace change_visibility_level rename_project remove_project + archive_project remove_fork_project destroy_merge_request destroy_issue + set_issue_iid set_issue_created_at set_note_created_at + ] + end + + # Used in EE specs + let(:additional_guest_permissions) { [] } + let(:additional_reporter_permissions) { [] } + let(:additional_maintainer_permissions) { [] } + let(:additional_owner_permissions) { [] } + + let(:guest_permissions) { base_guest_permissions + additional_guest_permissions } + let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions } + let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions } + let(:owner_permissions) { base_owner_permissions + additional_owner_permissions } + + before do + project.add_guest(guest) + project.add_maintainer(maintainer) + project.add_developer(developer) + project.add_reporter(reporter) + end +end -- cgit v1.2.1 From a30d8e4a49644af7b082892c677d5d3365309afe Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 6 Jun 2019 15:57:29 -0700 Subject: Make OpenID Connect work without requiring a name If there is no name argument given, OmniAuth will try to guess the name by the class name. In https://github.com/omniauth/omniauth/blob/v1.9.0/lib/omniauth/strategy.rb#L139, `OmniAuth::Strategies::OpenIDConnect` gets translated to `openidconnect`. This leads to an immediate 404 error after clicking the login button because OmniAuth can't match the current route (/users/auth/openid_connect) against the expected one (/users/auth/openidconnect). Other providers, such as Google OAuth2, set this name as the default option within the OmniAuth Strategy. Until a fix is merged upstream, let's just set the parameter ourselves. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/62208 --- spec/lib/gitlab/omniauth_initializer_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec') diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb index f9c0daf1ef1..32296caf819 100644 --- a/spec/lib/gitlab/omniauth_initializer_spec.rb +++ b/spec/lib/gitlab/omniauth_initializer_spec.rb @@ -83,5 +83,13 @@ describe Gitlab::OmniauthInitializer do subject.execute([cas3_config]) end + + it 'configures name for openid_connect' do + openid_connect_config = { 'name' => 'openid_connect', 'args' => {} } + + expect(devise_config).to receive(:omniauth).with(:openid_connect, name: 'openid_connect') + + subject.execute([openid_connect_config]) + end end end -- cgit v1.2.1 From 1bafad515f4f9d63a7ff152b0f17979375d37a9c Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Tue, 4 Jun 2019 17:31:22 -0500 Subject: IDE trigger files change event - On CMD+S when editor is focused - On editor blur - When file is created in tree - When file is removed in tree - When file is renamed in tree --- spec/javascripts/ide/stores/actions/file_spec.js | 16 ++++++++++++++++ spec/javascripts/ide/stores/actions_spec.js | 5 +++-- 2 files changed, 19 insertions(+), 2 deletions(-) (limited to 'spec') diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index e6fb08bcc49..dd2313dc800 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -719,4 +719,20 @@ describe('IDE store file actions', () => { .catch(done.fail); }); }); + + describe('triggerFilesChange', () => { + beforeEach(() => { + spyOn(eventHub, '$emit'); + }); + + it('emits event that files have changed', done => { + store + .dispatch('triggerFilesChange') + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change'); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 04e236fb042..37354283cab 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -488,7 +488,7 @@ describe('Multi-file store actions', () => { 'path', store.state, [{ type: types.DELETE_ENTRY, payload: 'path' }], - [{ type: 'burstUnusedSeal' }], + [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }], done, ); }); @@ -510,7 +510,7 @@ describe('Multi-file store actions', () => { payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, }, ], - [{ type: 'deleteEntry', payload: 'test' }], + [{ type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }], done, ); }); @@ -558,6 +558,7 @@ describe('Multi-file store actions', () => { }, }, { type: 'deleteEntry', payload: 'test' }, + { type: 'triggerFilesChange' }, ], done, ); -- cgit v1.2.1 From 3f5447dbbd0a939943ee12903490c264d79d6d27 Mon Sep 17 00:00:00 2001 From: Sarah Yasonik Date: Fri, 7 Jun 2019 08:48:10 +0000 Subject: Remove metrics_time_window feature flag --- .../projects/environments_controller_spec.rb | 14 -------- spec/javascripts/monitoring/dashboard_spec.js | 40 +++------------------- 2 files changed, 4 insertions(+), 50 deletions(-) (limited to 'spec') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index d5eea5b0439..9699f2952f2 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -433,20 +433,6 @@ describe Projects::EnvironmentsController do end context 'when only one time param is provided' do - context 'when :metrics_time_window feature flag is disabled' do - before do - stub_feature_flags(metrics_time_window: false) - expect(environment).to receive(:additional_metrics).with(no_args).and_return(nil) - end - - it 'returns a time-window agnostic response' do - additional_metrics(start: '1552647300.651094') - - expect(response).to have_gitlab_http_status(204) - expect(json_response).to eq({}) - end - end - it 'raises an error when start is missing' do expect { additional_metrics(end: '1552647300.651094') } .to raise_error(ActionController::ParameterMissing) diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 80b9b740b94..1a371c3adaf 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -68,7 +68,7 @@ describe('Dashboard', () => { it('shows a getting started empty state when no metrics are present', () => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, showTimeWindowDropdown: false }, + propsData: { ...propsData }, store, }); @@ -85,7 +85,7 @@ describe('Dashboard', () => { it('shows up a loading state', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false }, + propsData: { ...propsData, hasMetrics: true }, store, }); @@ -102,7 +102,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showLegend: false, - showTimeWindowDropdown: false, }, store, }); @@ -122,7 +121,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -142,7 +140,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -173,7 +170,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -203,7 +199,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -237,7 +232,6 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, environmentsEndpoint: '', - showTimeWindowDropdown: false, }, store, }); @@ -250,27 +244,6 @@ describe('Dashboard', () => { }); }); - it('does not show the time window dropdown when the feature flag is not set', done => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - showTimeWindowDropdown: false, - }, - store, - }); - - setTimeout(() => { - const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); - - expect(timeWindowDropdown).toBeNull(); - - done(); - }); - }); - it('renders the time window dropdown with a set of options', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), @@ -278,7 +251,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: true, }, store, }); @@ -304,7 +276,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: true, }, store, }); @@ -338,7 +309,7 @@ describe('Dashboard', () => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + propsData: { ...propsData, hasMetrics: true }, store, }); @@ -359,7 +330,7 @@ describe('Dashboard', () => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + propsData: { ...propsData, hasMetrics: true }, store, }); @@ -388,7 +359,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -424,7 +394,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, externalDashboardUrl: '/mockUrl', }, store, @@ -450,7 +419,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, externalDashboardUrl: '', }, store, -- cgit v1.2.1 From b70d23c25a4bc54fda22135b0a76bae102cfd88b Mon Sep 17 00:00:00 2001 From: Patrick Derichs Date: Fri, 7 Jun 2019 11:15:55 +0200 Subject: Add task count and completed count to responses of Issue and MR Add spec for task_completion_status Add test cases for task_completion_status result Extracted shared samples Add new spec file for task completion status response Fix style errors Add changelog entry Changed samples to Hashes Remove test for successful request Remove not nil expectation Add task_completion_status to api documentation for issues Add task_completion_status to api documentation for merge_requests Refactor spec so it just requests one specific item Add task_completion_status to Taskable Simplified task completion status in entities Refactor spec so it separates status code check and content check Fix spec description text and field name --- spec/requests/api/task_completion_status_spec.rb | 85 ++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 spec/requests/api/task_completion_status_spec.rb (limited to 'spec') diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb new file mode 100644 index 00000000000..ee2531197b1 --- /dev/null +++ b/spec/requests/api/task_completion_status_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'task completion status response' do + set(:user) { create(:user) } + set(:project) do + create(:project, :public, creator_id: user.id, namespace: user.namespace) + end + + shared_examples 'taskable completion status provider' do |path| + samples = [ + { + description: '', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: 'Lorem ipsum', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: %{- [ ] task 1 + - [x] task 2 }, + expected_count: 2, + expected_completed_count: 1 + }, + { + description: %{- [ ] task 1 + - [ ] task 2 }, + expected_count: 2, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1 + - [x] task 2 }, + expected_count: 2, + expected_completed_count: 2 + }, + { + description: %{- [ ] task 1}, + expected_count: 1, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1}, + expected_count: 1, + expected_completed_count: 1 + } + ] + samples.each do |sample_data| + context "with a description of #{sample_data[:description].inspect}" do + before do + taskable.update!(description: sample_data[:description]) + + get api("#{path}?iids[]=#{taskable.iid}", user) + end + + it { expect(response).to have_gitlab_http_status(200) } + + it 'returns the expected results' do + expect(json_response).to be_an Array + expect(json_response).not_to be_empty + + task_completion_status = json_response.first['task_completion_status'] + expect(task_completion_status['count']).to eq(sample_data[:expected_count]) + expect(task_completion_status['completed_count']).to eq(sample_data[:expected_completed_count]) + end + end + end + end + + context 'task list completion status for issues' do + it_behaves_like 'taskable completion status provider', '/issues' do + let(:taskable) { create(:issue, project: project, author: user) } + end + end + + context 'task list completion status for merge_requests' do + it_behaves_like 'taskable completion status provider', '/merge_requests' do + let(:taskable) { create(:merge_request, source_project: project, target_project: project, author: user) } + end + end +end -- cgit v1.2.1 From 8f537845ed513639518bd44fbb0a61426aec2481 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Fri, 7 Jun 2019 13:26:26 +0200 Subject: Fix migration specs using factories We need to stub default_git_depth and default_git_depth= because some old migrations specs try to create a record using schema before that column was introduced. The `let!` calls were executed before the `before` hook which still caused some factories to fail, so they're created in the `before` hook now as well. --- .../migrate_legacy_uploads_spec.rb | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) (limited to 'spec') diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb index 802c8fb8c97..16b9de6a84e 100644 --- a/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb @@ -5,23 +5,39 @@ describe Gitlab::BackgroundMigration::MigrateLegacyUploads, :migration, schema: let(:test_dir) { FileUploader.options['storage_path'] } # rubocop: disable RSpec/FactoriesInMigrationSpecs - let!(:namespace) { create(:namespace) } - let!(:project) { create(:project, :legacy_storage, namespace: namespace) } - let!(:issue) { create(:issue, project: project) } + let(:namespace) { create(:namespace) } + let(:project) { create(:project, :legacy_storage, namespace: namespace) } + let(:issue) { create(:issue, project: project) } - let!(:note1) { create(:note, note: 'some note text awesome', project: project, noteable: issue) } - let!(:note2) { create(:note, note: 'some note', project: project, noteable: issue) } + let(:note1) { create(:note, note: 'some note text awesome', project: project, noteable: issue) } + let(:note2) { create(:note, note: 'some note', project: project, noteable: issue) } - let!(:hashed_project) { create(:project, namespace: namespace) } - let!(:issue_hashed_project) { create(:issue, project: hashed_project) } - let!(:note_hashed_project) { create(:note, note: 'some note', project: hashed_project, attachment: 'text.pdf', noteable: issue_hashed_project) } + let(:hashed_project) { create(:project, namespace: namespace) } + let(:issue_hashed_project) { create(:issue, project: hashed_project) } + let(:note_hashed_project) { create(:note, note: 'some note', project: hashed_project, attachment: 'text.pdf', noteable: issue_hashed_project) } - let!(:standard_upload) do + let(:standard_upload) do create(:upload, path: "secretabcde/image.png", model_id: create(:project).id, model_type: 'Project', uploader: 'FileUploader') end + before do + # This migration was created before we introduced ProjectCiCdSetting#default_git_depth + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) + + namespace + project + issue + note1 + note2 + hashed_project + issue_hashed_project + note_hashed_project + standard_upload + end + def create_remote_upload(model, filename) create(:upload, :attachment_upload, path: "note/attachment/#{model.id}/#{filename}", secret: nil, -- cgit v1.2.1 From 25269f66ba60e211ad64ff08beb9799be1103c52 Mon Sep 17 00:00:00 2001 From: Martin Hanzel Date: Fri, 7 Jun 2019 13:23:10 +0000 Subject: Move Vue models from vue_shared to boards Some classes in vue_shared were used only by boards, so they were moved to the boards directory. --- spec/frontend/boards/modal_store_spec.js | 4 ++-- spec/javascripts/boards/board_card_spec.js | 4 ++-- spec/javascripts/boards/boards_store_spec.js | 4 ++-- spec/javascripts/boards/issue_card_spec.js | 4 ++-- spec/javascripts/boards/issue_spec.js | 4 ++-- spec/javascripts/boards/list_spec.js | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) (limited to 'spec') diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js index 3257a3fb8a3..4dd27e94d97 100644 --- a/spec/frontend/boards/modal_store_spec.js +++ b/spec/frontend/boards/modal_store_spec.js @@ -1,7 +1,7 @@ /* global ListIssue */ -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import Store from '~/boards/stores/modal_store'; diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index e1017130bed..13b708a03d5 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import eventHub from '~/boards/eventhub'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/list'; import boardsStore from '~/boards/stores/boards_store'; import boardCard from '~/boards/components/board_card.vue'; diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index b5559db8784..e81115e10c9 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -6,8 +6,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import Cookies from 'js-cookie'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index a5bf97bdcc2..8a20911cc66 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -4,8 +4,8 @@ import Vue from 'vue'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import IssueCardInner from '~/boards/components/issue_card_inner.vue'; diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index e4ff3eb381f..bb7abe52eae 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,8 +1,8 @@ /* global ListIssue */ import Vue from 'vue'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index bb6fc6c693d..15c9ff6dfb4 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -4,8 +4,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import _ from 'underscore'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/services/board_service'; -- cgit v1.2.1